JavaでHTTPリクエストを作成する5つの方法

July 21, 2020
執筆者
レビュー担当者
Diane Phan
Twilion

How to make HTTP requests in Java

[ヘッダー画像クレジット: バタフライ星雲の鉄、NASA Astronomy Picture of the Day、2020年7月21日(修正版)]

この記事はTwilio Developer EvangelistのMatthew Gilliardこちら(英語)て執筆した記事を日本語化したものてす。

HTTPリクエストの作成はモダンプログラミングのコア機能であり、新しいプログラミング言語の習得時に最初に学習したい操作の1つです。Javaプログラマーがこの操作を実行する場合、JDKのコアライブラリやサードパーティライブラリなどさまざまな方法を使用できます。この記事では、Java HTTPクライアントを使用する方法について説明します。もちろん他の方法も使用できますので、情報をお寄せいただければ幸いです。この記事では、次の内容について説明します。

Core Java:
  • HttpURLConnection
  • HttpClient
よく使用されるライブラリ:
  • ApacheHttpClient
  • OkHttp
  • Retrofit

ここではNASA APIAstronomy Picture of the Day APIをコードサンプルに使用します。このコードはすべて、GitHubのJava 11ベースのプロジェクトにあります。

Java httpリクエストの作成に使用する主要なJava API

Java 1.1以来、JDKに付属するコアライブラリにはHTTPクライアントが含まれています。Java 11では新しいクライアントが追加されました。プロジェクトに余分な依存関係を追加したくない場合は、これらのクライアントの1つを使用することをお勧めします。

Java 1.1 HttpURLConnection

まず、クラス名で頭字語を大文字にするかどうかを決めなければなりません。眼を閉じて1997年当時のことを思い出してみましょう。この年には映画「タイタニック」が大ヒットして感動の嵐を巻き起こし、スパイスガールズのアルバムがベストセラーになりましたが、間違いなく最大のニュースは、Java 1.1にHttpURLConnectionが追加されたことでしょう。  HttpURLConnectionを使用してGETリクエストを作成し、APODデータを取得すると、次のようになります。

// Create a neat value object to hold the URL
URL url = new URL("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY");

// Open a connection(?) on the URL(??) and cast the response(???)
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

// Now it's "open", we can set the request method, headers etc.
connection.setRequestProperty("accept", "application/json");

// This line makes the request
InputStream responseStream = connection.getInputStream();

// Manually converting the response body InputStream to APOD using Jackson
ObjectMapper mapper = new ObjectMapper();
APOD apod = mapper.readValue(responseStream, APOD.class);

// Finally we have the response
System.out.println(apod.title);

[GitHub - 全コード]

このコードはかなり冗長で、分かりにくいという声も聞かれます(URLを開いた後にヘッダーを設定する理由も腑に落ちません)。POST本文やカスタムタイムアウトなどを使用し、より複雑なリクエストの作成が必要な場合は、それも可能ですが、私はこのAPIを直観的で使いやすいと思ったことは一度もありません。

では一体、HTTPUrlConnectionはどのような場合に使用するのでしょうか。古いバージョンのJavaを使用しているクライアントをサポートしており、かつ依存関係を追加できない場合は、HTTPUrlConnectionが役立ちます。このような開発者は少数派かもしれませんが、もっと古いコードベースでも使われていますので、最新のアプローチを理解するためにもぜひお読みください。

Java 11 HttpClient

HttpURLConnectionの登場から20年以上が経ち、映画「ブラックパンサー」がヒットしている頃、Java 11に新しいHTTPクライアントであるjava.net.http.HttpClientが追加されました。これは非常に論理的なAPIであり、HTTP/2とWebsocketを処理できます。また、CompletableFuture APIを使用して、リクエストを同期的または非同期的に作成することもできます。

