Java

[Java] Java로 HTTP GET, POST 통신하기

림 림 2020. 10. 22. 16:23
반응형

최근에 프로젝트를 진행하면서 Java로 외부 API를 활용하여 데이터를 얻어와야 하는 일이 생겼습니다.

그래서 Java로 HTTP 통신을 하는 방법에 대해 공부해보았습니다.

Oracle JDK 11 버전을 사용했으며, Oracle 공식 문서를 참고하고 직접 Java 클래스를 열어보며 분석했습니다. 

* 전체 코드는 맨 뒤에 있습니다.

 

Java API 활용하기

Java SE 플랫폼의 기본 API를 정의하는 java.base 모듈에는 java.lang 패키지를 비롯한 다양한 패키지들이 있습니다. 그 중에 java.net 패키지에는 네트워크 애플리케이션을 구현하기 위한 클래스들이 있습니다. 이 패키지에 있는 HttpUrlConnection 클래스와 URL 클래스를 활용하여 HTTP 통신을 할 수 있습니다.

1. URL 클래스

참고: docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/URL.html

URL 클래스는 자원을 요청할 주소를 나타내는 클래스입니다. URL 클래스를 살펴보면 Serializable 인터페이스를 구현하고 있어, 직렬화를 위한 조건을 갖추고 있습니다.

public final class URL implements java.io.Serializable { ... }

 

1) URL 객체 생성하기

URL 클래스의 생성자는 다양한 형태로 선언되어 있습니다. 그 중에 가장 많이 이용되는 생성자는 url을 문자열 형태로 나타낸 객체의 레퍼런스를 인자로 전달받는 생성자입니다. 

public URL(String spec) throws MalformedURLException { ... }

 

예제)
https://www.google.com 의 url을 나타내는 URL 객체를 생성하고 싶다면 다음과 같이 코드를 작성하면 됩니다.

import java.net.URL;

...


URL url = new URL("https://www.google.com");

공식 Java API 문서를 보면, 위에서 사용한 생성자를 비롯하여 모든 URL 클래스 객체의 생성자는 MalformedURLException예외를 throws 하여 예외처리에 대한 책임을 전가하고 있습니다. MalformedURLException은 생성자의 인자로 받은 url 문자열이 null이거나 프로토콜을 알 수 없을 때 등의 상황에 발생합니다. 따라서 URL 객체를 생성한 클래스에서 그 예외를 처리해주어야 합니다.

try {
	URL url = new URL("https://www.google.com");
} catch (MalformedURLException e) {
	e.printStackTrace();
}

 

2) 연결 객체 얻기

URL 클래스에 선언된 openConnection() 메소드는 url 객체에 대한 연결을 담당하는 URLConnection 객체를 반환합니다. 

    transient URLStreamHandler handler;
    
    ...
    
    public URLConnection openConnection() throws java.io.IOException {
        return handler.openConnection(this);
    }

 

예제)
위에서 생성한 URL 객체에 대한 연결 객체를 얻어보겠습니다.

URLConnection connection = url.openConnection()

 

 

2. HttpURLConnection 클래스

참고: docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpURLConnection.html

HttpURLConnection 클래스는 HTTP 프로토콜 통신을 위한 클래스입니다. 각각의 객체들은 하나의 요청을 위해 사용됩니다. HttpURLConnection 클래스를 살펴보면 URLConnection 클래스를 확장한(상속받은) 추상클래스임을 알 수 있습니다.

public abstract class HttpURLConnection extends URLConnection { ... }

 

1) HttpURLConnection 객체 생성

위에서 URL 객체의 openConnection() 메소드를 통해 URLConnection 객체를 얻을 수 있었습니다. HttpURLConnection객체는 URLConnection 객체를 확장하고(상속받고)있기 때문에 Type Casting을 통해 HttpURLConnection객체를 쉽게 얻을 수 있습니다.

예제)
https://www.google.com 의 url에 연결하는 HTTP 연결 객체를 생성하고 싶다면 다음과 같이 코드를 작성하면 됩니다. (예외 처리 생략)

import java.net.URL;
import java.net.HttpURLConnection;

...

URL url = new URL("https://www.google.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

 

2) 요청 메소드 설정

