レスポンスヘッダを取得する

前回は、Java 11から正式に追加されたHTTPクライアントAPIを使った基本的なHTTP通信のやり方を解説した。

  • HTTPクライアントAPIを使ったHTTP通信の例

    HTTPクライアントAPIを使ったHTTP通信の例

今回も引き続き、いろいろなバリエーションのHTTP通信のやり方を解説する。

前回は、レスポンスボディはHttpResponseインスタンスに対してbody()メソッドを呼び出すことで取得できることを説明した。それでは、レスポンスヘッダについてはどうだろうか。次のコードは、HttpResponseからレスポンスヘッダを取り出して、ヘッダ名と値を一覧表示する例である。

// リクエストを送信
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// レスポンスヘッダを出力
System.out.println("======== Headers ========");
response.headers().map().entrySet().stream()
    .map(e -> e.getKey() + ": " + e.getValue().stream()
            .collect(Collectors.joining(";")))
    .forEach(System.out::println);

レスポンスヘッダの情報はHttpResponseオブジェクトからheaders()メソッドによって取得することができる。headers()メソッドの戻り値はHttpHeadersインスタンスで、そこからすべてのヘッダ情報を取り出したい場合はmap()メソッドを使えばよい。特定のヘッダ名を指定して値を取得したい場合には、allValues()やfirstValue()メソッドなどが利用できる。

map()メソッドはヘッダ名と値の組み合わせをMap<String,List<String>>オブジェクトとして返すので(値は複数持つる場合があるのでList<String>になっている)、上の例ではこれをStream APIを用いて処理している。実行すると、次のようにレスポンスヘッダが取得できていることがわかる。

======== Headers ========
:status: 200
cache-control: max-age=0, private, must-revalidate
content-length: 141841
content-security-policy: upgrade-insecure-requests
content-type: text/html; charset=utf-8
date: Tue, 26 May 2020 03:11:05 GMT
etag: W/"aeca63eebc50bde173aa423c41ebe6a8"
server: nginx
vary: Accept-Encoding
via: 1.1 1eaa44d3cb0c85af04bf84d0f0c5256f.cloudfront.net (CloudFront)
x-amz-cf-id: k7ZV5vejBiy6chDCLyT1LsuaJ-MjM3J5gLDu1NhOJyYpLQPgaemY4w==
x-amz-cf-pop: NRT57-C4
x-cache: Miss from cloudfront
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
x-request-id: 63a18f5f-091b-4f21-a6d3-643d6c1b3462
x-runtime: 0.232330
x-xss-protection: 1; mode=block

POSTメソッドでデータを送信する

次に、POSTメソッドによるリクエストの方法を紹介しよう。リクエストをカプセル化するためのHttpRequestクラスは、デフォルトではGETメソッドを使用してリクエストを行う。POSTメソッドを使うためには、次のようにHttpRequest.Builderを使った設定でPOST()メソッドの呼び出しを追加すればよい。

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://httpbin.org/post"))
    .timeout(Duration.ofSeconds(60))
    .POST(HttpRequest.BodyPublishers.ofString("""
                Hello!
                This is a test for HTTP Client API.
                """))
    .build();

POSTリクエストの本文を追加したい場合は、HttpRequest.BodyPublisherインスタンスでカプセル化した上で、POSTメソッドの引数として渡すことで設定できる。HttpRequest.BodyPublisherインタフェースはFlow.Publisher<ByteBuffer>をextendsしているため、リアクティブストリームのプロデューサとして動作するようになっている。

(注:Java 9から、リアクティブストリーム用のAPIが標準機能として追加されている。リアクティブストリームAPIについては、また別の機会に解説したい)

HttpRequest.BodyPublisherインスタンスはHttpRequest.BodyPublishersクラスの各種staticメソッドを使って生成できる。リクエスト本体に文字列を渡したい場合は、ofString()メソッドを使ってHttpRequest.BodyPublisherを作ればよい。

