L is Bエンジニアブログ

ビジネス用メッセンジャーdirectのエンジニアによるブログ

LisBエンジニアブログ

ビジネスチャットdirectのエンジニアブログ

Java11 で標準に取り入れられた HttpClient を試してみる

こんにちわ、サーバーで Java を書いている持田(@mike_neck)です。

Java11 が公式にリリースされてから約半月経ちましたが、もうお試しになられたでしょうか? 今回は Java11 でやっと標準APIに取り入れられた HTTP クライアント を簡単に試したいと思います。

f:id:mike_neck:20181019112748p:plain

なお、この記事においては 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"

HttpRequestnewBuilder() メソッドで取得できる 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> が返されて、そのジェネリクスの型 TBodyPublisher<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 を使ってみたいという方がいましたら、 ぜひご連絡を @mike_neck または こちら までください。