私たちの立場:私たちは Geonode であり、プロキシを販売しています。OkHttp のプロキシ設定は実に独特で、プロキシ認証はヘッダーではなく proxyAuthenticator を通じて行われます。これを間違えると 407 エラーが発生しますが、これは設定上の問題であるにもかかわらず、認証情報の不具合のように見えてしまいます。そのセクションは文末近くにあります。 その前の部分は、プロキシを一切使用しなくても正常に動作します。このライブラリを学習する場合は、まずはご自身の接続環境で試してみてください。
OkHttpの現在の所在
古いリンクは機能しなくなっているため、最初に明記しておく価値があります。
OkHttpのリポジトリは移動しました。github.com/square/okhttp
は現在 github.com/lysine-dev/okhttp
にリダイレクトされ、square.github.io/okhttp
にあったドキュメントサイトは404エラーを返します。現在のサイトは lysine.dev/okhttp
です。 このプロジェクトは活発にメンテナンスされており、2026年9月時点で確認された通り、Apache-2.0ライセンスが適用されています。
Mavenの座標は変更されていません。 引き続きcom.squareup.okhttp3:okhttp
として公開されています:
implementation("com.squareup.okhttp3:okhttp:5.5.0")
また、関連するアーティファクトを最新の状態に保つための部品表(BOM)もあります:
implementation(platform("com.squareup.okhttp3:okhttp-bom:5.5.0"))
要件: Android 5.0 以上(API レベル 21 以上)および Java 8 以上。OkHttp は、I/O 処理のために Okio に、また Kotlin 標準ライブラリに依存しています。これらはいずれも、プロジェクト側では「後方互換性に優れた小さなライブラリ」と説明されています。 AndroidではAndroidX Startupを使用しており、マニフェストで初期化処理を無効にする場合、アプリはApplication.onCreate
内のOkHttp.initialize(applicationContext)
を呼び出す必要があります。
旧バージョンの3.12.x
ブランチはAndroid 2.3以上およびJava 7をサポートしていますが、プロジェクト側はこれらのプラットフォームについて「TLS 1.2のサポートがなく、使用すべきではない」と明言しています。
最初のリクエスト
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
この7行の例で重要な点が3つあります。
**try
-with-resources は省略できません。** Response
は Closeable
を実装しており、閉じられていないレスポンスはその接続を保持し続けます。リークが十分に発生するとプールが枯渇し、その時点でリクエストは失敗するのではなくハングアップします。これはネットワークの問題のように見えますが、実際にはそうではありません。
**body().string()
は 1 回のみ呼び出すことができます。** このメソッドはストリームを消費します。2 回呼び出すと例外が発生し、レスポンスが閉じられた後に呼び出しても例外が発生します。ボディを 2 回以上必要とする場合は、文字列を保存してください。
** ``execute()`
は同期処理です。** 非同期処理を行う場合は、``enqueue()
を使用してください。これは ``Callback
` を受け取り、OkHttp のディスパッチャースレッドプール上で実行されます。
POST リクエストも、ボディを含む点で同様の形式になります:
public static final MediaType JSON = MediaType.get("application/json");
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
クライアントの共有
最もよく間違えられがちな設計上の判断です。
OkHttpClient
には、接続プールとスレッドプールが保持されています。リクエストごとにクライアントを作成すると、再利用できたはずの接続をすべて無駄にし、その後放棄されるスレッドを作成することになります。これは機能はしますが、処理が遅く、負荷がかかるとリソースを枯渇させてしまいます。
アプリケーション用に1つのクライアントを作成し、それを共有してください。 設計上、スレッドセーフです。
コードの一部で異なる設定が必要な場合でも、2つ目のクライアントをゼロから作成しないでください。既存のクライアントをクローンして、プールが共有されるようにします:
OkHttpClient shortTimeout = client.newBuilder()
.readTimeout(5, TimeUnit.SECONDS)
.build();
newBuilder()
は、親クライアントと接続プールおよびディスパッチャを共有するクライアントを生成します。これはまさに望ましい動作です。
シャットダウン時、特に短命なプロセスでは、リソースを明示的に解放してください:
client.dispatcher().executorService().shutdown();
client.connectionPool().evictAll();
これを怠ると、JVMが終了する前にプールのキープアライブ期間中ハングしてしまう可能性があり、CLIツールでのデバッグが困難になります。
OkHttp がリクエストに追加する内容
このライブラリはリクエストを書き換えます。何が追加されるかを理解しておくことで、ある種の混乱を防ぐことができます。
ドキュメントには次のように明記されています。「OkHttpは、元のリクエストに存在しないヘッダーを追加する場合があります。これには、Content-Length、Transfer-Encoding、User-Agent、Host、Connection、Content-Typeなどが含まれます。また、Accept-Encodingヘッダーが既に存在しない限り、透過的なレスポンス圧縮のためにこのヘッダーを追加します。Cookieがある場合、OkHttpはそれらを含むCookieヘッダーを追加します。」
これには2つの結果があります。
透過的な圧縮は自動的に行われ、自動的に解除されます。 OkHttpは圧縮を要求し、レスポンスを解凍した後、「解凍されたレスポンス本文には適用されないため、対応するレスポンスヘッダーであるContent-EncodingおよびContent-Lengthを削除します」。 したがって、レスポンスにContent-Lengthが含まれていないのはバグではなく正常な動作です。ただし、Accept-Encodingを自分で設定した場合は例外で、その場合は解凍処理はユーザー自身の責任となります。
キャッシュが有効になっている場合、条件付きリクエストは自動的に発生します。 OkHttpは、古くなったキャッシュエントリを再検証するために、If-Modified-SinceおよびIf-None-Matchを追加します。
また、デフォルトでリダイレクトを追跡し、認証チャレンジが発生した際には、「(設定されている場合)Authenticatorにチャレンジの処理を依頼し」、提供された認証情報を使用して再試行します。
このライブラリは、自らを「原則に則っており、特にバグのあるサーバーの回避、無効なシナリオのテスト、あるいは関連するRFCに矛盾するような設定を行う場合など、過度な設定を避けている」と説明しています。 また、自身の制限についても率直に明記しています。具体的には、「ボディを含む GET リクエストは許可しない」こと、そしてキャッシュは「代替実装を持つインターフェースではない」ことです。意図的に無効なリクエストを送信する必要がある場合、このライブラリは適していません。これは見落としではなく、設計上の選択です。
インターセプター
主要な拡張ポイントであり、このライブラリの使い方を決定づける機能です。
class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
ドキュメントでは、「chain.proceed(request)への呼び出しは、各インターセプターの実装において極めて重要な部分です。この一見単純に見えるメソッドこそが、すべてのHTTP処理が行われる場所です」と強調されています。 また、注意すべき警告として、「chain.proceed(request)が複数回呼び出される場合は、以前のレスポンスボディを必ず閉じなければならない」とあります。
インターセプターには2種類あり、正しく選択することが重要です。 addInterceptor() または addNetworkInterceptor() で登録します。ドキュメントにはその違いが明確に説明されています。
アプリケーション・インターセプター:
- 「リダイレクトや再試行などの中間レスポンスについて気にする必要はありません。」
- 「HTTPレスポンスがキャッシュから提供された場合でも、常に1回だけ呼び出されます。」
- 「アプリケーションの本来の意図を尊重します。
If-None-Matchのような OkHttp によって挿入されたヘッダーは考慮しません。」 - 「ショートサーキットを行い、
Chain.proceed()を呼び出さないことが許可されています。」 - 「リトライを行い、
Chain.proceed()に対して複数回の呼び出しを行うことが許可されています。」 - 「
withConnectTimeout、withReadTimeout、withWriteTimeoutを使用して、呼び出しのタイムアウトを調整できる。」
ネットワークインターセプター:
- 「リダイレクトやリトライなどの中間レスポンスに対して処理を実行できる。」
- 「ネットワーク通信をショートカットするキャッシュされたレスポンスに対しては呼び出されない。」
- 「ネットワークを介して送信されるそのままのデータを観察できます。」
- 「リクエストを伝送する
Connectionにアクセスできます。」
実用的なルール:リクエストの意図に関する処理には、アプリケーションインターセプターを使用してください — 認証ヘッダーの追加、ユーザーエージェントの設定、アプリケーションレベルのロギングなど。 実際にネットワーク上を流れるデータに関する処理には、ネットワークインターセプターを使用する — 圧縮されたボディの検査、すべてのリダイレクトホップの確認、接続の検証など。
最もよくある間違いは、認証インターセプターをネットワークインターセプターとして登録してしまうことです。これにより、リダイレクトホップごとに1回ずつ処理が実行され、意図しないホストに認証情報が漏洩する可能性があります。
タイムアウト
OkHttp には適切なデフォルト設定と 4 つの個別の設定があり、どの設定がトリガーされたかを知ることで、問題の所在を特定できます。
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.callTimeout(60, TimeUnit.SECONDS)
.build();
**connectTimeout
** は、TCP および TLS 接続の確立を対象としています。
**readTimeout
** は、レスポンス全体ではなく個々の読み取り操作の間に適用されます。そのため、速度は遅いものの着実に進行しているダウンロードでは、この設定がトリガーされることはありません。
**writeTimeout
** は、アップロードについても同様の動作をします。
**callTimeout
** は、リダイレクト、再試行、ボディ転送を含む呼び出し全体に制限を設けます。デフォルトでは 0 であり、全体的な制限がないことを意味します。
設定すべきは最後の設定です。 これを設定しないと、データが少しずつしか送信されない呼び出しは、受信するバイトごとに読み取りタイムアウトがリセットされるため、無限に実行され続ける可能性があります。callTimeout
はジョブを終了させる上限であり、curlの--max-time
に相当します。この違いについては、curlでのタイムアウト設定で解説しました。
呼び出しごとのオーバーライドは、アプリケーションインターセプタのwithReadTimeout
や関連メソッドを通じて利用可能です。これにより、すべてのデフォルト設定を緩めることなく、特定の処理に時間がかかるエンドポイントに対してのみ余裕を持たせることができます。
プロキシ
OkHttpが他のほとんどのクライアントと異なる点であり、多くの人がここで時間を浪費してしまう部分です。
Proxy proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress("proxy.example.com", 9000));
OkHttpClient client = new OkHttpClient.Builder()
.proxy(proxy)
.build();
SOCKSの場合は、同じ形式で Proxy.Type.SOCKS を使用してください。
認証に関しては、多くの人が意外に思う点があります。 Proxy-Authorization ヘッダーを設定する必要はありません。 代わりに、proxyAuthenticator を指定します。これは、プロキシから 407 チャレンジが発行された際に OkHttp が呼び出すものです:
Authenticator proxyAuth = (route, response) -> {
if (response.request().header("Proxy-Authorization") != null) {
return null; // already tried these credentials; give up
}
String credential = Credentials.basic("user", "pass");
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
};
OkHttpClient client = new OkHttpClient.Builder()
.proxy(proxy)
.proxyAuthenticator(proxyAuth)
.build();
null を返すことは不可欠です。これがないと、パスワードが間違っていた場合に失敗ではなく無限の再試行ループに陥ってしまいます。つまり、OkHttp は認証処理を呼び出し、同じ誤った認証情報を取得し、再び 407 を受け取り、さらに呼び出しを繰り返すことになります。 このループを断ち切るには、すでに Proxy-Authorization ヘッダーを送信したかどうかを確認する必要があります。
さらに3つの注意点があります。
407は401とは異なります。 407はプロキシがアクセスを拒否し、ターゲットに到達しなかったことを意味します。一方、401はプロキシが正常に機能し、ターゲット側が認証情報を要求していることを意味します。これは、authenticator()の設定としては全く異なるものです。
proxySelector() を使用すると、クライアントごとではなくリクエストごとにプロキシを選択できます。これにより、複数のクライアントを作成することなく、ホストごとに異なるルーティングを行うことが可能です。
設定が反映されたことを確認してください。 プロキシを設定した場合と設定していない場合の両方で、自分のアドレスを返すサービスにリクエストを送信してみてください。 ここでの設定ミスは「サイレント」に進行します――リクエストは成功し、直接送信されてしまいます――そのため、送信元アドレスを確認することが確実性を確保する唯一の方法です。この「サイレント成功」のパターンについては、プロキシのテストが重要な理由で解説しました。
レスポンスの適切な処理
コードが単に動作するだけか、負荷がかかった状況でも動作するかを分ける、いくつかのパターン。
**例外が発生していないかだけでなく、isSuccessful()
も確認しましょう。** OkHttp は、ネットワーク障害に対して IOException
をスローしますが、HTTP ステータスコードの異常に対してはスローしません。404 や 500 は、通常の Response
として返されます:
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
String body = response.body() != null ? response.body().string() : "";
throw new IOException("HTTP " + response.code() + " from " + request.url()
+ ": " + body.substring(0, Math.min(200, body.length())));
}
return response.body().string();
}
エラー本文の最初の部分を含めることで、意味不明なステータスコードが問題点を明示するメッセージに変わります。ほとんどのAPIは、400ステータスの本文でその内容を説明しています。
大きなレスポンスをStringにバッファリングしないでください。 ``body().string()`
` はすべてをメモリに読み込みます。大容量のダウンロードの場合は、ストリーム処理を行ってください:
try (Response response = client.newCall(request).execute();
BufferedSource source = response.body().source();
BufferedSink sink = Okio.buffer(Okio.sink(new File("out.bin")))) {
sink.writeAll(source);
}
非同期呼び出しでもボディのクローズは必須であり、コールバックはバックグラウンドスレッドで実行されます:
client.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
logger.warn("request failed", e);
}
@Override public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody body = response.body()) {
handle(body.string());
}
}
});
onFailure
はネットワークの問題でのみ発生することに注意してください。HTTP 500エラーはonResponse
で処理されますが、この名称が文字通りの意味だと期待している人にとっては意外な結果となるでしょう。
再試行には注意が必要です。 OkHttp は、デフォルトで有効になっている retryOnConnectionFailure()
によって制御され、一部の接続レベルの障害に対して自動的に再試行を行います。ただし、HTTP エラーステータスに対しては再試行を行いませんし、行うべきでもありません。成功していた可能性のある POST リクエストを再試行すると、書き込みが重複してしまう恐れがあるからです。 独自の再試行ロジックを追加する場合は、アプリケーションインターセプター内で実装し、試行回数に上限を設け、試行の間に待機時間を設け、APIが冪等性キーを提供していない限り、冪等なメソッドに限定してください。
その他のメリット
簡単に言えば、これらがこの技術を選ぶ理由です。
HTTP/2。「サポートされている場合、同じホストへのすべてのリクエストが1つのソケットを共有できる」という特徴があります。 接続プール。「HTTP/2が利用できない場合、リクエストの遅延を低減」します。透過的なGZIP圧縮。レスポンスキャッシュ。「繰り返しリクエストにおいて、ネットワーク通信を完全に回避」します。
耐障害性。「一般的な接続の問題からシームレスに回復する」ほか、サービスに複数のアドレスがある場合、「最初の接続に失敗した場合は代替アドレスを試行する」— プロジェクト側では、「これは IPv4+IPv6 および冗長化されたデータセンターでホストされるサービスにとって必要不可欠である」と指摘している。
最新の TLS。TLS 1.3、ALPN、証明書ピンニングを含み、プラットフォームの実装を利用します。JVM 上では、BoringSSL を Java と統合する Conscrypt もサポートしており、これが最初のセキュリティプロバイダーである場合は自動的に使用されます。
標準への準拠。 プロジェクトでは、準拠している仕様を以下のように列挙している:HTTPセマンティクスに関するRFC 9110、キャッシュに関するRFC 9111、HTTP/1.1に関するRFC 9112、 HTTP/2に関するRFC 9113、WebSocketsに関するRFC 6455、およびサーバー送信イベントに関するWHATWG仕様です。仕様が曖昧な場合は、「一般的なブラウザや一般的なHTTPライブラリなどの最新のユーザーエージェント」に準拠しています。
よくある質問
OkHttp は現在もメンテナンスされていますか?
はい。リポジトリは square/okhttp から lysine-dev/okhttp へ移行し、ドキュメントサイトは lysine.dev/okhttp になりましたが、プロジェクトは活発に開発されており、Apache-2.0 ライセンスが適用されています。Maven の座標は引き続き com.squareup.okhttp3:okhttp です。
リクエストごとに新しい OkHttpClient を作成すべきですか?
いいえ。このクライアントは接続プールとスレッドプールを保持しており、設計上スレッドセーフです。アプリケーション用に 1 つ作成し、それを共有してください。異なる設定が必要な場合は、既存のクライアントに対して newBuilder() を実行することで、プールを共有できます。
なぜレスポンスを閉じなければならないのですか?
Responseは閉じられるまで接続を維持するためです。レスポンスがリークされると、接続プールが枯渇してしまいます。その症状として、リクエストが失敗するのではなくハングアップしてしまうことがあり、診断が困難になります。常に try-with-resources を使用してください。
アプリケーションインターセプターとネットワークインターセプターの違いは何ですか?
アプリケーション・インターセプターは元のリクエストを処理し、キャッシュされたレスポンスの場合でも正確に1回だけ呼び出され、処理を早期に終了させたり再試行を行ったりすることがあります。一方、ネットワーク・インターセプターは、リダイレクトや再試行を含む個々のネットワーク通信をすべて処理し、接続にアクセスでき、キャッシュされたレスポンスの場合は完全にスキップされます。
OkHttpでプロキシを設定するにはどうすればよいですか?
OkHttpClient.Builder.proxy() に java.net.Proxy を渡します。認証については、ヘッダーではなく proxyAuthenticator を設定してください。また、Proxy-Authorization ヘッダーがすでに存在する場合、または誤った認証情報によって無限のリトライループが発生する場合は、null を返してください。
タイムアウトはどのように設定すべきですか?
connectTimeout を約 10 秒、readTimeout および writeTimeout を約 30 秒に設定してください。そして、最も重要なのは callTimeout です。これはデフォルトで 0 に設定されており、呼び出し全体を制限する唯一の設定です。これを設定しないと、レスポンスの送信が徐々にしか行われない場合、読み取りタイムアウトが 1 バイトごとにリセットされるため、タイムアウトが発生しません。
OkHttpはGZIPを自動的に処理しますか?
はい、Accept-Encodingを手動で設定しない限りは処理します。OkHttpは圧縮をリクエストし、レスポンスを解凍し、解凍後のボディを記述しなくなったContent-EncodingおよびContent-Lengthを削除します。ヘッダーを手動で設定すると、解凍処理も自身で行うことになります。
OkHttpにはどのJavaバージョンが必要ですか?
Java 8以降、およびAndroid 5.0(APIレベル21)以降が必要です。OkioおよびKotlin標準ライブラリに依存しています。旧バージョンの3.12.xブランチはJava 7およびAndroid 2.3に対応していますが、TLS 1.2をサポートしていないため、使用しないでください。
まとめ
OkHttpは、綿密に設計された実装を基盤とするコンパクトなAPIであり、これを正しく使用するためのポイントは主に2つあります。アプリケーション全体で1つのクライアントを共有すること、そしてすべてのレスポンスをtry-with-resourcesで閉じることです。どちらの失敗も目立ったエラーメッセージは表示されず、最終的には読み取れるエラーではなく、リクエストがハングアップするという形で現れます。
インターセプターは開発に多くの時間を費やす部分であり、「アプリケーション」と「ネットワーク」の区別については、試行錯誤で学ぶのではなく、正しく理解しておく価値があります。アプリケーション・インターセプターはユーザーの意図を認識して一度だけ実行されますが、ネットワーク・インターセプターはすべてのホップを監視し、キャッシュされたレスポンスの場合はスキップされます。 ネットワーク層で認証インターセプターを登録するのは典型的なミスであり、リダイレクトのたびに発火してしまいます。
callTimeout を設定してください。デフォルトは 0 ですが、これは呼び出し全体を制限する唯一の設定であり、これが設定されていないために、本来数秒で完了するはずの処理が、他の何かによって強制終了されるまで実行され続けることがあります。
また、古いドキュメントを参照している場合は、リンクを確認してください。プロジェクトは lysine.dev/okhttp に移行しており、Squareがホストしていたサイトは廃止されました。ただし、アーティファクトの座標は変更されていないため、ビルドファイルの変更は必要ありません。