上の例で接続先に指定している https://httpbin.org/ は、いろいろなHTTP通信をテストするサービスである。https://httpbin.org/post にリクエストを送信した場合は、リクエスト本文に渡された内容がJSON形式で返ってくる。今回の例の場合、通信に成功すれば次のようなレスポンスが得られるはずだ。

{
  "args": {}, 
  "data": "Hello!\nThis is a test for HTTP Client API.\n", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Content-Length": "43", 
    "Host": "httpbin.org", 
    "User-Agent": "Java-http-client/14", 
    "X-Amzn-Trace-Id": "Root=1-5ecb8a7f-d214363bd79ba1acfff44d60"
  }, 
  "json": null, 
  "origin": "xxx.xxx.xxx.xxx"
  "url": "https://httpbin.org/post"
}

HttpRequest.BodyPublishersクラスには、ofString()のほかにも、本文を送信しない場合のnoBody()メソッドや、ファイルを送信する場合のofFile()メソッド、InputStreamからデータを読み取って送信する場合のofInputStream()メソッドなどがある。次のコードは、InputStreamから本文を読み取って送信する例である。

// データ送信のためのSupplierを作成する
byte[] data = "This is a sample request body.".getBytes();
Supplier<ByteArrayInputStream> supplier = new Supplier<>() {
        @Override
        public ByteArrayInputStream get() {
            return new ByteArrayInputStream(data);
        }
};

// HttpRequestインスタンスを作成
HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://httpbin.org/post"))
        .timeout(Duration.ofSeconds(60))
        .headers("Content-Type", "text/plain;charset=UTF-8")        // Content-Typeヘッダをセット
        .POST(HttpRequest.BodyPublishers.ofInputStream(supplier))   // Publisherをセット
        .build();

POST()メソッドに渡すHttpRequest.BodyPublisherインスタンスを、ofInputStream()メソッドで作成してる。このメソッドの引数にはInputStreamを返すSupplierインスタンスを渡す必要がある。上の例では、Supplierを使用していることをわかりやすくするために事前にインスタンスを用意した上でセットしているが、このPOST()メソッドの行はLambdaを利用して次のように記述することもできる。

.POST(HttpRequest.BodyPublishers.ofInputStream(() ->new ByteArrayInputStream(data)))

なお、PUTメソッドによるリクエストもPOSTメソッドと同様にHttpRequest.BodyPublishersを使って行う。PUTリクエストはPUT()メソッドで設定できる。

Basic認証を利用する

続いて、Basic認証を利用する方法を紹介する。HTTPクライアントAPIで認証を利用するには、HttpClient.Builderに対してauthenticator()メソッドを使ってAuthenticatorを設定する。Authenticatiorはネットワーク接続の認証情報を扱うためのクラスで、通常はこのクラスを継承してgetPasswordAuthentication()メソッドをオーバーライドする形で利用する。次のコードは、ユーザー名「USER」、パスワード「PASSWD」としてAuthenticatorを作成し、HttpClient.Builderにセットしてリクエストを送信する例である。

// HttpClientインスタンスを作成
HttpClient client = HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_2)
        .authenticator(new Authenticator() {    // ユーザー名とパスワードを渡してBasic認証を使用
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication("USER","PASSWD".toCharArray());
            }
        })
        .build();

// HttpRequestインスタンスを作成
HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://httpbin.org/basic-auth/USER/PASSWD"))
        .timeout(Duration.ofSeconds(60))
        .build();

try {
    // リクエストを送信
    HttpResponse<String> response = client.send(request,
            HttpResponse.BodyHandlers.ofString());
    System.out.println(response.body());
} catch (IOException | InterruptedException e) {
    e.printStackTrace();
}

https://httpbin.org/basic-auth/USER/PASSWD はBasic認証のテスト用のURLで、"USER"と"PASSWD"の部分は任意のユーザー名とパスワードを設定できる。上のコードは、認証に成功すればレスポンスを取得できるが、Authenticatorに異なるユーザー名/パスワードを設定した場合などは、認証に失敗してIOExceptionが発生するはずだ。

次回は、非同期通信やWebSocketの使い方について紹介する。