HTTP 요청을 위해서는 요청 메소드를 설정해야합니다. setRequestMethod() 메소드는 요청 메소드를 문자열 파라미터로 받아서 유효한 요청 메소드면 method 멤버 변수에 요청 메소드를 저장하고, 아니면 ProtocolException 예외를 발생시킵니다. 처리할 수 있는 요청 메소드로는 GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE 가 있습니다.

    public void setRequestMethod(String method) throws ProtocolException {
        if (connected) {
            throw new ProtocolException("Can't reset method: already connected");
        }
        // This restriction will prevent people from using this class to
        // experiment w/ new HTTP methods using java.  But it should
        // be placed for security - the request String could be
        // arbitrarily long.

        for (int i = 0; i < methods.length; i++) {
            if (methods[i].equals(method)) {
                if (method.equals("TRACE")) {
                    SecurityManager s = System.getSecurityManager();
                    if (s != null) {
                        s.checkPermission(new NetPermission("allowHttpTrace"));
                    }
                }
                this.method = method;
                return;
            }
        }
        throw new ProtocolException("Invalid HTTP method: " + method);
    }

 

method 멤버변수는 기본으로 "GET"으로 초기화되어있습니다. 따라서 setRequestMethod()를 통해 요청 메소드를 설정하지 않으면 GET 요청을 보내게 됩니다.

    protected String method = "GET";

 

예제)
위에서 생성한 HttpURLConnection 객체로 GET 요청을 하고 싶다면 다음과 같이 코드를 작성하면 됩니다.

connection.setRequestMethod("GET");

POST 요청을 하고 싶다면 다음과 같이 코드를 작성하면 됩니다.

connection.setRequestMethod("POST");

 

3) 요청 헤더 설정

다음으로 HttpURLConnection클래스가 확장(상속)하는 URLConnection클래스에 정의된 setRequestProperty() 메소드로 요청 헤더를 설정할 수 있습니다. 

    public void setRequestProperty(String key, String value) {
        checkConnected();
        if (key == null)
            throw new NullPointerException ("key is null");

        if (requests == null)
            requests = new MessageHeader();

        requests.set(key, value);
    }

setRequestProperty() 메소드는 String 타입의 key, value 파라미터를 받습니다. 각 파라미터를 통해 요청 헤더의 이름과 값을 설정할 수 있습니다. 만약 key값이 null이라면 NullPointerException 예외를 발생시킵니다.

예제)
HTTP 요청을 하는 사용자의 애플리케이션 타입, 운영 체제, 소프트웨어 벤더 또는 소프트웨어 버전 등을 식별할 수 있는 User-Agent 헤더를 설정하고 싶다면 다음과 같이 코드를 작성하면 됩니다.

private static final String USER_AGENT = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:35.0) Gecko/20100101 Firefox/35.0";

...

connection.setRequestProperty("User-Agent", USER_AGENT);
        

 

4) POST 요청시 데이터 넘겨주기

POST 요청을 할 때에는 OutputStream 객체로 데이터를 전송합니다. setDoOutput() 메소드를 통해 OutputStream 객체로 전송할 데이터가 있다는 옵션을 설정해야 합니다. checkConnected() 메소드에서는 연결 객체가 연결되어있는지 확인하고, 이미 연결되어있다면 IllegalStateException 예외를 발생시킵니다.

    public void setDoOutput(boolean dooutput) {
        checkConnected();
        doOutput = dooutput;
    }

setDoOutput() 메소드는 boolean 타입의 dooutput 파라미터를 받아 doOutput멤버 변수에 저장합니다. doOutput 변수가 true이면 OutputStream으로 데이터를 전송한다는 뜻이고, false이면 하지 않는다는 뜻인데, 기본으로 false로 초기화되어있기 때문에 POST로 데이터를 전송하려면 꼭 옵션을 설정해줘야 합니다.

    protected boolean doOutput = false;

 

getOutputStream() 메소드를 통해 연결에 사용할 OutputStream 객체를 얻을 수 있습니다. 프로토콜이 출력을 지원하지 않는다면 UnknownServiceException 예외를 발생시킵니다.

    public OutputStream getOutputStream() throws IOException {
        throw new UnknownServiceException("protocol doesn't support output");
    }

전송할 데이터가 문자열일 경우는 OutputStream 클래스를 확장하는 DataOutputStream 클래스의 writebytes() 메소드를 활용하여 쉽게 데이터를 설정할 수 있습니다. DataOutputStream 클래스는 생성자에 OutptStream 객체를 전달하여 생성할 수 있습니다. 따라서 위에서 getOutputSteam() 메소드를 통해 얻은 객체를 바로 넣어줄 수 있습니다.

예제)
위에서 얻은 연결 객체에 전송할 데이터를 설정해보겠습니다. 데이터를 설정하고 flush(), close()해줍니다.

private static final String DATA = "test data";

...

DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
outputStream.writeBytes(DATA);
outputStream.flush();
outputStream.close();

 

5) 응답 코드 얻기

