Java 9で新しいHTTPクライアントを探る

Java 9および11での新しいHTTPクライアントの探索

1. 前書き

このチュートリアルでは、Java 9の新しいインキュベーションhttps://docs.oracle.com/javase/9/docs/api/jdk/incubator/http/HttpClient.html.について説明します。

ごく最近まで、JavaはHttpURLConnection APIのみを提供していました。これは低レベルであり、機能豊富な and がユーザーフレンドリーであることで知られていません。

そのため、Apache HttpClientJetty、SpringのRestTemplateなど、広く使用されているサードパーティライブラリが一般的に使用されていました。

2. 初期設定

JDK 9のThe HTTP Client module is bundled as an incubator moduleは、下位互換性を備えたHTTP/2をサポートし、HTTP /1.1を容易にします。

これを使用するには、module-info.javaファイルを使用してモジュールを定義する必要があります。このファイルは、アプリケーションを実行するために必要なモジュールも示しています。

module com.example.java9.httpclient {
  requires jdk.incubator.httpclient;
}

3. HTTPクライアントAPIの概要

HttpURLConnection,とは異なり、HTTPクライアントは同期および非同期の要求メカニズムを提供します。

APIは3つのコアクラスで構成されます。

  • HttpRequestは、HttpClientを介して送信される要求を表します

  • HttpClientは、複数の要求に共通の構成情報のコンテナーとして動作します

  • HttpResponseは、HttpRequest呼び出しの結果を表します

次のセクションで、それぞれについて詳しく説明します。 まず、リクエストに焦点を当てましょう。

4. HttpRequest

suggests,という名前のHttpRequest,は、送信する要求を表すオブジェクトです。 HttpRequest.Builder.を使用して新しいインスタンスを作成できます

HttpRequest.newBuilder()を呼び出すことで取得できます。 Builderクラスは、リクエストを構成するために使用できる一連のメソッドを提供します。

最も重要なものについて説明します。

4.1. URIの設定

リクエストを作成するときに最初にしなければならないことは、URLを提供することです。

これは、2つの方法で実行できます。URIパラメーターを指定してBuilderのコンストラクターを使用するか、Builderインスタンスでメソッドuri(URI)を呼び出します。

HttpRequest.newBuilder(new URI("https://postman-echo.com/get"))

HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))

基本的なリクエストを作成するために最後に設定する必要があるのは、HTTPメソッドです。

4.2. HTTPメソッドの指定

Builderからメソッドの1つを呼び出すことにより、リクエストが使用するHTTPメソッドを定義できます。

  • 取得する()

  • POST(BodyProcessor本体)

  • PUT(BodyProcessor本体)

  • DELETE(BodyProcessor本体)

BodyProcessorについては後で詳しく説明します。 それでは、a very simple GET request exampleを作成しましょう。

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .GET()
  .build();

このリクエストには、HttpClientに必要なすべてのパラメータが含まれています。 ただし、リクエストにパラメーターを追加する必要がある場合があります。重要なものは次のとおりです。

  • HTTPプロトコルのバージョン

  • ヘッダー

  • タイムアウト

4.3. HTTPプロトコルバージョンの設定

APIはHTTP / 2プロトコルを完全に活用し、デフォルトで使用しますが、使用するプロトコルのバージョンを定義できます。

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .version(HttpClient.Version.HTTP_2)
  .GET()
  .build();

ここで重要なのは、HTTP / 2がサポートされていない場合、クライアントはHTTP /1.1などにフォールバックすることです。

4.4. ヘッダーの設定

リクエストにヘッダーを追加する場合は、提供されているビルダーメソッドを使用できます。

これは、次の2つの方法のいずれかで実行できます。

  • すべてのヘッダーをキーと値のペアとしてheaders()メソッドに渡すか、

  • 単一のKey-Valueヘッダーにheader()メソッドを使用する:

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .headers("key1", "value1", "key2", "value2")
  .GET()
  .build();

