高機能HTTPクライアントOkHttp完全攻略ガイド

この記事から得られる知識

  • OkHttpの基本的な仕組みと、HTTP/2対応や接続プーリングなどの強力な機能
  • 同期リクエストと非同期リクエストの具体的な実装方法と、それぞれの使い分け
  • GETリクエストや、JSONボディ・ファイルアップロードを含む様々なPOSTリクエストの送信方法
  • リクエスト/レスポンスに割り込んで共通処理を実装できる「インターセプター」の活用法
  • 接続・読み取り・書き込みタイムアウトやリトライ処理といった、詳細なカスタマイズ方法
  • レスポンスキャッシュ機能を利用した、通信パフォーマンスの最適化手法

第1章: OkHttpとは? – モダンなHTTP通信の必需品

OkHttpは、Square社によって開発された、JavaおよびKotlin向けのモダンで高機能なHTTPクライアントライブラリです。 Androidアプリ開発ではデファクトスタンダードとして広く採用されており、その信頼性と性能の高さからサーバーサイドJava開発でも人気を博しています。

従来のHttpURLConnectionと比較して、OkHttpは非常にシンプルで直感的なAPIを提供しながら、内部では数多くの高度な機能によって通信の効率性と安定性を高めています。

これらの特徴により、OkHttpは単なるリクエスト送信ライブラリにとどまらず、ネットワーク通信に関わる複雑な処理をスマートにこなし、開発者が本来のビジネスロジックに集中できるように支援してくれます。


第2章: 準備と基本的な使い方

2-1. 依存関係の追加

OkHttpを利用するには、まずプロジェクトにライブラリを追加する必要があります。ビルドツールに応じた設定をファイルに追加してください。バージョンは最新のものを確認して使用することを推奨します。

pom.xmlに以下の依存関係を追加します。

<dependency>
  <groupId>com.squareup.okhttp3</groupId>
  <artifactId>okhttp</artifactId>
  <version>LATEST_VERSION</version> <!-- 最新バージョンに置き換えてください -->
</dependency>

2-2. OkHttpClient, Request, Response

OkHttpの利用は、主に3つのクラスを中心に進められます。

クラス 説明
OkHttpClient HTTPリクエストを実行するための中心的なクラス。タイムアウト設定、キャッシュ、インターセプターなどの設定を保持します。
Request HTTPリクエストを表現するクラス。URL、HTTPメソッド、ヘッダー、リクエストボディなどを設定します。
Response HTTPレスポンスを表現するクラス。ステータスコード、ヘッダー、レスポンスボディなどが含まれます。

2-3. インスタンスの生成とシングルトン

OkHttpClientインスタンスは、内部で接続プーリングやスレッドプールなどのリソースを共有します。そのため、リクエストごとに新しいインスタンスを生成するのではなく、アプリケーション全体でシングルトン(単一のインスタンス)として共有することが強く推奨されています。 これにより、リソースを効率的に再利用し、アプリケーションのパフォーマンスを最大化できます。

// アプリケーションで共有するシングルトンインスタンス
OkHttpClient client = new OkHttpClient();

特定のAPI呼び出しでのみタイムアウト値を変更したいなど、個別の設定が必要な場合は、既存のクライアントをベースに新しいインスタンスを作成できます。

OkHttpClient customClient = client.newBuilder()
    .readTimeout(10, TimeUnit.SECONDS)
    .build();

第3章: 同期リクエストと非同期リクエスト

OkHttpは、同期(ブロッキング)と非同期(ノンブロッキング)の両方のリクエスト実行方法をサポートしています。 アプリケーションの要件に応じて適切に使い分けることが重要です。

3-1. 同期リクエスト (Synchronous)

同期リクエストは、execute()メソッドを使用して実行します。このメソッドは、レスポンスが完全に受信されるまで現在のスレッドをブロックします。 そのため、Androidのメインスレッドなど、UIを扱うスレッドで直接呼び出すと、アプリケーションがフリーズする原因となるため絶対に行ってはいけません。 バックグラウンドスレッドで実行する必要があります。

注意: 同期リクエストは、結果を待つ間スレッドをブロックします。 UIスレッドでの使用は避け、withContext(Dispatchers.IO)のような仕組みを使ってバックグラウンドで処理してください。
OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
    .url("https://api.example.com/data")
    .build();

try (Response response = client.newCall(request).execute()) { // この行で処理がブロックされる
    if (!response.isSuccessful()) {
        throw new IOException("Unexpected code " + response);
    }

    // レスポンスヘッダーの取得
    Headers responseHeaders = response.headers();
    for (int i = 0; i < responseHeaders.size(); i++) {
        System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
    }

    // レスポンスボディの取得
    // response.body().string() は一度しか呼び出せないことに注意
    String responseBody = response.body().string();
    System.out.println(responseBody);

} catch (IOException e) {
    e.printStackTrace();
}

