본문 바로가기
블로그 이미지

방문해 주셔서 감사합니다! 항상 행복하세요!

  
   - 문의사항은 메일 또는 댓글로 언제든 연락주세요.
   - "해줘","답 내놔" 같은 질문은 답변드리지 않습니다.
   - 메일주소 : lts06069@naver.com


Java(자바)/Java 기본

Java 11 HttpClient (자바11 HttpClient) 기능 살펴 보기

야근없는 행복한 삶을 위해 ~
by 마샤와 곰 2020. 12. 28.

자바가 11버전으로 업그레이드 되면서 java.net 패키지에 http관련된 라이브러리가 추가 되었습니다.

Java11 이전에는 대부분 아파치에서 제공하는 라이브러리를 사용 했었습니다.

 * Apache Http Client(org.apache.httpcomponents)

 

먼저 살펴 볼 방법은 get 방식으로 요청하는 방법 입니다.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;

public class HttpClientJava11 {

    public static void get(String address, String[] headers) throws Exception {
        //헤더값은 이런식으로 사용합니다(키, 값)headers => "Content-type","plain/text"  
        HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).build();
        String result = client.sendAsync(
            HttpRequest.newBuilder(
                new URI(address)).GET().headers(headers).build(),  //GET방식 요청
                HttpResponse.BodyHandlers.ofString()  //응답은 문자형태
            ).thenApply(HttpResponse::body)  //thenApply메소드로 응답body값만 받기
            .get();  //get메소드로 body값의 문자를 확인
        System.out.println(result);
    }
}    

 

HttpClient 클래스에 존재하는 newBuilder라는 static 메소드를 호출하여 클라언트 객체를 만들어 줍니다.

만들어진 클라이언트 객체에서 sendAsync라는 메소드를 사용하면 이름처럼 "비동기화" 된 요청을 할 수 있습니다.

동기적 요청을 하려면 send 메소드를 호출하여 주면 됩니다.

 

sendAsync 메소드에서 필요한 파라미터는 2개 입니다.

 1) HttpRequest request : 요청하는 형태, 데이터를 정의 합니다.

 2) BodyHandler<T> responseBodyHandler : 응답 받을 형태를 관리 합니다.

 

위 예제는 URI 클래스에서 get방식으로 요청을 하는 모습이며, 응답받는 데이터는 문자형태( BodyHandlers.ofString() )로 받도록 정의 된 모습입니다.

sendAsync메소드는 CompletableFuture라는 클래스를 제공합니다.

해당 클래스는 시간이 걸릴 수 있는 작업을 Future 내부로 작성하고 호출자 스레드가 결과를 기다리는 동안 다른 유용한 작업을 할 수 있는 기능을 제공합니다.   * 출처 : 모던 자바인 액션

 

thenApply를 통해서 Responsebody 값만 가져오게 한 뒤에 마지막에 get 메소드를 호출하면 응답결과 메시지만 받을 수 있습니다.

몇번 사용하다보면 익숙해 질 수 있습니다.

 

다음으로 살펴 볼 방법은 post 방법 입니다.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;

public class HttpClientJava11 {

    public static void post(String address, Map<?, ?> params, String[] headers) throws Exception {

        ObjectMapper objectMapper = new ObjectMapper();
        String requestBody = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(params);

        BodyPublisher body = BodyPublishers.ofString(requestBody);  //post 파라미터 최종 모습 입니다.

        HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).build();

        URI url = new URI(address);
        String result = client.sendAsync(HttpRequest.newBuilder(url).POST(body).headers(headers).build(),
            HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body).get();
        System.out.println(result);
    }
}    

 

post 메소드는 전송 할 데이터가 일반적으로 key, value 형태로 되어 있기 때문에 map 데이터를 변환해 주는 ObjectMapper 클래스를 사용하였습니다.

메소드를 사용하는 방법은 get 메소드와 매우 비슷합니다.

또한 body로 구현된 BodyPublisher 클래스에서 subscribe를 통해서 해당 행위에 대한 구독을 할 수 있습니다.

requestBody의 발행 조건에 대해 구독자를 추가 할 수 있습니다.

* 사실 사용법에 대해서는 연습(?) 중 입니다..ㅠ_ㅠ

 

또 다른 요청 형태인 put이나 delete방식 사용법은 거의 동일합니다.

put은 post처럼 요청하면 되며, delete는 get처럼 요청하면 됩니다.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;

public class HttpClientJava11 {