HttpRequest request2 = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .header("key1", "value1")
  .header("key2", "value2")
  .GET()
  .build();

リクエストをカスタマイズするために使用できる最後の便利な方法はa timeout()です。

4.5. タイムアウトの設定

それでは、応答を待つ時間を定義しましょう。

設定された時間が経過すると、HttpTimeoutExceptionがスローされます。デフォルトのタイムアウトは無限大に設定されています。

タイムアウトは、Durationオブジェクトで設定できます–ビルダーインスタンスでメソッドtimeout()を呼び出すことによって:

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .timeout(Duration.of(10, SECONDS))
  .GET()
  .build();

5. リクエスト本文の設定

リクエストビルダーメソッドPOST(BodyProcessor body)PUT(BodyProcessor body)DELETE(BodyProcessor body).を使用して、リクエストに本文を追加できます。

新しいAPIは、リクエスト本文の受け渡しを簡素化する、すぐに使用できる多数のBodyProcessor実装を提供します。

  • StringProcessorHttpRequest.BodyProcessor.fromStringで作成されたStringから本体を読み取ります)

  • InputStreamProcessorHttpRequest.BodyProcessor.fromInputStreamで作成されたInputStreamから本体を読み取ります)

  • ByteArrayProcessorHttpRequest.BodyProcessor.fromByteArrayで作成されたバイト配列から本体を読み取ります)

  • FileProcessorHttpRequest.BodyProcessor.fromFileで作成された、指定されたパスのファイルから本文を読み取ります)

本体が必要ない場合は、HttpRequest.noBody()を渡すだけです。

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/post"))
  .POST(HttpRequest.noBody())
  .build();

5.1. StringBodyProcessor

BodyProcessorの実装でリクエスト本文を設定するのは、非常に簡単で直感的です。

たとえば、単純なStringを本体として渡したい場合は、StringBodyProcessorを使用できます。

すでに述べたように、このオブジェクトはファクトリメソッドfromString()で作成できます。引数としてStringオブジェクトのみを取り、それから本体を作成します。

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/post"))
  .headers("Content-Type", "text/plain;charset=UTF-8")
  .POST(HttpRequest.BodyProcessor.fromString("Sample request body"))
  .build();

5.2. InputStreamBodyProcessor

これを行うには、InputStreamSupplierとして渡す必要があるため(作成を遅延させるため)、上記のStringBodyProcessorとは少し異なります。

ただし、これも非常に簡単です。

byte[] sampleData = "Sample request body".getBytes();
HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/post"))
  .headers("Content-Type", "text/plain;charset=UTF-8")
  .POST(HttpRequest.BodyProcessor
   .fromInputStream(() -> new ByteArrayInputStream(sampleData)))
  .build();

ここで単純なByteArrayInputStreamをどのように使用したかに注目してください。もちろん、これは任意のInputStream実装にすることができます。

5.3. ByteArrayProcessor

ByteArrayProcessorを使用して、パラメーターとしてバイトの配列を渡すこともできます。

byte[] sampleData = "Sample request body".getBytes();
HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/post"))
  .headers("Content-Type", "text/plain;charset=UTF-8")
  .POST(HttpRequest.BodyProcessor.fromByteArray(sampleData))
  .build();

5.4. FileProcessor

ファイルを操作するために、提供されたFileProcessorを利用できます。そのファクトリメソッドは、ファイルへのパスをパラメータとして受け取り、コンテンツから本文を作成します。

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/post"))
  .headers("Content-Type", "text/plain;charset=UTF-8")
  .POST(HttpRequest.BodyProcessor.fromFile(
    Paths.get("src/test/resources/sample.txt")))
  .build();

HttpRequestを作成する方法と、それに追加のパラメーターを設定する方法について説明しました。

次に、リクエストの送信とレスポンスの受信を担当するHttpClientクラスについて詳しく見ていきます。

6. HttpClient

