こんにちわ、サーバーで Java を書いている持田です。
Java11 が公式にリリースされてから約半月経ちましたが、もうお試しになられたでしょうか? 今回は Java11 でやっと標準APIに取り入れられた HTTP クライアント を簡単に試したいと思います。
なお、この記事においては jshell で実行していくものとします。
$ java -version openjdk version "11" 2018-09-25 OpenJDK Runtime Environment 18.9 (build 11+28) OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)
概要
Http Client で HTTP リクエストを構築する際に利用するクラス/インターフェースは以下のとおりです。
java.net.http.HttpClient
java.net.http.HttpRequest
java.net.http.HttpRequest.BodyPublisher
java.net.http.HttpResponse
java.net.http.HttpResponse.BodyHandler
HttpClient
まずは HttpClient
を作ります。
jshell> var client = HttpClient.newHttpClient() client ==> jdk.internal.net.http.HttpClientImpl@78b1cc93(1)
newHttpClient
メソッドで取得できる HttpClient
は HTTP のバージョンが HTTP/2 でリダイレクト非対応、デフォルト(システム準拠)の ProxySelector
、デフォルトの SSLContext
が選択されます。
これをカスタマイズしたい場合は、 newBuilder()
経由で設定します。
jshell> var client = HttpClient.newBuilder().version( ...> HttpClient.Version.HTTP_1_1).build() client ==> jdk.internal.net.http.HttpClientImpl@19d37183(2)
HttpRequest
次に HttpRequest
を作っていきます。 今回は YouTrack の issue API を呼び出してみます。
jshell> var url = String.format( ...> "https://example.com/youtrack/api/issues?fields=%s", ...> URLEncoder.encode( ...> "idReadable,summary,id,project(id,shortName, name)", ...> "UTF-8" ...> ) ...> ) url ==> "https://example.com/youtrack/api/issues ... id%2CshortName%2C+name%29"
HttpRequest
は newBuilder()
メソッドで取得できる HttpRequest.Builder
オブジェクトを使って組み立てます。
jshell> var request = HttpRequest.newBuilder( ...> ).uri(URI.create(url) ...> ).headers( ...> "Authorization","Bearer aabbcc112233", ...> "Accept", "application/json" ...> ).GET().build() request ==> https://example.com/youtrack/api/issues? ... 2CshortName%2C+name%29 GET
HttpResponse
HttpClient
は HTTP リクエストを同期でも非同期でも送信できるようになっています。 同期で送信(send
)した場合は HttpResponse<T>
が返されて、そのジェネリクスの型 T
は BodyPublisher<T>
の T
と一致します。 非同期で送信(sendAsync
)した場合は CompletableFuture<HttpResponse<T>>
が返されます(。ジェネリクスの型は同じ)。
まずは、同期でリクエストを送ります。
jshell> var response = client.send(request, HttpResponse.BodyHandlers.ofString(java.nio.charset.StandardCharsets.UTF_8)) response ==> (GET https://example.com/youtrack/api/is ... CshortName%2C+name%29) 200
send
メソッドを呼び出した後は、リクエストが返ってくるまで jshell の制御は戻ってきません。
HTTP ステータスは statusCode
メソッドで確認できます。
jshell> response.statusCode() $9 ==> 200
ヘッダーを確認する場合は headers
メソッドが使えそうです。
jshell> response.headers().map().entrySet().forEach( ...> e -> System.out.println(String.format("%s: %s", e.getKey(), e.getValue()))) access-control-expose-headers: [Location] cache-control: [no-cache, no-store, no-transform, must-revalidate] connection: [keep-alive] content-type: [application/json;charset=utf-8] date: [Fri, 19 Oct 2018 01:27:47 GMT] expires: [Thu, 01 Jan 1970 00:00:00 GMT] referrer-policy: [strict-origin-when-cross-origin] server: [openresty] strict-transport-security: [max-age=31536000; includeSubdomains;] transfer-encoding: [chunked] vary: [Accept-Encoding, User-Agent] x-content-type-options: [nosniff] x-frame-options: [SAMEORIGIN] x-xss-protection: [1; mode=block]
レスポンスボディは body
メソッドで取得できます。
jshell> response.body() $10 ==> "[{\"project\":{\"shortName\":\"TEST\",\"id\":\"81-9\",\"$type\":\"jetbrains.charisma.persistent.Project\"},\"summary\":\"test\",\"idReadable\":\"TEST-1\",\"id\":\"27-20420\",\"$type\":\"jetbrains.charisma.persistent.Issue\"},{\"project\":{\"shortName\":\"TEST\",\"id\":\"81-9\",\"$type\":\"jetbrains.charisma.persistent.Project\"},\"summary\":\"issue\",\"idReadable\":\"TEST-2\",\"id\":\"27-20401\",\"$type\":\"jetbrains.charisma.persistent.Issue\"},{\"project\":{\"shortName\":\"TEST\",\"id\":\"81-9\",\"$type\":\"jetbrains.charisma.persistent.Project\"},\"summary\":\"...{\"project\":{\"shortName\":\"TEST\",\"id\":\"81-9\",\"$type\":\"jetbrains.charisma.persistent.Project\"},\"summary\":\"abc\",\"idReadable\":\"TEST-139\",\"id\":\"27-20283\",\"$type\":\"jetbrains.charisma.persistent.Issue\"},{\"project\":{\"shortName\":\"TEST\",\"id\":\"81-9\",\"$type\":\"jetbrains.chari
非同期も試してみます。先程は GET リクエストだったので、今度は POST リクエストを投げます。
jshell> var post = HttpRequest.newBuilder( ...> ).uri(URI.create(url) ...> ).headers( ...> "Authorization","Bearer aabbcc112233", ...> "Accept", "application/json", ...> "Content-Type", "application/json" ...> ).POST( ...> HttpRequest.BodyPublishers.ofString("{" + ...> "\"project\":{\"id\":\"81-5\"}," + ...> "\"summary\":\"石田三成を探し出す\"" + ...> "}") ...> ).build() post ==> https://example.com/youtrack/api/issues? ... CshortName%2C+name%29 POST jshell> var postResponse = client.sendAsync( ...> post, ...> HttpResponse.BodyHandlers.ofString( ...> java.nio.charset.StandardCharsets.UTF_8)) postResponse ==> jdk.internal.net.http.common.MinimalFuture@38425407[Not completed] (id=57)
MinimalFuture
という JDK 内部で使われる CompletableFuture
が返ってきていることがわかります。
Consumer<HttpResponse<String>>
を作ってステータスコードとボディを表示してみます。
jshell> Consumer<HttpResponse<String>> printBody = res -> System.out.println(res.body()) printBody ==> $Lambda$198/0x0000000800204840@40a4337a jshell> Consumer<HttpResponse<String>> printStatus = res -> System.out.println(res.statusCode()) printStatus ==> $Lambda$199/0x0000000800204c40@2d1ef81a jshell> postResponse.thenAccept(printStatus.andThen(printBody)) 200 {"project":{"shortName":"example","id":"81-5","$type":"jetbrains.charisma.persistent.Project"},"summary":"石田三成を探し出す","idReadable":"example-222","id":"27-20427","$type":"jetbrains.charisma.persistent.Issue"} $15 ==> jdk.internal.net.http.common.MinimalFuture@1165b38[Completed normally] (id=105)
以上、 Java11 で標準APIに取り込まれた HTTP クライアントを紹介しました。 現在 L is B では Java8 で書かれたサーバーを Java11 に移行を企画しているところです。 そんなわけで、 L is B で Java11 を使ってみたいという方がいましたら、 ぜひご連絡をこちらまでください。