getResponseCode() 메소드를 통해 응답 코드를 얻을 수 있습니다. 정상적인 응답일 경우 200이 반환됩니다.

public int getResponseCode() throws IOException {
        /*
         * We're got the response code already
         */
        if (responseCode != -1) {
            return responseCode;
        }

        /*
         * Ensure that we have connected to the server. Record
         * exception as we need to re-throw it if there isn't
         * a status line.
         */
        Exception exc = null;
        try {
            getInputStream();
        } catch (Exception e) {
            exc = e;
        }

        /*
         * If we can't a status-line then re-throw any exception
         * that getInputStream threw.
         */
        String statusLine = getHeaderField(0);
        if (statusLine == null) {
            if (exc != null) {
                if (exc instanceof RuntimeException)
                    throw (RuntimeException)exc;
                else
                    throw (IOException)exc;
            }
            return -1;
        }

        /*
         * Examine the status-line - should be formatted as per
         * section 6.1 of RFC 2616 :-
         *
         * Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase
         *
         * If status line can't be parsed return -1.
         */
        if (statusLine.startsWith("HTTP/1.")) {
            int codePos = statusLine.indexOf(' ');
            if (codePos > 0) {

                int phrasePos = statusLine.indexOf(' ', codePos+1);
                if (phrasePos > 0 && phrasePos < statusLine.length()) {
                    responseMessage = statusLine.substring(phrasePos+1);
                }

                // deviation from RFC 2616 - don't reject status line
                // if SP Reason-Phrase is not included.
                if (phrasePos < 0)
                    phrasePos = statusLine.length();

                try {
                    responseCode = Integer.parseInt
                            (statusLine.substring(codePos+1, phrasePos));
                    return responseCode;
                } catch (NumberFormatException e) { }
            }
        }
        return -1;
    }

 

예제)

int responseCode = connection.getResponseCode();

 

6) 응답 데이터 얻기

getInputStream() 메소드를 통해 응답 데이터를 읽을 수 있는 InputStream객체를 얻을 수 있습니다. 응답을 문자열 타입으로 얻기 위해 BufferedReader 객체를 사용할 수 있습니다.

예제)

BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuffer stringBuffer = new StringBuffer();
String inputLine;

while ((inputLine = bufferedReader.readLine()) != null)  {
     stringBuffer.append(inputLine);
}
bufferedReader.close();

String response = stringBuffer.toString();

 

Java로 HTTP GET, POST 통신하기 - 전체 코드

POST 요청 코드

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class Main {
    private static final String URL = "https://www.google.com";
    private static final String POST = "POST";
    private static final String USER_AGENT = "Mozilla/5.0";
    private static final String DATA = "test data";

    public static void main(String[] args) throws IOException {
        URL url = new URL(URL);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        connection.setRequestMethod(POST);
        connection.setRequestProperty("User-Agent", USER_AGENT);
        connection.setDoOutput(true);

        DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
        outputStream.writeBytes(DATA);
        outputStream.flush();
        outputStream.close();

        int responseCode = connection.getResponseCode();

        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        StringBuffer stringBuffer = new StringBuffer();
        String inputLine;

        while ((inputLine = bufferedReader.readLine()) != null)  {
            stringBuffer.append(inputLine);
        }
        bufferedReader.close();

        String response = stringBuffer.toString();
    }
}

GET 요청을 할 경우에는 요청 메소드를 GET으로 변경하고 OutputStream을 사용하지 않게끔 코드를 작성하면 됩니다.

GET 요청 코드

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class Main {
    private static final String URL = "https://www.google.com";
    private static final String GET = "GET";
    private static final String USER_AGENT = "Mozilla/5.0";
    private static final String DATA = "test data";

    public static void main(String[] args) throws IOException {
        URL url = new URL(URL);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        connection.setRequestMethod(GET);
        connection.setRequestProperty("User-Agent", USER_AGENT);

        int responseCode = connection.getResponseCode();

        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        StringBuffer stringBuffer = new StringBuffer();
        String inputLine;

        while ((inputLine = bufferedReader.readLine()) != null)  {
            stringBuffer.append(inputLine);
        }
        bufferedReader.close();

        String response = stringBuffer.toString();
    }
}

 

 

반응형

'Java' 카테고리의 다른 글

[Java] Object 클래스  (0) 2021.08.08
[Java] Oracle Java API 버그 제보하기  (0) 2021.08.08
[Java] JPMS와 Module  (0) 2021.08.08
[Java] JVM 구조  (0) 2020.10.25
[Java] Java 프로그램이 메모리를 사용하는 방식  (0) 2020.10.01