HTTP Client API
HTTP Client APIの追加はもっとも多くの開発者にとって影響があると思われます。
Java 9がリリースされる前にはHTTP2 Clientといった触れ込みでリリースされる予定がありましたが、Java 11までに完全に実装が書き換わり、Java 11ではHTTP2のみならず、HTTP関連のプロトコルを扱うためのClient APIとしてリリースされました。
また、Javaで、HTTP Clientとしての機能を使う場合には標準で用意されているHttpURLConnectionクラスを利用するか、Apache HttpClientやOkHttpなどの外部ライブラリを利用するケースが多いと思います。
しかし、HTTP Client APIはこれまでのHttpURLConnectionよりも使いやすく、また、多くのケースでは外部ライブラリを使わなくてもよいレベルになるはずです。
特に、HttpURLConnectionに比べて、以下のメリットがあります。
- HTTP 1.1だけではなく、HTTP 2にも対応している
- 同期処理だけでなく、Non-Blockingでの非同期処理にも対応している
- 外部APIを使っていた方でもわかりやすく、よりなりじみやすいAPI構造
- WebSocket通信も可能
基本的なAPIの使い方
最初に、APIの利用方法をイメージするため、基本的なHTTP GET/POSTの利用方法を紹介します。
リスト5は同期処理としてHTTP GETでHTMLテキストを取得する場合の実行例です。(1)でHTTP Clientのオブジェクトを作成し、(2)でリクエストを作成、(3)でリクエストを実行といった流れになっています。
HttpClient client = HttpClient.newHttpClient(); // (1) HTTP Clientを作成
HttpRequest getRequest = HttpRequest.newBuilder()
.uri(URI.create("https://codezine.jp/"))
.build(); // (2) リクエストを作成
HttpResponse<String> response = client.send(getRequest, HttpResponse.BodyHandlers.ofString()); // (3) レスポンスを取得する
logger.log(Level.INFO,response.body());
HttpURLConnectionなどを使った処理よりも、HTTP操作としては自然なコーディングスタイルとして利用できることがわかるはずです。
また、リスト6はApache HTTP Clientを使った場合のHTTP GETメソッドを使った処理のコード例ですが、同様な流れで記述できることがわかります。
従って、リプレースなどもしやすいはずです。
CloseableHttpClient client = HttpClients.createDefault(); // HTTP Clientを作成
HttpGet requestGet = new HttpGet("http://localhost/get.php"); // (2) リクエストを作成
CloseableHttpResponse response = client.execute(requestGet); // (3) レスポンスを取得する
logger.log(System.Logger.Level.INFO,EntityUtils.toString(response.getEntity()));
response.close();
続いて、非同期処理の場合のコード例をリスト7に示します。
非同期処理ではHttpResponseの代わりに、(1)のように非同期でリクエストを実行すると、戻り値としてCompletableFutureが返って来ます。
CompletableFutureはJava1.8から導入された、比較的新しいAPIです。CompletableFutureクラスの使い方は今回説明しませんが、Futureインターフェースの実装クラスで複数の非同期処理が管理しやすくなります。
レスポンスに対する処理は(2)のようにCompletableFutureクラスのthenAcceptメソッドなどを利用し処理を行うことができます。
HttpClient client = HttpClient.newHttpClient();
HttpRequest getRequest = HttpRequest.newBuilder()
.uri(URI.create("http://localhost/get.php"))
.build();
CompletableFuture<Void> f = client.sendAsync(getRequest,HttpResponse.BodyHandlers.ofString()) // (1) 非同期リクエストの実行
.thenAccept(response -> { // (2) レスポンスに対する処理
logger.log(Level.INFO,"status code : " + response.statusCode());
logger.log(Level.INFO,"body is : " + response.body());
});
続いて、JSONデータをPOST処理する場合の例をリスト8に示します。(1)でPOSTするJSONデータを作成します。そして、(2)でJSONデータのコンテントタイプをヘッダにて指定し、(3)にてPOSTメソッドで送信するJSONデータを指定します。
今までの例では、(3)のように明示的にPOSTメソッドを指定していませんでしたが、これはデフォルトでGETメソッドが指定されるためです。
HttpClient client = HttpClient.newHttpClient();
String json = "{ \"key\" : \"value\" }"; // (1) POSTするJSONデータ
HttpRequest postRequest = HttpRequest.newBuilder()
.header("Content-Type","application/json") // (2) ヘッダの指定
.uri(URI.create("http://localhost/post.php"))
.POST(HttpRequest.BodyPublishers.ofString(json)) // (3) POSTメソッドを指定
.build();
HttpResponse<String> response = client.send(postRequest, HttpResponse.BodyHandlers.ofString());
logger.log(Level.INFO,response.body());
リクエストボディとレスポンスボディの型指定
これまで紹介したコードを見ると、sendもしくはsendAsyncメソッドでHttpResponse.BodyHandlersというクラスを使用しています。
また、POSTメソッドではHttpRequest.BodyPublishersというクラスを使用しています。
HTTP Client APIでは、レスポンスボディの形式をHttpResponse.BodyHandlerインターフェースを使って処理します。
そして、HttpResponse.BodyHandlersには表1のように、よく利用するであろう実装があらかじめ用意されています。
また、ここで紹介するメソッド以外にも用意されているので、詳細はAPIリファレンスを参照してください。
| レスポンスボディの型 | メソッド | 概要 |
|---|---|---|
| Void | discarding() | レスポンスボディを取得しない場合 |
| byte[] | ofByteArray() | byte配列として取得する場合 |
| String | ofString() | 文字列として取得する場合 |
| Stream<String> | ofLines() | Stream<String>(1行ごとを文字列)として取得する場合 |
| InputStream | ofInputStream() | InputStreamとして取得する場合 |
| Path | ofFile(Path file) | 指定したファイルにデータを保存して取得する場合 |
| Path | ofFileDownload(Path directory, OpenOption... openOptions) | 指定したフォルダにContent-Dispositionヘッダで指定されたファイル名でデータを保存して取得する場合 |
リクエストボディの形式は、HttpRequest.BodyPublisherインターフェースを使って処理します。
こちらも、HttpRequest.BodyPublishersに、表2のようにあらかじめよく利用すると思われる実装が用意されています。
| 指定するリクエストボディの型 | メソッド | 概要 |
|---|---|---|
| void | noBody() | 空のリクエストボディを指定する場合 |
| byte[] | ofByteArray(byte[] buf) | byte配列データを指定する場合 |
| String | ofString(String body) | 文字列データを指定する場合 |
| InputStream | ofInputStream(Supplier<? extends InputStream> streamSupplier) | 指定したファイルのデータを指定する場合 |
| Path | ofFile(Path file) | InputStreamとして取得する場合 |
リクエストボディとレスポンスボディの処理は内部で、Java 9から導入されたリアクティブ・ストリームのFlow.PublisherやFlow.SubscriberなどのAPIを利用しています。
従って、単純なHTTP操作ではなく、少々複雑なChunk処理や、WebSocketのようなHTTPプロトコル内で動作するプロトコルなどの処理もしやすくなるはずです。
最後に
Java 11はLTS版の位置付けになっているので、開発者にとってもJavaサポートバージョンの1つの区切りとして意識する必要があると言えます。そのバージョンでJava EE関連のモジュールが削除されたことは、Javaとしての1つの方向性を示しているでしょう。
また、HTTP Client APIの追加は、多くの開発者が興味を持つ変更だと思われます。現在の開発ではWebサービスと連携して機能を開発することは必須とも言え、こういったAPIが今導入されることはむしろ遅かったとも思います。
今回のHTTP Client APIの紹介は簡単な利用例のみでしたが、次回はWebSocket通信を含めてより実用的な利用方法などを紹介します。