    //get 요청 방법
    public static void get(String address, String[] headers) throws Exception {
        //헤더값은 이런식으로 사용합니다(키, 값)headers => "Content-type","plain/text"  
        HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).build();
        String result = client.sendAsync(
            HttpRequest.newBuilder(
                new URI(address)).GET().headers(headers).build(),  //GET방식 요청
                HttpResponse.BodyHandlers.ofString()  //응답은 문자형태
            ).thenApply(HttpResponse::body)  //thenApply메소드로 응답body값만 받기
            .get();  //get메소드로 body값의 문자를 확인
        System.out.println(result);
    }

    //post 요청 방법
    public static void post(String address, Map<?, ?> params, String[] headers) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();
        String requestBody = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(params);
        BodyPublisher body = BodyPublishers.ofString(requestBody);  //post 파라미터 최종 모습 입니다.
        HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).build();
        URI url = new URI(address);
        String result = client.sendAsync(HttpRequest.newBuilder(url).POST(body).headers(headers).build(),
            HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body).get();
        System.out.println(result);
    }
    
    //delete 요청 방법
    public static void delete(String address, String[] headers) throws Exception {
        HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).build();
        String result = client.sendAsync(
            HttpRequest.newBuilder(
                new URI(address)).DELETE().headers(headers).build(),  //DELETE방식 요청
                HttpResponse.BodyHandlers.ofString()
            ).thenApply(HttpResponse::body)
            .get();
        System.out.println(result);
    }
    
    //put 요청 방법
    public static void put(String address, Map<?, ?> params, String[] headers) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();
        String requestBody = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(params);
        BodyPublisher body = BodyPublishers.ofString(requestBody);  
        HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).build();
        URI url = new URI(address);
        String result = client.sendAsync(HttpRequest.newBuilder(url).PUT(body).headers(headers).build(),
            HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body).get();
        System.out.println(result);
    }    
}    

 

마지막으로 살펴볼 기능은 파일 전송을 위한 방법 입니다.

파일 전송은 조금 어려운 점이 데이터를 byte단위로 쪼개어 전송을 해야 된다는 점 입니다.

BodyPublishers에서 제공하는 ofFile을 이용해서 파일을 전송해보려 했는데 문제가 발생했습니다.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;

public class HttpClientJava11 {


    //multipart 요청 방법
    public static void multipart(String address, String[] headers) throws Exception {
        BodyPublisher fileBody = BodyPublishers.ofFile(Paths.get("파일경로/test.csv"));
        HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).build();
        URI url = new URI(address);
        String result = client.sendAsync(HttpRequest.newBuilder(url).POST(fileBody).headers(headers).build(),
            HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body).get();
        System.out.println(result);
    }
        
}    

 

이렇게 작성하다 보니까...파일은 보내질 것 같은데 파라미터는 어떻게 보내는지 감이오지 않았습니다.

단순하게 파일전송만 하는 경우는 많지 않으니 사실 사용할 수 없는 방법이였습니다.

그래서 다음으로 검색하여 적용한 부분이 데이터를 바이트 단위로 변환하는 방법이였습니다.

BodyPublisher에 들어가는 데이터를 바이트단위의 배열로 채우는 방법 입니다.

    public static void multipart(String address, Map<?, ?> params) throws Exception {
        HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).build();
        HttpRequest request = HttpRequest.newBuilder().uri(new URI(address))
            .headers("Content-Type", "multipart/form-data; boundary=" + boundary)
            .POST(oMultipartData(params, boundary))
            .build();  //BodyPublisher를 바이트단위의 형태로 넣습니다.

        String result = client
            .sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::body).get();
        System.out.println(result);
    }
	
	
    private static BodyPublisher oMultipartData(Map<?, ?> data, String boundary) throws IOException {
        var byteArrays = new ArrayList<byte[]>();
        byte[] separator = ("--" + boundary + "\r\nContent-Disposition: form-data; name=")
            .getBytes(StandardCharsets.UTF_8);
        for (Map.Entry<?, ?> entry : data.entrySet()) {
            byteArrays.add(separator);
            if (entry.getValue() instanceof Path) {  //파일이면
                var path = (Path) entry.getValue();
                String mimeType = Files.probeContentType(path);
                byteArrays.add(("\"" + entry.getKey() + "\"; filename=\"" + path.getFileName() + "\"\r\nContent-Type: "
                    + mimeType + "\r\n\r\n").getBytes(StandardCharsets.UTF_8));
                byteArrays.add(Files.readAllBytes(path));
                byteArrays.add("\r\n".getBytes(StandardCharsets.UTF_8));
            } else {  //단순 데이터면
                byteArrays.add(("\"" + entry.getKey() + "\"\r\n\r\n" + entry.getValue() + "\r\n")
                    .getBytes(StandardCharsets.UTF_8));
            }
        }
        byteArrays.add(("--" + boundary + "--").getBytes(StandardCharsets.UTF_8));
        return BodyPublishers.ofByteArrays(byteArrays);
    }

 

multipart메소드에서 Map객체를 받도록 하였습니다.

Map객체 샘플과 요청 방법 모습은 아래와 같습니다.

{
    HashMap<Object, Object> param = new HashMap<>();
    param.put("req", 1234);
    param.put("req1", "fefe");
    param.put("req2", new String[] {"abcd","1234"});
    param.put("file_", Paths.get("E:/tester.jpg"));  //파일 데이터1
    param.put("file_", Paths.get("E:/re_filename.txt")); //파일 데이터2
    
    multipart("http://127.0.0.1:8181/testValid2", param);  // 전송
}

 

위 내용을 전부 정리하여 본 클래스 입니다.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;

public class HttpClientJava11 {

    private static final String boundary = "-------------oiawn4tp89n4e9p5";
    