私がHTTPリクエストを作成する場合は、100回のうち99回は、コード内でレスポンス本文を読みたいと思います。これが難しいライブラリは、使用したいと思いません。HttpClientではBodyHandlerを受け付けるため、HTTPレスポンスを任意のクラスに変換できます。組み込みハンドラもいくつかあります。例えばString、バイナリデータ用のbyte[]、行で分割するStream<String>などです。独自のハンドラを定義することもできます。JSON解析用の組み込みBodyHandlerがないため、役に立つ場合があります。私が記述したハンドラは(こちら)はJacksonをベースとし、Javaドキュメントにある例に従って記述しました。これはAPODクラスSupplierを返すため、結果が必要な場合は.get()を呼び出します。

以下の例は同期的リクエストです。

// create a client
var client = HttpClient.newHttpClient();

// create a request
var request = HttpRequest.newBuilder(
       URI.create("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY"))
   .header("accept", "application/json")
   .build();

// use the client to send the request
var response = client.send(request, new JsonBodyHandler<>(APOD.class));

// the response:
System.out.println(response.body().get().title);

非同期的リクエストの場合は、clientrequestを同じ方法で作成してから.sendの代わりに.sendAsyncを呼び出します。

// use the client to send the request
var responseFuture = client.sendAsync(request, new JsonBodyHandler<>(APOD.class));

// We can do other things here while the request is in-flight

// This blocks until the request is complete
var response = responseFuture.get();

// the response:
System.out.println(response.body().get().title);

[GitHub - 全コード]

サードパーティのJava HTTPクライアントライブラリ

組み込みクライアントが役に立たなくても、大丈夫。プロジェクトに利用できる多くのライブラリがあります。

Apache HttpClient

Apache Software FoundationHTTPクライアントは、長年使用されています。 このHTTPクライアントは広く使用されており、多くの高度なライブラリの基盤になっていますが、その歴史は少し複雑です。旧式の一般的なHttpClientは開発が終了しており、新バージョン(これもHttpClientクライアントと呼ばれます)はHttpComponentsプロジェクトで開発されています。HTTP/2サポートが追加されたバージョン5.0は、2020年のはじめにリリースされました。このライブラリも、同期的リクエストと非同期的リクエストをサポートしています。

全体的に、このAPIは比較的低レベルであり、多くの実装を自分で行う必要があります。次のコードは、NASA APIを呼び出す例です。使用はそれほど難しくはありませんが、本番コードで必要な多くのエラー処理をスキップしたため、Jacksonコードを追加してJSONレスポンスを解析しなければなりませんでした。また、stdoutでの警告を回避するために、ロギングフレームワークを構成する必要があります(大した作業ではありませんが少し面倒です)。とにかくコードをご覧いただきましょう。

ObjectMapper mapper = new ObjectMapper();

try (CloseableHttpClient client = HttpClients.createDefault()) {

   HttpGet request = new HttpGet("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY");

   APOD response = client.execute(request, httpResponse ->
       mapper.readValue(httpResponse.getEntity().getContent(), APOD.class));

   System.out.println(response.title);
}

[GitHub - 全コード]

Apacheは、syncリクエストとasyncリクエストの例をいくつか提供しています。

OkHttp

OkHttpSquareのHTTPクライアントです。GZIPの自動処理、ネットワークエラー時のレスポンスのキャッシングや再試行、または他のホストへのフォールバック、HTTP/2とWebSocketのサポートなど、多くの便利な組み込み機能を備えています。このAPIはすっきりしていますが、組み込みのJSONレスポンス解析機能はありません。そのため、ここでもJacksonを用いてJSON解析用のコードを追加しました。

ObjectMapper mapper = new ObjectMapper();

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
   .url("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY")
   .build(); // defaults to GET

Response response = client.newCall(request).execute();

APOD apod = mapper.readValue(response.body().byteStream(), APOD.class);

System.out.println(apod.title);

[GitHub - 全コード]

これでも十分ですが、OkHttpの真の価値は、特にRetrofitを追加した場合に発揮されます。

Retrofit

RetrofitはSquareのもう1つのライブラリであり、OkHttp上に構築されます。OkHttpは低レベルの各種機能に加え、Javaクラスを構築する機能も備えています。これでHTTPの詳細を抽象化し、Java対応の優れたAPIを実現します。

まず、APOD APIに対して呼び出すメソッドを宣言するインターフェースと、HTTPリクエストへの対応方法を定義するアノテーションを作成する必要があります。