すべての要求はHttpClientを使用して送信されます。これは、HttpClient.newBuilder()メソッドを使用するか、HttpClient.newHttpClient()を呼び出すことでインスタンス化できます。

要求/応答を処理するために使用できる、多くの便利で自己記述的な方法を提供します。

ここでこれらのいくつかを取り上げましょう。

6.1. プロキシの設定

接続のプロキシを定義できます。 Builderインスタンスでproxy()メソッドを呼び出すだけです。

HttpResponse response = HttpClient
  .newBuilder()
  .proxy(ProxySelector.getDefault())
  .build()
  .send(request, HttpResponse.BodyHandler.asString());

この例では、デフォルトのシステムプロキシを使用しました。

6.2. リダイレクトポリシーの設定

アクセスしたいページが別のアドレスに移動する場合があります。

その場合、通常は新しいURIに関する情報を含むHTTPステータスコード3xxを受け取ります。 HttpClient can redirect the request to the new URI automatically if we set the appropriate redirect policy.

BuilderfollowRedirects()メソッドでそれを行うことができます:

HttpResponse response = HttpClient.newBuilder()
  .followRedirects(HttpClient.Redirect.ALWAYS)
  .build()
  .send(request, HttpResponse.BodyHandler.asString());

すべてのポリシーは、列挙型HttpClient.Redirectで定義および記述されています。

6.3. 接続のAuthenticatorの設定

Authenticatorは、接続の資格情報(HTTP認証)をネゴシエートするオブジェクトです。

さまざまな認証スキーム(基本認証またはダイジェスト認証など)を提供します。 ほとんどの場合、認証にはサーバーに接続するためのユーザー名とパスワードが必要です。

これらの値の単なるホルダーであるPasswordAuthenticationクラスを使用できます。

HttpResponse response = HttpClient.newBuilder()
  .authenticator(new Authenticator() {
    @Override
    protected PasswordAuthentication getPasswordAuthentication() {
      return new PasswordAuthentication(
        "username",
        "password".toCharArray());
    }
}).build()
  .send(request, HttpResponse.BodyHandler.asString());

上記の例では、ユーザー名とパスワードの値をプレーンテキストとして渡しました。もちろん、実稼働シナリオでは、これは異なる必要があります。

すべてのリクエストで同じユーザー名とパスワードを使用する必要はありません。 Authenticatorクラスは、提供する値を見つけるために使用できるいくつかのgetXXX(たとえば、getRequestingSite())メソッドを提供します。

次に、新しいHttpClientの最も便利な機能の1つである、サーバーへの非同期呼び出しについて説明します。

6.4. リクエストの送信-同期vs 非同期

新しいHttpClientは、サーバーにリクエストを送信するための2つの可能性を提供します。

  • send(…) – synchronously(応答が来るまでブロックします)

  • sendAsync(…) – asynchronously(応答を待たず、ブロックしない)

これまで、send(. ..)メソッドは自然に応答を待ちます。

HttpResponse response = HttpClient.newBuilder()
  .build()
  .send(request, HttpResponse.BodyHandler.asString());

この呼び出しはHttpResponseオブジェクトを返します。アプリケーションフローからの次の命令は、応答がすでにここにある場合にのみ実行されると確信しています。

ただし、特に大量のデータを処理する場合は、多くの欠点があります。

したがって、sendAsync(. ..)メソッドを使用できます–これはCompletableFeature<HttpResponse>to process a request asynchronouslyを返します:

CompletableFuture> response = HttpClient.newBuilder()
  .build()
  .sendAsync(request, HttpResponse.BodyHandler.asString());

また、新しいAPIは複数の応答を処理し、要求と応答の本文をストリーミングできます。

List targets = Arrays.asList(
  new URI("https://postman-echo.com/get?foo1=bar1"),
  new URI("https://postman-echo.com/get?foo2=bar2"));
