OkHttp
Kotlin/Java向けの高性能HTTPクライアント。Square社開発でAndroid公式推奨。HTTP/2サポート、接続プーリング、GZIP圧縮、レスポンスキャッシング、ネットワーク障害からの自動復旧機能を内蔵。Kotlinコルーチンと相性が良く、現代的な非同期処理に最適化。
概要
OkHttpは、Square社が開発した効率的なHTTPクライアントライブラリです。元々はJava向けに作られましたが、現在ではKotlinファーストの設計となっており、モダンなHTTP通信の実装に必要な機能を網羅しています。
主な特徴
- 接続プーリング: HTTP/2とHTTP/1.1の接続を効率的に再利用
- 透過的なGZIP圧縮: 帯域幅を削減してレスポンスを高速化
- レスポンスキャッシング: 不要なネットワークリクエストを削減
- WebSocketサポート: リアルタイム通信をネイティブサポート
- インターセプター: リクエスト/レスポンスの監視と変更
- 自動リトライとリダイレクト: 堅牢な通信の実現
- Kotlinファースト: OkHttp 5.0以降、Kotlin向けの最適化
インストール
Gradle (Kotlin DSL)
dependencies {
// OkHttp 5.0 (最新版)
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.12")
// ロギングインターセプター
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12")
// MockWebServer (テスト用)
testImplementation("com.squareup.okhttp3:mockwebserver:5.0.0-alpha.12")
}
Maven
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>5.0.0-alpha.12</version>
</dependency>
基本的な使い方
クライアントの作成
// デフォルト設定のクライアント
val client = OkHttpClient()
// カスタマイズされたクライアント
val customClient = OkHttpClient.Builder()
.connectTimeout(Duration.ofSeconds(10))
.readTimeout(Duration.ofSeconds(30))
.writeTimeout(Duration.ofSeconds(15))
.build()
シンプルなリクエスト
// OkHttp 5.0の新しいKotlin向けAPI
val request = Request(
url = "https://api.example.com/data".toHttpUrl()
)
// 従来のビルダーパターン
val requestBuilder = Request.Builder()
.url("https://api.example.com/data")
.build()
実装例
1. GETリクエスト
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import java.io.IOException
class ApiClient {
private val client = OkHttpClient()
// 同期的なGETリクエスト
fun getSync(url: String): String? {
val request = Request(url = url.toHttpUrl())
return try {
client.newCall(request).execute().use { response ->
if (response.isSuccessful) {
response.body?.string()
} else {
throw IOException("Unexpected code $response")
}
}
} catch (e: IOException) {
e.printStackTrace()
null
}
}
// 非同期GETリクエスト
fun getAsync(url: String, callback: (String?) -> Unit) {
val request = Request(url = url.toHttpUrl())
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
callback(null)
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (response.isSuccessful) {
callback(response.body?.string())
} else {
callback(null)
}
}
}
})
}
}
2. POSTリクエスト
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.FormBody
import okhttp3.MultipartBody
class PostRequests {
private val client = OkHttpClient()
// JSON POSTリクエスト
fun postJson(url: String, json: String): Response {
val mediaType = "application/json; charset=utf-8".toMediaType()
val body = json.toRequestBody(mediaType)
val request = Request.Builder()
.url(url)
.post(body)
.build()
return client.newCall(request).execute()
}
// フォームデータの送信
fun postForm(url: String, params: Map<String, String>): Response {
val formBody = FormBody.Builder().apply {
params.forEach { (key, value) ->
add(key, value)
}
}.build()
val request = Request.Builder()
.url(url)
.post(formBody)
.build()
return client.newCall(request).execute()
}
// マルチパートリクエスト
fun postMultipart(url: String, filePath: String, description: String): Response {
val file = File(filePath)
val fileBody = file.asRequestBody("image/jpeg".toMediaType())
val requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", file.name, fileBody)
.addFormDataPart("description", description)
.build()
val request = Request.Builder()
.url(url)
.post(requestBody)
.build()
return client.newCall(request).execute()
}
}
3. インターセプターの実装
import okhttp3.Interceptor
import okhttp3.logging.HttpLoggingInterceptor
class InterceptorExamples {
// 認証インターセプター
class AuthInterceptor(private val token: String) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val newRequest = originalRequest.newBuilder()
.header("Authorization", "Bearer $token")
.build()
return chain.proceed(newRequest)
}
}
// リトライインターセプター
class RetryInterceptor(private val maxRetries: Int = 3) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
var response: Response? = null
var exception: IOException? = null
for (i in 0 until maxRetries) {
try {
response = chain.proceed(request)
if (response.isSuccessful) return response
response.close()
} catch (e: IOException) {
exception = e
}
// 待機時間を設定(指数バックオフ)
Thread.sleep((2.0.pow(i) * 1000).toLong())
}
throw exception ?: IOException("Max retries exceeded")
}
}
// クライアントへの追加
fun createClient(token: String): OkHttpClient {
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
return OkHttpClient.Builder()
.addInterceptor(AuthInterceptor(token))
.addInterceptor(RetryInterceptor())
.addInterceptor(loggingInterceptor)
.build()
}
}
4. WebSocketの実装
import okhttp3.WebSocket
import okhttp3.WebSocketListener
class WebSocketExample {
private val client = OkHttpClient.Builder()
.readTimeout(0, TimeUnit.MILLISECONDS)
.build()
fun connectWebSocket(url: String) {
val request = Request.Builder()
.url(url)
.build()
val listener = object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
println("WebSocket接続が開かれました")
// メッセージを送信
webSocket.send("Hello Server!")
}
override fun onMessage(webSocket: WebSocket, text: String) {
println("受信したメッセージ: $text")
}
override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
println("受信したバイナリ: ${bytes.hex()}")
}
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
println("WebSocketが閉じられます: $code / $reason")
webSocket.close(1000, null)
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
println("WebSocketエラー: ${t.message}")
}
}
client.newWebSocket(request, listener)
}
}
5. 接続プーリングとキャッシング
import okhttp3.Cache
import okhttp3.ConnectionPool
import okhttp3.CacheControl
import java.util.concurrent.TimeUnit
class PerformanceOptimization {
// カスタム接続プールの設定
private val connectionPool = ConnectionPool(
maxIdleConnections = 5,
keepAliveDuration = 5,
timeUnit = TimeUnit.MINUTES
)
// キャッシュの設定(10MB)
private val cacheDir = File(System.getProperty("java.io.tmpdir"), "okhttp-cache")
private val cache = Cache(cacheDir, 10 * 1024 * 1024) // 10MB
val optimizedClient = OkHttpClient.Builder()
.connectionPool(connectionPool)
.cache(cache)
.build()
// キャッシュ制御の例
fun requestWithCacheControl(url: String) {
// キャッシュを強制的に使用
val forceCache = Request.Builder()
.url(url)
.cacheControl(CacheControl.FORCE_CACHE)
.build()
// キャッシュを無視
val noCache = Request.Builder()
.url(url)
.cacheControl(CacheControl.FORCE_NETWORK)
.build()
// 最大経過時間を指定
val maxAge = Request.Builder()
.url(url)
.cacheControl(
CacheControl.Builder()
.maxAge(10, TimeUnit.MINUTES)
.build()
)
.build()
}
}
6. ファイルのダウンロードと進捗管理
import okhttp3.ResponseBody
import java.io.FileOutputStream
class FileDownloader {
private val client = OkHttpClient()
fun downloadFile(
url: String,
destinationFile: File,
progressListener: (bytesRead: Long, contentLength: Long) -> Unit
) {
val request = Request.Builder()
.url(url)
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val body = response.body ?: throw IOException("Response body is null")
val contentLength = body.contentLength()
body.source().use { source ->
FileOutputStream(destinationFile).use { output ->
val buffer = ByteArray(8192)
var totalBytesRead = 0L
var bytesRead: Int
while (source.read(buffer).also { bytesRead = it } != -1) {
output.write(buffer, 0, bytesRead)
totalBytesRead += bytesRead
progressListener(totalBytesRead, contentLength)
}
}
}
}
}
}
7. コルーチンとの統合
import kotlinx.coroutines.*
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
// OkHttpをコルーチンで使用するための拡張関数
suspend fun Call.await(): Response = suspendCancellableCoroutine { continuation ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
continuation.resume(response)
}
override fun onFailure(call: Call, e: IOException) {
continuation.resumeWithException(e)
}
})
continuation.invokeOnCancellation {
cancel()
}
}
// 使用例
class CoroutineApiClient {
private val client = OkHttpClient()
suspend fun getData(url: String): String = withContext(Dispatchers.IO) {
val request = Request(url = url.toHttpUrl())
client.newCall(request).await().use { response ->
if (response.isSuccessful) {
response.body?.string() ?: throw IOException("Empty response body")
} else {
throw IOException("Unexpected code $response")
}
}
}
}
他のライブラリとの比較
OkHttp vs Retrofit
- OkHttp: 低レベルHTTPクライアント、細かい制御が可能
- Retrofit: OkHttpをベースにした高レベルRESTクライアント
OkHttp vs Ktor Client
- OkHttp: 成熟したライブラリ、Android標準
- Ktor Client: Kotlin専用、マルチプラットフォーム対応
OkHttp vs Fuel
- OkHttp: より多機能、エンタープライズ向け
- Fuel: シンプルなAPI、小規模プロジェクト向け
ベストプラクティス
-
クライアントの再利用
// シングルトンパターンの実装 object HttpClient { val instance: OkHttpClient by lazy { OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .build() } } -
適切なタイムアウトの設定
val client = OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(15, TimeUnit.SECONDS) .build() -
エラーハンドリングの実装
sealed class NetworkResult<T> { data class Success<T>(val data: T) : NetworkResult<T>() data class Error<T>(val exception: Exception) : NetworkResult<T>() } suspend fun <T> safeApiCall( apiCall: suspend () -> T ): NetworkResult<T> { return try { NetworkResult.Success(apiCall()) } catch (e: Exception) { NetworkResult.Error(e) } }
まとめ
OkHttpは、Kotlinアプリケーションにおける強力で効率的なHTTPクライアントライブラリです。接続プーリング、キャッシング、WebSocketサポートなどの高度な機能により、あらゆる規模のアプリケーションに対応できます。特にAndroid開発では事実上の標準となっており、Retrofitなどの人気ライブラリの基盤としても使われています。