public interface APODClient {
   @GET("/planetary/apod")
   @Headers("accept: application/json")
   CompletableFuture<APOD> getApod(@Query("api_key") String apiKey);
}

CompletableFuture<APOD>は、その戻り値の型によって非同期的クライアントになります。Squareはその他のアダプタも提供していますが、開発者が独自のアダプタを記述することもできます。このようなインターフェースを利用すると、クライアントと同じテスト環境を構築できるため、非常に便利です。

インターフェースの宣言後に、Retrofitに実装の作成を指示します。 この実装を使用して、特定のベースURLに対するリクエストを作成できます。これは、統合テストでのベースURLの切り替えにも便利です。クライアントの生成用コードは、次のとおりです。

Retrofit retrofit = new Retrofit.Builder()
   .baseUrl("https://api.nasa.gov")
   .addConverterFactory(JacksonConverterFactory.create())
   .build();

APODClient apodClient = retrofit.create(APODClient.class);

CompletableFuture<APOD> response = apodClient.getApod("DEMO_KEY");

// do other stuff here while the request is in-flight

APOD apod = response.get();

System.out.println(apod.title);

[GitHub - 全コード]

API認証

インターフェースに複数のメソッドがあり、そのすべてにAPIキーが必要な場合は、ベースOkHttpClientHttpInterceptorを追加してAPIキーを構成できます。カスタムクライアントをRetrofit.Builderに追加できます。  カスタムクライアントの作成に必要なコードは、次のとおりです。

private OkHttpClient clientWithApiKey(String apiKey) {
   return new OkHttpClient.Builder()
           .addInterceptor(chain -> {
               Request originalRequest = chain.request();
               HttpUrl newUrl = originalRequest.url().newBuilder()
                   .addQueryParameter("api_key", apiKey).build();
               Request request = originalRequest.newBuilder().url(newUrl).build();
               return chain.proceed(request);
           }).build();
}

[GitHub - 全コード]

この種のJava APIは、極めてシンプルなケースを除くすべてのケースに使用できます。リモートAPIを表すクラスの構築は抽象化の好例であり、依存関係の注入に便利です。また、このようなクラスをRetrofitでカスタマイズ可能なOkHttpクライアントに基づいて作成するのも良い方法です。

その他のJava用HTTPクライアント

この記事をTwitterに投稿して以来、多くの方から、使用しているHTTPクライアントに関するコメントが寄せられました。以上の内容が本当に求めている情報でない場合は、次の提案も参照してください。

  • REST Assured - RESTサービスのテスト用に設計されたHTTPクライアント。リクエスト作成に最適なインターフェースと、レスポンスに関するアサーションを行うための便利なメソッドを提供します。
  • cvurl - Java 11 HttpClientのラッパーです。複雑なリクエストの作成時に発生しうる問題の解消に役立ちます。
  • Feign - Retrofitと同様に、アノテーション付きのインターフェースでクラスを構築できます。Feignは非常に柔軟性が高く、複数のオプションを使用してリクエスト、メトリックス、リトライの作成と読み取りを実行できます。
  • SpringのRestTemplate(同期)クライアントとWebClient(非同期)クライアント - プロジェクト全般でSpringを使用してきた場合は、そのエコシステムを継続することをお勧めします。Baeldungの比較記事を参照してください。
  • MicroProfile Rest Client - アノテーション付きインターフェースモードでクラスを構築できる、もう1つのクライアントです。興味深いのは、同じインターフェースを再利用してWebサーバーも作成でき、クライアントとサーバーを適合させられるという点です。サービスと一緒にそのサービスのクライアントも構築する場合は、使用してみることをお勧めします。

まとめ

JavaのHTTPクライアントには多くの選択肢があります。単純なケースでは、組み込みのjava.net.http.HttpClientをお勧めします。より複雑なユースケースの場合、またはより大きなアプリケーションの一部としてHTTP APIをJavaクラスとして抽象化したい場合は、RetrofitまたはFeignを参照してください。Happy hacking, I can’t wait to see what you build!

📧 mgilliard@twilio.com

🐦 @MaximumGilliard