HttpClient client = HttpClient.newHttpClient();
List> futures = targets.stream()
  .map(target -> client
    .sendAsync(
      HttpRequest.newBuilder(target).GET().build(),
      HttpResponse.BodyHandler.asString())
    .thenApply(response -> response.body()))
  .collect(Collectors.toList());

6.5. 非同期呼び出しのExecutorの設定

非同期呼び出しで使用されるスレッドを提供するExecutorを定義することもできます。

このようにして、たとえば、リクエストの処理に使用されるスレッドの数を制限できます。

ExecutorService executorService = Executors.newFixedThreadPool(2);

CompletableFuture> response1 = HttpClient.newBuilder()
  .executor(executorService)
  .build()
  .sendAsync(request, HttpResponse.BodyHandler.asString());

CompletableFuture> response2 = HttpClient.newBuilder()
  .executor(executorService)
  .build()
  .sendAsync(request, HttpResponse.BodyHandler.asString());

デフォルトでは、HttpClientはエグゼキュータjava.util.concurrent.Executors.newCachedThreadPool()を使用します。

6.6. CookieManagerの定義

新しいAPIとビルダーを使用すると、接続にCookieManagerを設定するのは簡単です。 ビルダーメソッドcookieManager(CookieManager cookieManager)を使用して、クライアント固有のCookieManagerを定義できます。

たとえば、Cookieをまったく受け入れないCookieManagerを定義しましょう。

HttpClient.newBuilder()
  .cookieManager(new CookieManager(null, CookiePolicy.ACCEPT_NONE))
  .build();

CookieManagerでCookieの保存が許可されている場合は、HttpClientからCookieManagerを確認することでCookieにアクセスできます。

httpClient.cookieManager().get().getCookieStore()

それでは、Http APIの最後のクラスであるHttpResponseに注目しましょう。

7. HttpResponseオブジェクト

HttpResponseクラスは、サーバーからの応答を表します。 多くの便利なメソッドを提供しますが、最も重要なのは次の2つです。

  • statusCode() –応答のステータスコード(タイプint)を返します(HttpURLConnectionクラスには可能な値が含まれています)

  • body() –応答の本文を返します(戻り値の型は、send()メソッドに渡された応答BodyHandlerパラメーターによって異なります)

応答オブジェクトには、uri()headers()trailers()version()などの他の便利なメソッドがあります。

7.1. 応答オブジェクトのURI

応答オブジェクトのメソッドuri()は、応答を受信したURIを返します。

リダイレクトが発生する可能性があるため、リクエストオブジェクトのURIとは異なる場合があります。

assertThat(request.uri()
  .toString(), equalTo("http://stackoverflow.com"));
assertThat(response.uri()
  .toString(), equalTo("https://stackoverflow.com/"));

7.2. 応答からのヘッダー

応答オブジェクトでメソッドheaders()を呼び出すことにより、応答からヘッダーを取得できます。

HttpResponse response = HttpClient.newHttpClient()
  .send(request, HttpResponse.BodyHandler.asString());
HttpHeaders responseHeaders = response.headers();

戻り値の型としてHttpHeadersオブジェクトを返します。 これは、jdk.incubator.httpパッケージで定義された新しいタイプであり、HTTPヘッダーの読み取り専用ビューを表します。

ヘッダー値の検索を簡素化する便利なメソッドがいくつかあります。

7.3. 応答からトレーラーを取得する

HTTP応答には、応答コンテンツの後に含まれる追加のヘッダーが含まれる場合があります。 これらのヘッダーは、トレーラーヘッダーと呼ばれます。

HttpResponse:でメソッドtrailers()を呼び出すことで取得できます

HttpResponse response = HttpClient.newHttpClient()
  .send(request, HttpResponse.BodyHandler.asString());
CompletableFuture trailers = response.trailers();

trailers()メソッドはCompletableFutureオブジェクトを返すことに注意してください。

7.4. 応答のバージョン

メソッドversion()は、サーバーとの通信に使用されたHTTPプロトコルのバージョンを定義します。

