JEP 321: HTTP Client (Standard)

8 min read

JEP 321: HTTP Client (Standard)

JDK 11에서 JDK 9에 도입 되고 JDK 10에서 업데이트 된 JEP110(HTTP 클라이언트 API(Incubator))를 표준화합니다 .

이 JEP는 JDK 9에서 잠복기 API로 소개되고 JDK 10에서 업데이트 된 HTTP 클라이언트 API를 표준화 할 것을 제안합니다. 잠복기 API는 상당한 개선을 가져온 여러 차례의 피드백을 받았지만 여전히 높은 수준에서 대체로 동일합니다. API는 CompletableFutures를 통해 non-blocking 요청 및 응답 의미론을 제공하며 , 종속 동작을 트리거하기 위해 연결될 수 있습니다. 요청 및 응답 본문의 역 압력 및 흐름 제어 는 API 의 플랫폼의 반응 스트림 지원을 통해 java.util.concurrent.Flow제공됩니다.

JDK 9와 JDK 10에서 배양하는 동안 구현은 거의 완전히 다시 작성되었습니다. 구현은 이제 완전히 비동기 적입니다 (이전 HTTP / 1.1 구현이 차단되었습니다). RX Flow 개념의 사용은 구현으로 밀어 넣어졌으며 HTTP / 2를 지원하는 데 필요한 여러 가지 기존 사용자 정의 개념이 제거되었습니다. 사용자 수준 요청 게시자 및 응답 구독자에서 기본 소켓까지 데이터 흐름을보다 쉽게 ​​추적 할 수 있습니다. 이것은 코드의 개념과 복잡성의 수를 크게 줄이고 HTTP / 1.1과 HTTP / 2 사이의 재사용 가능성을 극대화합니다.

표준 API의 모듈 이름과 패키지 이름이됩니다 java.net.http

jdk 10 이후의 변경사항

미리 정의의 구현 BodyPublisher, BodyHandler그리고 BodySubscriber정적 팩토리 메소드를 통해 생성은,의 복수로 명명 규칙에 따라 비 인스턴스화 유틸리티 팩토리 클래스를 분리하기 위해 밖으로 이동되었습니다. 이렇게하면 상대적으로 작은 인터페이스의 가독성이 향상됩니다.

정적 팩토리 메소드의 이름은 다음과 같은 광범위한 범주로 업데이트되었습니다.

  • fromXxx: Adapters from standard Subscriber, e.g. takes a Flow.Subscriber returns a BodySubscriber.
  • ofXxx: Factories that create a new pre-defined Body[Publisher|Handler|Subscriber] that perform useful common tasks, such as handling the response body as a String, or streaming the body to a File.
  • other: Combinators (takes a BodySubscriber returns a BodySubscriber) and other useful operations.

일반적인 시나리오에서 사용성을 향상시키기 위해 몇 가지 BodyHandler및 해당 BodySubscriber사항이 추가되었습니다.

  • discard(Object replacement)응답 바디를 무시 / 무시하고 주어진 교체를 허용 함. 피드백은 혼란 스러울 수 있다고 지적했습니다. 그것은 제거되었고 두 개의 분리 된 핸들러로 대체되었습니다 : 1) discarding(), 2) replacing(Object replacement).
  • 추가 ofLines()반환 BodyHandler<Stream>하는로 응답 본문의 스트리밍을 지원하는 Stream라인, 라인별로 라인. 그것과 비슷한 의미를 제공합니다 BufferedReader.lines().
  • 추가 fromLineSubscriber​된 Flow.Subscriber것은 String라인 에 대한 응답 본문의 적용을 지원합니다 .
  • BodySubscriber.mapping하나의 응답 본문 유형에서 다른 응답 본문 유형으로 범용 매핑을 추가 했습니다.

푸시 약속 지원은 API에 대한 영향을 줄이고 정기적 인 요청 / 응답에 더 많이 반영되도록 다시 작업되었습니다. 특히 MultiSubscriber와 MultiResultMap는 제거되었습니다. 푸시 약속은 ​​이제 PushPromiseHandler보내기 작업 중에 선택적으로 제공되는 기능 인터페이스를 통해 처리 됩니다.

HttpClient.Redirect정책은 대체함으로써, 단순화되었습니다 SAME_PROTOCOL와 SECURE함께, 정책 NORMAL. 이전에 명명 된 이름 SECURE은 실제로 적절하게 명명되지 않았으며 이름 이 변경되어야한다는 사실이 관찰되었습니다. 이는 NORMAL대부분의 일반적인 경우에 적합 할 가능성이 높기 때문입니다. 을 감안할 때 새로 전술, 이름, NORMAL, SAME_PROTOCOL이상한 이름이 나타납니다, 아마도 혼란 및 사용 가능성이 없습니다.

WebSocket.MessagePart제거 되었어. 이 열거 형은 수신 측에서 메시지 전달이 완료되었는지 여부를 나타 내기 위해 사용되었습니다. 이 목적을 위해 간단한 부울을 사용하는 송신 측과 비대칭입니다. 또한 수신 된 메시지를 단순한 부울 값으로 처리하면 수신 코드 논리가 크게 줄어들고 단순화된다는 것이 관찰되었습니다. WHOLE앞서 말한 이점과 주요 목적 중 하나로 전달되는 메시지의 결정은 MessagePart자체 무게를 지니지 않는 것으로 판명되었습니다.


import java.io.IOException;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class HttpClientExamples {

    public static void main(String[] args) throws IOException, InterruptedException {
        syncRequest();
        asyncRequest();
        postData();
        basicAuth();
    }

    private static void syncRequest() throws IOException, InterruptedException {
        var request = HttpRequest.newBuilder()
                .uri(URI.create("https://winterbe.com"))
                .build();
        var client = HttpClient.newHttpClient();
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());
    }

    private static void asyncRequest() {
        var request = HttpRequest.newBuilder()
                .uri(URI.create("https://winterbe.com"))
                .build();
        var client = HttpClient.newHttpClient();
        client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::body)
                .thenAccept(System.out::println);
    }

    private static void postData() throws IOException, InterruptedException {
        var request = HttpRequest.newBuilder()
                .uri(URI.create("https://postman-echo.com/post"))
                .timeout(Duration.ofSeconds(30))
                .version(HttpClient.Version.HTTP_2)
                .header("Content-Type", "text/plain")
                .POST(HttpRequest.BodyPublishers.ofString("Hi there!"))
                .build();
        var client = HttpClient.newHttpClient();
        var response = client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.statusCode());      // 200
    }

    private static void basicAuth() throws IOException, InterruptedException {
        var client = HttpClient.newHttpClient();

        var request1 = HttpRequest.newBuilder()
                .uri(URI.create("https://postman-echo.com/basic-auth"))
                .build();
        var response1 = client.send(request1, HttpResponse.BodyHandlers.ofString());
        System.out.println(response1.statusCode());      // 401

        var authClient = HttpClient
                .newBuilder()
                .authenticator(new Authenticator() {
                    @Override
                    protected PasswordAuthentication getPasswordAuthentication() {
                        return new PasswordAuthentication("postman", "password".toCharArray());
                    }
                })
                .build();
        var request2 = HttpRequest.newBuilder()
                .uri(URI.create("https://postman-echo.com/basic-auth"))
                .build();
        var response2 = authClient.send(request2, HttpResponse.BodyHandlers.ofString());
        System.out.println(response2.statusCode());      // 200
    }

}

참조