try-with-resources構文を使用することで、Responseオブジェクトが確実にクローズされ、リソースリークを防ぐことができます。

3-2. 非同期リクエスト (Asynchronous)

非同期リクエストは、enqueue()メソッドを使用します。このメソッドは即座にリターンし、HTTPリクエストはバックグラウンドのスレッドで実行されます。 結果は、引数として渡すCallbackインターフェースを通じて通知されます。 UIをブロックしないため、Androidアプリなどでのネットワーク通信に適しています。

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
    .url("https://api.example.com/data")
    .build();

client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        // リクエストが失敗した場合の処理 (ネットワークエラーなど)
        e.printStackTrace();
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        // レスポンスはワーカースレッドで受け取る
        if (!response.isSuccessful()) {
            throw new IOException("Unexpected code " + response);
        }

        try (ResponseBody responseBody = response.body()) {
            // ここでレスポンスを処理
            System.out.println(responseBody.string());
        }
        // UIの更新が必要な場合は、runOnUiThreadなどを使用してメインスレッドに処理を渡す必要がある
    }
});

onFailureは、DNS解決の失敗、ソケットのタイムアウト、接続拒否など、ネットワークレベルのエラーが発生した場合に呼び出されます。一方、onResponseは、サーバーが4xxや5xxといったエラーコードを返した場合でも、レスポンスが正常に受信できれば呼び出されます。そのため、onResponse内では必ずresponse.isSuccessful()をチェックすることが重要です。

同期と非同期の使い分け

  • 同期: スクリプトや単純なコンソールアプリケーションなど、処理を順番に実行したい場合に適しています。
  • 非同期: GUIアプリケーションや、多数の同時接続を捌くサーバーアプリケーションなど、I/O処理でスレッドをブロックしたくない場合に必須です。

第4章: 実践的なリクエストの送信

4-1. GETリクエスト (クエリパラメータ付き)

GETリクエストでクエリパラメータを渡すには、HttpUrl.Builderを使用するのが最も安全で便利です。 このビルダーは、パラメータ名を自動でURLエンコードしてくれるため、手動でのエンコーディングミスを防げます。

OkHttpClient client = new OkHttpClient();

HttpUrl.Builder urlBuilder = HttpUrl.parse("https://api.example.com/search").newBuilder();
urlBuilder.addQueryParameter("q", "OkHttp & Retrofit");
urlBuilder.addQueryParameter("sort", "stars");

String url = urlBuilder.build().toString();

Request request = new Request.Builder()
    .url(url)
    .build();

// 同期または非同期でリクエストを実行
// ...

4-2. POSTリクエスト (様々なボディ)

POSTリクエストでは、RequestBodyオブジェクトを使用してサーバーにデータを送信します。 OkHttpは様々な形式のボディを簡単に作成できます。

JSONボディの送信

最も一般的なユースケースの一つがJSONデータの送信です。RequestBody.create()メソッドに、JSON文字列とMediaTypeを指定します。

public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String json = "{\"userName\":\"testUser\", \"password\":\"secret\"}";

RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
    .url("https://api.example.com/login")
    .post(body)
    .build();

try (Response response = client.newCall(request).execute()) {
    // ...
}

GsonやMoshiなどのライブラリと組み合わせることで、Javaオブジェクトを直接JSON文字列に変換して利用するのが一般的です。

フォーム形式 (Form) の送信

HTMLのフォーム送信のようにapplication/x-www-form-urlencoded形式でデータを送る場合は、FormBody.Builderを使用します。

OkHttpClient client = new OkHttpClient();

RequestBody formBody = new FormBody.Builder()
    .add("grant_type", "password")
    .add("username", "testUser")
    .add("password", "secret")
    .build();

Request request = new Request.Builder()
    .url("https://api.example.com/token")
    .post(formBody)
    .build();

try (Response response = client.newCall(request).execute()) {
    // ...
}

ファイルアップロード (Multipart)

ファイルや複数のデータを同時に送信する場合は、multipart/form-data形式を使用します。OkHttpではMultipartBody.Builderを使ってこれを構築します。

private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

OkHttpClient client = new OkHttpClient();

File fileToUpload = new File("path/to/your/image.png");

RequestBody requestBody = new MultipartBody.Builder()
    .setType(MultipartBody.FORM)
    .addFormDataPart("title", "My Awesome Picture")
    .addFormDataPart("description", "A picture of a cat taken by OkHttp.")
    .addFormDataPart("image", fileToUpload.getName(),
        RequestBody.create(fileToUpload, MEDIA_TYPE_PNG))
    .build();