HTTP / 2を使用することを定義した場合でも、サーバーはHTTP /1.1を介して応答できることに注意してください。

サーバーが応答したバージョンは、応答で指定されます。

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .version(HttpClient.Version.HTTP_2)
  .GET()
  .build();
HttpResponse response = HttpClient.newHttpClient()
  .send(request, HttpResponse.BodyHandler.asString());
assertThat(response.version(), equalTo(HttpClient.Version.HTTP_1_1));

8. Java 11Httpクライアント

Java 11の主な変更点は、HTTP client API that implements HTTP/2 and Web Socket.の標準化でした。これは、Javaのごく初期の頃からJDKに存在していた従来のHttpUrlConnectionクラスを置き換えることを目的としています。

変更はJEP 321の一部として実装されました。

8.1. JEP 321の一部としての主要な変更

  1. Java 9のインキュベーションされたHTTP APIは、Java SE APIに公式に組み込まれました。 新しいHTTP APIsは、java.net.HTTP. *にあります。

  2. HTTPプロトコルの新しいバージョンは、クライアントによる要求の送信とサーバーからの応答の受信の全体的なパフォーマンスを改善するように設計されています。 これは、ストリームの多重化、ヘッダー圧縮、プッシュプロミスなどの「多数の変更」を導入することで実現されます。

  3. Java 11以降、the API is now fully asynchronous (the previous HTTP/1.1 implementation was blocking).の非同期呼び出しは、CompletableFutureを使用して実装されます。CompletableFutureの実装は、前のステージが終了すると各ステージの適用を処理するため、このフロー全体が非同期になります。

  4. 新しいHTTPクライアントAPIは、サードパーティの依存関係を追加することなく、HTTP / 2などの最新のWeb機能をサポートするHTTPネットワーク操作を実行する標準的な方法を提供します。

  5. 新しいAPIは、HTTP 1.1 / 2 WebSocketのネイティブサポートを提供します。 コア機能を提供するコアクラスとインターフェイスは次のとおりです。

    • HttpClient class, java.net.http.HttpClient

    • HttpRequestクラス、java.net.http.HttpRequest

    • HttpResponse インターフェース、 java.net.http.HttpResponse

    • WebSocketインターフェース、java.net.http.WebSocket

8.2. Java 11以前のHTTPクライアントに関する問題

既存のHttpURLConnection APIとその実装には、多くの問題がありました。

  • URLConnectionAPIは、現在機能しなくなった複数のプロトコル(FTP、gopherなど)で設計されました。

  • APIはHTTP / 1.1より前のものであり、抽象的すぎます。

  • ブロックモードでのみ動作します(つまり、要求/応答ごとに1つのスレッド)。

  • 保守が非常に困難です。

9. Java 11でのHttpクライアントの変更

9.1. 静的ファクトリクラスの紹介

BodyPublisherBodySubscriber、およびBodyHandler.の既存の実装を含む、新しい静的ファクトリクラスBodyPublishersBodySubscribers、およびBodyHandlers が導入されました

これらは、応答本文を文字列として処理したり、本文をファイルにストリーミングしたりするなど、便利な一般的なタスクを実行するために使用されます。

例えば Java 11以前では、次のようなことをしなければなりませんでした。

HttpResponse response = client.send(request, HttpResponse.BodyHandler.asString());

これを次のように単純化できます。

HttpResponse response = client.send(request, BodyHandlers.ofString());

また、静的メソッドの名前は、より明確にするために標準化されています。

例えば アダプターとして使用する場合はfromXxxのようなメソッド名を使用し、事前定義されたハンドラー/サブスクライバーを作成する場合はofXxxのような名前を使用します。

9.2. 一般的な体型の流Methodsな方法

作成されたパブリッシャーおよび一般的なボディタイプを処理するハンドラーの便利なファクトリメソッドが導入されました。

例えば バイト、ファイル、文字列からパブリッシャーを作成するための流methodsなメソッドを以下に示します。

