- JEP 321: HTTP Client (Standard)
- Http Client で GET リクエスト
- Http Client で POST リクエスト
- 非同期リクエスト
- Json レスポンスからオブジェクトへマッピング
- Json の POST
- HttpRequest.Builder のコピー
- レスポンスヘッダ
- Cookie
- リダイレクト
- Proxy
- ファイルのダウンロード
- まとめ
JEP 321: HTTP Client (Standard)
Java 9 で Incubator として入った HTTP Client (JEP 110: HTTP/2 Client (Incubator)) が Java 11 で標準導入となりました。
古い標準APIで HTTP 接続を行うには HttpURLConnection を使い、以下のように実装することができます。
static void get(URL url) { HttpURLConnection conn = null; try { conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); int code = conn.getResponseCode(); if (code != HttpURLConnection.HTTP_OK) { System.out.println("ResponseCode:" + code); return; } try (InputStream is = conn.getInputStream()) { System.out.println( new String(is.readAllBytes(), StandardCharsets.UTF_8)); } } catch (IOException e) { e.printStackTrace(); } finally { if (Objects.nonNull(conn)) conn.disconnect(); } }
URLConnection は ftp や gopher など複数のプロトコルを扱うよう設計されていますが、そのほとんどのプロトコルは現在では利用されていません。 API は HTTP/1.1 以前のもので抽象的すぎて使いづらく、メンテナンスも難しいもとなっています。 さらに、リクエスト/レスポンスごとに 1 つのスレッド使ったブロッキングモードでしか動かすことができません。
そこで、従来の HttpURLConnection API を置き換える新しい HTTP クライアント API が定義されました(HTTP/2 と WebSocket のサポートあり)。
中心となるクラスは以下です。
- java.net.http.HttpClient
- java.net.http.HttpRequest
- java.net.http.HttpRequest.BodyPublisher
- java.net.http.HttpResponse
- java.net.http.HttpResponse.BodyHandler
Http Client で GET リクエスト
HttpClient
を作成し HttpRequest
を渡すと HttpResponse
が帰ってきます。
以下のようになります。
HttpClient client = HttpClient.newBuilder().build(); HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://blog1.mammb.com/")) .timeout(Duration.ofSeconds(15)) .build(); HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); System.out.println(res.body());
HttpClient は HttpClient client = HttpClient.newHttpClient();
のようにインスタンス化することもできます。
BodyHandler
は HttpResponse.BodyHandlers
に以下のようなファクトリが用意されています。
- BodyHandlers::ofByteArray
- BodyHandlers::ofFile
- BodyHandlers::ofString
- BodyHandlers::ofInputStream
Http Client で POST リクエスト
BodyPublisher
を使って POST するボディを指定します。
HttpClient client = HttpClient.newBuilder().build(); HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://blog1.mammb.com/")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofFile(Paths.get("file.json"))) .build(); HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); System.out.println(res.body());
BodyPublishers
には以下のようなファクトリが用意されています。
- BodyPublishers::ofString
- BodyPublishers::ofFile
- BodyPublishers::ofByteArray
- BodyPublishers::ofInputStream
- BodyPublishers::noBody
非同期リクエスト
HttpClient.sendAsync()
で非同期リクエストにすることができます。
client.sendAsync(req, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenAccept(System.out::println) .get();
戻り値は CompletableFuture
となるため、複数のリクエストを並列に実行して、すべての完了を待ち合わせるといったことが簡単に実現できます。
HttpClient client = HttpClient.newHttpClient();
List<HttpRequest> requests = uris.stream()
.map(HttpRequest::newBuilder)
.map(HttpRequest.Builder::build)
.collect(Collectors.toList());
CompletableFuture.allOf(requests.stream()
.map(request -> client.sendAsync(request, ofString()))
.toArray(CompletableFuture<?>[]::new))
.join();
Json レスポンスからオブジェクトへマッピング
HttpClient には Json に対するサポートがありませんが、BodyHandler
を自作すれば簡単に対応が可能です。
JSON-B の RI である yasson を使う場合は以下のような依存を追加します。
dependencies {
implementation 'org.eclipse:yasson:1.0.7'
}
BodyHandler
を生成するユーティリティを以下のように作成します。
import javax.json.bind.Jsonb; import javax.json.bind.JsonbBuilder; import java.io.ByteArrayInputStream; import java.net.http.HttpResponse; import java.util.Objects; public final class ExtBodyHandlers { private static final Jsonb jsonb = JsonbBuilder.create(); private ExtBodyHandlers() { } public static <T> HttpResponse.BodyHandler<T> ofObjectAsJson(final Class<T> type) { Objects.requireNonNull(type); return (responseInfo) -> HttpResponse.BodySubscribers.mapping( HttpResponse.BodySubscribers.ofByteArray(), byteArray -> jsonb.fromJson(new ByteArrayInputStream(byteArray), type)); } }
Jsonb はスレッドセーフなので、スタティックに定義して問題ありません。
例として NTP の結果が Json 形式で取得できるので、以下のようにすることで、レスポンスとして直接 Json からオブジェクトにマッピングした結果として取得できます。
HttpClient client = HttpClient.newBuilder().build(); HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://ntp-a1.nict.go.jp/cgi-bin/json")) .build(); HttpResponse<Ntp> res = client.send(req, ExtBodyHandlers.ofObjectAsJson(Ntp.class)); System.out.println(res.body().toString());
ここで Ntp は以下のような Bean です。
public class Ntp { private String id; private float it; private float st; private long leap; private long next; private long step; public Ntp() { } // getter / setter / toString }
以下のような結果が得られます。
Ntp[id='ntp-a1.nict.go.jp', it=0.0, st=1.59602214E9, leap=36, next=1483228800, step=1]
Json の POST
前述の Jsonb を使えば、Json の POST は単に以下のようにするだけです。
HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://blog1.mammb.com/")) .POST(BodyPublishers.ofString(jsonb.toJson(Foo))) .header("Content-Type", "application/json") .build();
HttpRequest.Builder のコピー
HttpRequest.Builder
をコピーして同じような複数のリクエストを生成できます。
HttpRequest.Builder builder = HttpRequest.newBuilder() .uri(URI.create("https://blog1.mammb.com/")); HttpRequest req1 = builder.copy().setHeader("X-Counter", "1").build(); HttpRequest req2 = builder.copy().setHeader("X-Counter", "2").build();
レスポンスヘッダ
HttpResponse.headers()
よりヘッダを取得できます。
HttpResponse<String> res = client.send(request, BodyHandlers.ofString());
res.headers().allValues("X-Custom-Header").forEach(System.out::println)
firstValue()
や firstValueAsLong()
などで取得することもできます。
Cookie
CookieManager はデフォルトで無効化されているため、以下のように明示的に有効化する必要があります。
HttpClient client = HttpClient.newBuilder()
.cookieHandler(new CookieManager())
.build();
または以下のようにすることもできます。
CookieHandler.setDefault(new CookieManager());
HttpClient client = HttpClient.newBuilder()
.cookieHandler(CookieHandler.getDefault())
.build();
CookieManager を有効化すれば HttpClient によって透過的に処理されるため、リクエストやレスポンスに特別な処理は不要です。
リダイレクト
followRedirects
で指定します。
HttpClient client = HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.ALWAYS) .build();
Redirect
は以下の定義となっています。
public enum Redirect { /** Never redirect. */ NEVER, /** Always redirect. */ ALWAYS, /** Always redirect, except from HTTPS URLs to HTTP URLs. */ NORMAL }
デフォルトは Redirect.NEVER
です。
Proxy
ProxySelector
を使います。
HttpClient client = HttpClient.newBuilder()
.proxy(ProxySelector.of(new InetSocketAddress(PROXY_HOST, PROXY_PORT)))
.build();
システムワイドの Proxy 設定を使う場合は以下のようにすることもできます。
HttpClient client = HttpClient.newBuilder() .proxy(ProxySelector.getDefault()) .build();
ファイルのダウンロード
BodyHandlers.ofFile
によりレスポンスをファイルとして保存することができます。
これによりファイルダウンロードを行うことができます。
Path localFile = Paths.get("7z.exe");
HttpResponse<Path> res = client.send(request,
BodyHandlers.ofFile(localFile));
まとめ
Java 11 で正式版となった HttpClient について見てきました。
UrlBuilder などが無いため、クエリパラメータなど含めた URI の構築が多少面倒な点があります。 Java URL builder などのライブラリを合わせて使うとよいでしょう。
また、HttpClient 自体には Json のサポートは無いため、JSON-B や Gson などを使うのが現実的でしょう。
いずれにせよ、HttpURLConnection
はもう使うことはなくなるでしょう。