【JDK 11】JEP 321:HTTP Client(Standard)詳細+demo


一・背景説明
jdk 9からHTTP Clientの標準化が導入され、ユーザーのフィードバックに応じてjdk 10で更新が開始され、著しい改善があり、使用方式はほぼ変わらない.CompletableFuturesにより、非ブロック要求と応答式が提供されます.トラフィック制御はjava.util.concurrent.Flow APIでサポートできます.jdk 9とjdk 10ではほぼ完全に実装を書き換え、完全非同期を実現した.従来のhttp 1.1実装はブロックされていたが、RX Flow概念の使用は実現され、データストリームをより容易に追跡することができるようになった.ユーザーからパブリッシャを要求し、サブスクライバに応答し、常にレイヤソケットに接続する.コードの複雑さを著しく低減し、HTTP/1.1とHTTP/2の間の再利用の可能性を最大化した.
二・demo
1·Synchronous Get :
(1)Response body as a String
    public void get(String uri) throws Exception {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(uri))
                .build();
        HttpResponse response =
                client.send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println(response.body());
    }

上記の例では、HttpResponse.BodyHandlers.ofString()を使用して応答バイトを文字列に変換し、各HttpRequestにHttpResponse.BodyHandlerを提供する必要があります.responseのヘッダとステータスコードが使用可能になると、responseバイトが受信される前にBodyHandlersが呼び出され、BodyHandlerは、応答ストリームのサブスクライバであるBodySubscriberの作成を担当します.BodySubscriberは、受信したバイトをより高度なjavaタイプに変換する責任を負います.
(2)Response body as a File
public void getToFile(String uri) throws Exception {
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
          .uri(URI.create(uri))
          .build();

    HttpResponse response =
                client.send(request, HttpResponse.BodyHandlers.ofFile(Paths.get("body.txt")));

    System.out.println("Response in file:" + response.body());
}

HttpResponseは、BodyHandlerの作成に便利な静的メソッドを多く提供しています.応答型のバイトは、完全に受信されるまでメモリに蓄積され、HttpResponse.BodyHandlers.ofByteArray()やHttpResponse.BodyHandlers.ofString()など、より高度なjavaタイプに変換されます.他にも、データを完全に受信した後にHttpResponse.BodyHandlers.ofFile()、HttpResponse.BodyHandlers.ofByteArrayConsumer()、HttpResponse.BodyHandlers.ofInputStream()、JSONオブジェクトへの変換などの処理方法をカスタマイズすることができます.
2·Asynchronous Get
非同期apiは、HttpResponseが受信したときに使用できるCompletableFutureを返し、java 8でサポートを開始し、非同期プログラミングをサポートすることを理解しています.
(1)Response body as a String
    public CompletableFuture getCompletableFuture(String uri) {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(uri))
                .build();

        return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::body);
    }

CompletableFuture.thenApply(Function)メソッドは、HttpResponseをエンティティタイプ、ステータスコードなどにマッピングできます.
(2)Response body as a File
    public CompletableFuture getCompletableFuturePath(String uri) {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(uri))
                .build();

        return client.sendAsync(request, HttpResponse.BodyHandlers.ofFile(Paths.get("body.txt")))
                .thenApply(HttpResponse::body);
    }

3·Post
要求されたデータは、HttpRequest.BodyPublisherによって提供されます.
    public void post(String uri, String data) throws Exception {
        HttpClient client = HttpClient.newBuilder().build();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(uri))
                .POST(HttpRequest.BodyPublishers.ofString(data))
                .build();

        HttpResponse> response = client.send(request, HttpResponse.BodyHandlers.discarding());
        System.out.println(response.statusCode());
    }

上記の例では、BodyPublisher.fromStringを使用して文字列を要求に必要なバイトに変換します.BodyPublisherは応答ストリームのパブリッシャーであり、HttpRequest.BuilderはPOST PUTなどの方法をサポートしている.
4·Concurrent Requests
Java StreamsとCompletableFutureを組み合わせてリクエストを並列に実行し、応答結果を待つのは簡単です.次の例ではlistの各uriがリクエストを送信し、リクエストを文字列に変換します.
    public void testConcurrentRequests(){
        HttpClient client = HttpClient.newHttpClient();
        List urls = List.of("http://www.baidu.com","http://www.alibaba.com/","http://www.tencent.com");
        List requests = urls.stream()
                .map(url -> HttpRequest.newBuilder(URI.create(url)))
                .map(reqBuilder -> reqBuilder.build())
                .collect(Collectors.toList());

        List>> futures = requests.stream()
                .map(request -> client.sendAsync(request, HttpResponse.BodyHandlers.ofString()))
                .collect(Collectors.toList());
        futures.stream()
                .forEach(e -> e.whenComplete((resp,err) -> {
                    if(err != null){
                        err.printStackTrace();
                    }else{
                        System.out.println(resp.body());
                        System.out.println(resp.statusCode());
                    }
                }));
        CompletableFuture.allOf(futures
                .toArray(CompletableFuture>[]::new))
                .join();
    }

5·Get JSON
多くの場合、応答結果はより高度なフォーマット(json)であり、いくつかのサードパーティJSONツールクラスを使用して応答結果を変換することができます.
public CompletableFuture> JSONBodyAsMap(URI uri) {
    UncheckedObjectMapper objectMapper = new UncheckedObjectMapper();

    HttpRequest request = HttpRequest.newBuilder(uri)
          .header("Accept", "application/json")
          .build();

    return HttpClient.newHttpClient()
          .sendAsync(request, HttpResponse.BodyHandlers.ofString())
          .thenApply(HttpResponse::body)
          .thenApply(objectMapper::readValue);
}

class UncheckedObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper {
    /** Parses the given JSON string into a Map. */
    Map readValue(String content) {
    try {
        return this.readValue(content, new TypeReference<>(){});
    } catch (IOException ioe) {
        throw new CompletionException(ioe);
    }
}

6・Post JSON多くの場合、私たちが提出したリクエストはJSON形式で、BodyPublisher::fromString+サードパーティJSONツールを使用してリクエストデータkey/valueをJSONにマッピングします.
    public CompletableFuture postJSON(URI uri, Map map)
            throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String requestBody = objectMapper
                .writerWithDefaultPrettyPrinter()
                .writeValueAsString(map);

        HttpRequest request = HttpRequest.newBuilder(uri)
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(requestBody))
                .build();

        return HttpClient.newHttpClient()
                .sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::statusCode)
                .thenAccept(System.out::println);
    }

7・Settings a Proxy HttpClientでProxyを構成できます.
public CompletableFuture get(String uri) {
    HttpClient client = HttpClient.newBuilder()
          .proxy(ProxySelector.of(new InetSocketAddress("www-proxy.com", 8080)))
          .build();

    HttpRequest request = HttpRequest.newBuilder()
          .uri(URI.create(uri))
          .build();

    return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
          .thenApply(HttpResponse::body);
}

システムのデフォルトのipエージェントも使用できます
HttpClient.newBuilder()
      .proxy(ProxySelector.getDefault())
      .build();