BodyPublishers.ofByteArray
BodyPublishers.ofFile
BodyPublishers.ofString

同様に、これらの一般的なボディタイプからハンドラーを作成するために使用できます:

BodyHandlers.ofByteArray
BodyHandlers.ofString
BodyHandlers.ofFile

9.3. その他のAPIの変更

1. この新しいAPIでは、discard(Object replacement)の代わりにBodyHandlers.discarding()BodyHandlers.replacing(value)を使用します。

HttpResponse response1 = HttpClient.newHttpClient()
            .send(request, BodyHandlers.discarding());
HttpResponse response1 = HttpClient.newHttpClient()
            .send(request, BodyHandlers.replacing(value));

2. [.typeNameLabel]#BodyHandlers #is added to の新しいメソッドofLines()は、応答本文のストリーミングを行のストリームとして処理します。

3. fromLineSubscriberメソッドがBodyHandlers class that can be used as an adapter between a BodySubscriber and a text-based Flow.Subscriber that parses text line by line.に追加されました

4. BodySubscribersクラスに新しいBodySubscriber.mappingを追加しました。これは、指定された関数をbodyオブジェクトに適用することで、ある応答のボディタイプから別のタイプへのマッピングに使用できます。

5. HttpClient.Redirectでは、列挙型定数SAME_PROTOCOLSECUREポリシーが新しい列挙型NORMALに置き換えられます。

10. HTTP / 2でのプッシュプロミスの処理

新しいHttpクライアントは、PushPromiseHandlerインターフェイスを介したプッシュプロミスをサポートします

これにより、サーバーはプライマリリソースを要求しながらコンテンツをクライアントに「プッシュ」できるため、ラウンドトリップが節約され、その結果、ページレンダリングのパフォーマンスが向上します。

リソースのバンドルを忘れることができるのは、実際にHTTP / 2の多重化機能です。 サーバーは、リソースごとに、クライアントへのプッシュプロミスと呼ばれる特別な要求を送信します。

受信したプッシュプロミスがある場合は、指定されたPushPromiseHandlerによって処理されます。 null値のPushPromiseHnadlerは、プッシュプロミスを拒否します。

以下の例に示すように、HttpClientにはオーバーロードされたsendAsyncメソッドがあり、このような約束を処理できます。

まず、PushPromiseHandlerを作成しましょう。

private static PushPromiseHandler pushPromiseHandler() {
    return (HttpRequest initiatingRequest,
        HttpRequest pushPromiseRequest,
        Function,
        CompletableFuture>> acceptor) -> {
        acceptor.apply(BodyHandlers.ofString())
            .thenAccept(resp -> {
                System.out.println(" Pushed response: " + resp.uri() + ", headers: " + resp.headers());
            });
        System.out.println("Promise request: " + pushPromiseRequest.uri());
        System.out.println("Promise request: " + pushPromiseRequest.headers());
    };
}

次に、sendAsyncメソッドを使用してこのプッシュプロミスを処理しましょう。

httpClient.sendAsync(pageRequest, BodyHandlers.ofString(), pushPromiseHandler())
    .thenAccept(pageResponse -> {
        System.out.println("Page response status code: " + pageResponse.statusCode());
        System.out.println("Page response headers: " + pageResponse.headers());
        String responseBody = pageResponse.body();
        System.out.println(responseBody);
    })
    .join();

11. 結論

この記事では、多くの柔軟性と強力な機能を提供するJava 9のHttpClientAPIについて説明しました。 Java9のHttpClientAPIに使用される完全なコードは、over on GitHubで入手できます。

また、Java 11 HttpClientの新しい変更点についても調査しました。これは、Java 9で導入されたHttpClientのインキュベーションをより強力な変更で標準化しました。 Java 11 Httpクライアントに使用されるコードスニペットもavailable over Githubです。

注:例では、https://postman-echo.com.によって提供されるサンプルRESTエンドポイントを使用しました