Java 9で新しいHTTPクライアントを探る
1前書き
このチュートリアルでは、Java 9の新しいインキュベーションhttps://docs.oracle.com/javase/9/docs/api/jdk/incubator/http/HttpClient.html
__.
__について探ります。
ごく最近まで、Javaは
HttpURLConnection
APIしか提供していませんでした – これは低レベルであり、機能豊富な
や
ユーザーフレンドリーであることは知られていません。
そのため、https://hc.apache.org/httpcomponents-client-ga/[Apache HttpClient]、http://www.eclipse.org/jetty/documentation/など、広く使用されているサードパーティ製のライブラリがよく使用されていました。 current/http-client-api.html[Jetty]、およびSpringのリンク:/rest-template[RestTemplate]。
2初期設定
-
HTTPクライアントモジュールはJDK 9でインキュベーターモジュールとしてバンドルされており** 、https://en.wikipedia.org/wiki/HTTP/2[HTTP/2]をサポートしていますが、依然としてHTTP/1.1を容易にします。
それを使用するには、
module-info.java
ファイルを使用してモジュールを定義する必要があります。これは、アプリケーションを実行するために必要なモジュールも示します。
module com.baeldung.java9.httpclient {
requires jdk.incubator.httpclient;
}
3 HTTPクライアントAPIの概要
HttpURLConnectionとは異なり、
HTTPクライアントは同期および非同期の要求メカニズムを提供します。
APIは3つのコアクラスで構成されています。
-
HttpRequest
–
を介して送信される要求を表します。
HttpClient
HttpClient
– ** は設定情報のコンテナとして振る舞う
複数のリクエストに共通
HttpResponse
– ** は、
HttpRequest
呼び出しの結果を表します
次のセクションでは、それぞれについて詳しく説明します。
まず、リクエストに注目しましょう。
4
HttpRequest
HttpRequest、
suggestという名前の
、
は送信したい要求を表すオブジェクトです。
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
からいずれかのメソッドを呼び出すことで、リクエストが使用するHTTPメソッドを定義できます。
-
取得する()
-
POST(ボディプロセッサ本体)
-
PUT(ボディプロセッサ本体)
-
DELETE(BodyProcessor本体)
後で詳しくは、BodyProcessorについて説明します。それでは、
非常に単純なGETリクエストの例
を作成しましょう。
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();
リクエストをカスタマイズするために使用できる最後の便利なメソッドは
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リクエストボディの設定
requestビルダーメソッドを使ってリクエストにボディを追加することができます。
POST(BodyProcessor本体)
、
PUT(BodyProcessor本体)
、および__DELETE(BodyProcessor本体)。
新しいAPIは、リクエストボディを渡すのを簡単にする、いくつかの
BodyProcessor
実装をそのまま提供します。
-
StringProcessor
(で作成された
String
から本体を読み取ります。
HttpRequest.BodyProcessor.fromString
)
**
InputStreamProcessor
(で作成された
InputStream
から本文を読み取ります。
HttpRequest.BodyProcessor.fromInputStream
)
**
ByteArrayProcessor
(で作成されたバイト配列から本文を読み込む
HttpRequest.BodyProcessor.fromByteArray
)
**
FileProcessor
(指定されたパスのファイルから本文を読み込みます。
HttpRequest.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
そのためには、
InputStream
を
Supplier
として作成する必要があります(作成を遅延させるため)。したがって、上記の
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.newBuilder()
メソッドを使用するか、または
HttpClient.newHttpClient()を呼び出すことによってインスタンス化できる
HttpClient__を使用して送信されます。
それは私たちが私たちの要求/応答を扱うために使うことができる多くの役に立つそして自己記述的な方法を提供します。
これらのいくつかをここでカバーしましょう。
6.1. プロキシを設定する
接続用のプロキシを定義できます。単に
Builder
インスタンスで
proxy()
メソッドを呼び出すだけです。
HttpResponse<String> response = HttpClient
.newBuilder()
.proxy(ProxySelector.getDefault())
.build()
.send(request, HttpResponse.BodyHandler.asString());
この例では、デフォルトのシステムプロキシを使用しました。
6.2. リダイレクトポリシーの設定
アクセスしたいページが別のアドレスに移動していることがあります。
その場合、通常新しいURIに関する情報と共にHTTPステータスコード3xxを受け取ります。適切なリダイレクトポリシーを設定すると、
HttpClient
は自動的にリクエストを新しいURIにリダイレクトできます。
Builder
の
followRedirects()
メソッドでそれを行うことができます。
HttpResponse<String> response = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.ALWAYS)
.build()
.send(request, HttpResponse.BodyHandler.asString());
すべてのポリシーはenum
HttpClient.Redirect
で定義および説明されています。
6.3. 接続に対する
Authenticator
の設定
Authenticator
は、接続用の認証情報(HTTP認証)をネゴシエートするオブジェクトです。
それは、異なる認証方式(例えば、基本認証またはダイジェスト認証など)を提供する。ほとんどの場合、認証にはサーバーに接続するためのユーザー名とパスワードが必要です。
これらの値を保持しているだけの
PasswordAuthentication
クラスを使用できます。
HttpResponse<String> 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. リクエストを送信する – 同期と非同期
New HttpClientは、リクエストをサーバーに送信するための2つの可能性を提供します。
-
send(…)
– 同期的に
(応答が来るまでブロックする) -
sendAsync(…)
– 非同期
(応答を待ちません、
ノンブロッキング)
これまでは、
send(…)
メソッドは当然応答を待っていました。
HttpResponse<String> response = HttpClient.newBuilder()
.build()
.send(request, HttpResponse.BodyHandler.asString());
この呼び出しは
HttpResponse
オブジェクトを返します。アプリケーションフローからの次の命令は、応答が既にある場合にのみ実行されます。
ただし、特に大量のデータを処理している場合は、多くの欠点があります。
それで、今、私たちは
sendAsync(…)
メソッドを使用することができます – それは
CompletableFeature <HttpResponse>
– ** を非同期的に処理するために返します:
CompletableFuture<HttpResponse<String>> response = HttpClient.newBuilder()
.build()
.sendAsync(request, HttpResponse.BodyHandler.asString());
新しいAPIは複数のレスポンスを処理し、リクエストボディとレスポンスボディをストリーミングすることもできます。
List<URI> 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<CompletableFuture<String>> 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<HttpResponse<String>> response1 = HttpClient.newBuilder()
.executor(executorService)
.build()
.sendAsync(request, HttpResponse.BodyHandler.asString());
CompletableFuture<HttpResponse<String>> response2 = HttpClient.newBuilder()
.executor(executorService)
.build()
.sendAsync(request, HttpResponse.BodyHandler.asString());
デフォルトでは、
HttpClient
はexecutor
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
を確認して、それらにアクセスできます。
httpClient.cookieManager().get().getCookieStore()
それでは、Http APIの最後のクラスである
HttpResponse
に注目しましょう。
7.
HttpResponse
オブジェクト
HttpResponse
クラスはサーバーからの応答を表します。それは多くの有用な方法を提供します – しかし最も重要な2つは以下の通りです
-
statusCode()
– 応答のステータスコード(type
int
)を返す
(
HttpURLConnection
クラスに有効な値が含まれています)
**
body()
– レスポンスのボディを返します(戻り型は
send()メソッドに渡されるresponse
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<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandler.asString());
HttpHeaders responseHeaders = response.headers();
戻り値の型として
HttpHeaders
オブジェクトを返します。これはHTTPヘッダーの読み取り専用ビューを表す
jdk.incubator.http
パッケージで定義された新しい型です。
ヘッダ値の検索を簡単にする便利な方法がいくつかあります。
7.3. レスポンスから予告編を取得する
HTTP応答には、応答コンテンツの後に含まれる追加のヘッダーが含まれる場合があります。これらのヘッダはトレーラヘッダと呼ばれます。
HttpResponseでメソッド
trailers()__を呼び出すことで取得できます。
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandler.asString());
CompletableFuture<HttpHeaders> 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<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandler.asString());
assertThat(response.version(), equalTo(HttpClient.Version.HTTP__1__1));
8結論
この記事では、多くの柔軟性と強力な機能を提供するJava 9の
HttpClient
APIを調べました。
いつものように、完全なコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-9[GitHubに追加]を見つけることができます。
注:例では、以下によって提供されるサンプルRESTエンドポイントを使用しました。
https://postman-echo.com
.