Request request = new Request.Builder()
    .url("https://api.example.com/upload")
    .post(requestBody)
    .build();

try (Response response = client.newCall(request).execute()) {
    if (!response.isSuccessful()) {
        throw new IOException("Unexpected code " + response);
    }
    System.out.println(response.body().string());
}

addFormDataPartメソッドで、テキストデータとファイルデータをパートとして追加していきます。ファイルパートでは、ファイル名とファイル内容を含むRequestBodyを指定します。


第5章: インターセプター (Interceptor) の活用

インターセプターは、OkHttpの中でも特に強力で柔軟な機能の一つです。 これを利用すると、実際のリクエストがサーバーに送信される前や、レスポンスがアプリケーションに返される前に、処理に割り込むことができます。

これにより、以下のような共通処理をエレガントに実装できます。

  • ログ出力: すべてのリクエストとレスポンスの内容を詳細にログ出力する。
  • 認証ヘッダーの付与: すべてのリクエストにAPIキーやOAuthトークンなどの認証情報を自動的に追加する。
  • リクエストの書き換え: ヘッダーを追加・削除したり、URLを変更したりする。
  • エラーハンドリング: 特定のエラーコードを受け取った際に、共通の処理(例:トークンのリフレッシュ)を行う。
  • GZIP圧縮: リクエストボディをGZIP圧縮して送信する。

5-1. Application Interceptor と Network Interceptor

OkHttpには2種類のインターセプターがあり、それぞれ役割が異なります。

種類 特徴 登録方法
Application Interceptor
  • アプリケーションが発行した元のリクエストを監視する。
  • リダイレクトやリトライを追跡しない。1回のリクエストにつき1度だけ呼び出される。
  • キャッシュからのレスポンスであっても呼び出される。
OkHttpClient.Builder().addInterceptor(Interceptor)
Network Interceptor
  • 実際にネットワークを流れるリクエストとレスポンスを監視する。
  • リダイレクトやリトライが発生した場合、その都度呼び出される。
  • キャッシュからのレスポンスの場合は呼び出されない(ネットワーク通信が発生しないため)。
OkHttpClient.Builder().addNetworkInterceptor(Interceptor)

一般的には、リクエストヘッダーの追加や全体的なログ出力など、アプリケーションの意図を直接操作したい場合はApplication Interceptorを使用します。ネットワークの通信状況そのものを監視したい場合にNetwork Interceptorが役立ちます。

5-2. 実装例: ログ出力インターセプター

リクエストとレスポンスの情報をログに出力するシンプルなインターセプターを作成してみましょう。

import java.io.IOException;
import java.util.Locale;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

class LoggingInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        long t1 = System.nanoTime();
        System.out.println(String.format("Sending request %s on %s%n%s",
                request.url(), chain.connection(), request.headers()));

        Response response = chain.proceed(request);

        long t2 = System.nanoTime();
        System.out.println(String.format(Locale.US, "Received response for %s in %.1fms%n%s",
                response.request().url(), (t2 - t1) / 1e6d, response.headers()));

        return response;
    }
}

このインターセプターをクライアントに登録するには、以下のようにします。

OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new LoggingInterceptor())
    .build();

chain.proceed(request)が重要な役割を担います。これは、インターセプターチェーン内の次のインターセプター(または、チェーンの最後であれば実際のネットワーク呼び出し)に処理を委譲し、その結果であるResponseを返します。この呼び出しを挟んで前後に処理を記述することで、リクエストとレスポンスの両方に介入できます。


第6章: 高度な設定とカスタマイズ

OkHttpClient.Builder を使用することで、通信の挙動を細かく制御できます。

6-1. タイムアウト設定

ネットワークは常に安定しているとは限りません。適切なタイムアウト設定は、アプリケーションが無限に待機状態になるのを防ぎ、ユーザー体験を損なわないために不可欠です。 OkHttpでは、以下の3種類のタイムアウトを設定できます。

種類 説明 デフォルト
接続タイムアウト (Connect Timeout) サーバーとのTCP接続が確立されるまでの最大待機時間。 10秒
読み取りタイムアウト (Read Timeout) 接続が確立された後、サーバーからのレスポンスデータが届くまでの、バイト間の最大待機時間。 10秒
書き込みタイムアウト (Write Timeout) リクエストボディのデータをサーバーに送信する際の、バイト間の最大待機時間。 10秒

これらの値は、OkHttpClient.Builderで設定します。

OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(30, TimeUnit.SECONDS)
    .writeTimeout(30, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .build();

6-2. リトライ設定

OkHttpはデフォルトで、べき等なリクエスト(GETなど)が失敗した場合に自動でリトライを試みます。この挙動はretryOnConnectionFailure()メソッドで制御できます。

OkHttpClient client = new OkHttpClient.Builder()
    .retryOnConnectionFailure(false) // 自動リトライを無効化
    .build();

より複雑なリトライロジック(例:特定のエラーコードの場合のみリトライ、指数バックオフで待機時間を延ばすなど)を実装したい場合は、インターセプターを利用するのが一般的です。

6-3. キャッシュ設定

OkHttpはHTTPキャッシュをサポートしており、一度取得したレスポンスをローカルストレージに保存し、次回以降の同じリクエストで再利用することで、通信時間とデータ量を削減できます。

キャッシュを有効にするには、キャッシュディレクトリと最大サイズを指定してCacheオブジェクトを作成し、クライアントに設定します。

int cacheSize = 10 * 1024 * 1024; // 10 MiB
File cacheDirectory = new File("my-app-cache");
Cache cache = new Cache(cacheDirectory, cacheSize);

OkHttpClient client = new OkHttpClient.Builder()
    .cache(cache)
    .build();

OkHttpは、レスポンスのCache-Controlヘッダーを尊重してキャッシュの挙動を決定します。 例えば、Cache-Control: max-age=3600というヘッダーがあれば、そのレスポンスは1時間有効なキャッシュとして保存されます。リクエスト側でキャッシュの挙動を強制したい場合は、Request.BuildercacheControl()メソッドを使用できます。

// キャッシュがあっても必ずネットワークにアクセスする
Request request = new Request.Builder()
    .url("https://api.example.com/news")
    .cacheControl(CacheControl.FORCE_NETWORK)
    .build();

// キャッシュが存在する場合のみ、それを使用する (オフライン時などに有効)
Request requestOffline = new Request.Builder()
    .url("https://api.example.com/news")
    .cacheControl(CacheControl.FORCE_CACHE)
    .build();

第7章: OkHttpと他ライブラリの連携

OkHttpはそのままでも非常に強力ですが、他のライブラリと組み合わせることで、さらに開発効率を高めることができます。特に有名なのがRetrofitとの連携です。

Retrofitとの連携

Retrofitは、OkHttpと同じくSquare社が開発した、型安全なHTTPクライアントライブラリです。 RetrofitはOkHttpを内部の通信エンジンとして利用しており、REST APIをJavaのインターフェースとして定義できるようにしてくれます。

RetrofitはOkHttpの上に構築された抽象化レイヤーであり、ネットワーク通信をより簡単かつクリーンに、特にREST API向けに行えるようにします。

例えば、以下のようなAPIがあったとします。

GET /users/{username}

これをRetrofitでは、次のようなインターフェースとして定義します。

public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
}

Retrofitがアノテーションを解析し、OkHttpを使った実際のリクエスト送信コードを自動生成してくれます。 これにより、URLの組み立て、パラメータのエンコード、レスポンスのJSONパースといった定型的なコードを記述する必要がなくなり、APIの仕様とJavaのメソッド呼び出しが直結するため、コードの可読性と保守性が劇的に向上します。

Retrofitを利用する場合でも、OkHttpの強力なカスタマイズ機能はそのまま活かせます。作成したOkHttpClientインスタンスをRetrofitのビルダーに渡すだけで、ログ出力インターセプターやキャッシュ設定などを適用できます。

// カスタマイズしたOkHttpClientインスタンス
OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .addInterceptor(new LoggingInterceptor())
    .cache(new Cache(cacheDir, cacheSize))
    .build();

// RetrofitのビルダーにOkHttpClientをセット
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .client(okHttpClient) // ここで連携
    .addConverterFactory(GsonConverterFactory.create())
    .build();

GitHubService service = retrofit.create(GitHubService.class);

このように、OkHttpを低レベルな通信エンジンとして、Retrofitを高レベルなAPI定義層として使い分けるのが、現代のJava/Android開発におけるベストプラクティスの一つとなっています。


まとめ

本記事では、高機能HTTPクライアントライブラリであるOkHttpについて、基本的な使い方からインターセプター、キャッシュ、各種カスタマイズといった高度なトピックまで、幅広く解説しました。

OkHttpは、HTTP/2や接続プーリングといったモダンな技術を背景に持ち、効率的で安定したネットワーク通信を簡単に実現します。同期・非同期の両方に対応した柔軟なAPI、リクエスト・レスポンスに介入できる強力なインターセプター機能、そして通信量を削減するキャッシュ機構などを備えており、あらゆるHTTP通信のニーズに応えることができます。

さらに、Retrofitのようなライブラリと組み合わせることで、定型的なコードを削減し、より宣言的で保守性の高いネットワーク層を構築することが可能です。この記事が、あなたのJava/Kotlinアプリケーション開発におけるネットワーク通信実装の一助となれば幸いです。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です