    public void tester() throws Exception {
        //get방식
        get("http://주소?param=1234", new String[] {"Content-type","applacation/json"});
        
        HashMap<Object, Object> param = new HashMap<>();
        param.put("req1", 1234);
        param.put("req2", "abcd");
        
        //post방식
        post("http://주소", param , new String[] {"Content-type","applacation/json"});
    
        //get방식
        delete("http://주소?param=1234", new String[] {"Content-type","applacation/json"});
        
        //put방식
        put("http://주소", param , new String[] {"Content-type","applacation/json"});  

        //파일정보 추가
        param.put("file1", Paths.get("파일경로/파일명"));
        param.put("file2", Paths.get("파일경로/파일명"));

        //파일전송
        multipart("http://주소", param)
    }

    //get 요청 방법
    public static void get(String address, String[] headers) throws Exception {
        //헤더값은 이런식으로 사용합니다(키, 값)headers => "Content-type","plain/text"  
        HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).build();
        String result = client.sendAsync(
            HttpRequest.newBuilder(
                new URI(address)).GET().headers(headers).build(),  //GET방식 요청
                HttpResponse.BodyHandlers.ofString()  //응답은 문자형태
            ).thenApply(HttpResponse::body)  //thenApply메소드로 응답body값만 받기
            .get();  //get메소드로 body값의 문자를 확인
        System.out.println(result);
    }

    //post 요청 방법
    public static void post(String address, Map<?, ?> params, String[] headers) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();
        String requestBody = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(params);
        BodyPublisher body = BodyPublishers.ofString(requestBody);  //post 파라미터 최종 모습 입니다.
        HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).build();
        URI url = new URI(address);
        String result = client.sendAsync(HttpRequest.newBuilder(url).POST(body).headers(headers).build(),
            HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body).get();
        System.out.println(result);
    }
    
    //delete 요청 방법
    public static void delete(String address, String[] headers) throws Exception {
        HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).build();
        String result = client.sendAsync(
            HttpRequest.newBuilder(
                new URI(address)).DELETE().headers(headers).build(),  //DELETE방식 요청
                HttpResponse.BodyHandlers.ofString()
            ).thenApply(HttpResponse::body)
            .get();
        System.out.println(result);
    }
    
    //put 요청 방법
    public static void put(String address, Map<?, ?> params, String[] headers) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();
        String requestBody = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(params);
        BodyPublisher body = BodyPublishers.ofString(requestBody);  
        HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).build();
        URI url = new URI(address);
        String result = client.sendAsync(HttpRequest.newBuilder(url).PUT(body).headers(headers).build(),
            HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body).get();
        System.out.println(result);
    }    
    
    //파일 전송 방법
    public static void multipart(String address, Map<?, ?> params) throws Exception {
        HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).build();
        HttpRequest request = HttpRequest.newBuilder().uri(new URI(address))
            .headers("Content-Type", "multipart/form-data; boundary=" + boundary)
            .POST(oMultipartData(params, boundary))
            .build();  //BodyPublisher를 바이트단위의 형태로 넣습니다.

        String result = client
            .sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::body).get();
        System.out.println(result);
    }
	
    //multipart메소드에서 사용하는 메소드
    private static BodyPublisher oMultipartData(Map<?, ?> data, String boundary) throws IOException {
        var byteArrays = new ArrayList<byte[]>();
        byte[] separator = ("--" + boundary + "\r\nContent-Disposition: form-data; name=")
            .getBytes(StandardCharsets.UTF_8);
        for (Map.Entry<?, ?> entry : data.entrySet()) {
            byteArrays.add(separator);
            if (entry.getValue() instanceof Path) {  //파일이면
                var path = (Path) entry.getValue();
                String mimeType = Files.probeContentType(path);
                byteArrays.add(("\"" + entry.getKey() + "\"; filename=\"" + path.getFileName() + "\"\r\nContent-Type: "
                    + mimeType + "\r\n\r\n").getBytes(StandardCharsets.UTF_8));
                byteArrays.add(Files.readAllBytes(path));
                byteArrays.add("\r\n".getBytes(StandardCharsets.UTF_8));
            } else {  //단순 데이터면
                byteArrays.add(("\"" + entry.getKey() + "\"\r\n\r\n" + entry.getValue() + "\r\n")
                    .getBytes(StandardCharsets.UTF_8));
            }
        }
        byteArrays.add(("--" + boundary + "--").getBytes(StandardCharsets.UTF_8));
        return BodyPublishers.ofByteArrays(byteArrays);
    }    
}    

 

이상으로 자바11에서 HttpClient를 활용하여 데이터를 주고받는 방법에 대해서 살펴 보았습니다.

아직 자세히 살펴본 내용이 아니라 다소 빈약한 부분이 존재합니다.  * 그래도 테스트는 전부 성공 했습니다!!

좋은 피드백과 의견 있으면 언제든 알려주세요! :)

 

반응형
* 위 에니메이션은 Html의 캔버스(canvas)기반으로 동작하는 기능 입니다. Html 캔버스 튜토리얼 도 한번 살펴보세요~ :)
* 직접 만든 Html 캔버스 애니메이션 도 한번 살펴보세요~ :)

댓글