нейпрослоп картина, а статья нет
Обзорная статья для начинающих сетевиков, чей предыдущий опыт с OkHttp ограничивался:
okHttp.newCall(request).execute()
Балуемся с OkHttp и исследуем его возможности🍿🍦🎪🎈
База
Пробежимся по стандартным настройкам, никаких хаков
Прокси
Можем указать кастомный IP, куда польется трафик
val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress("192.168.1.1", 8080))val client = OkHttpClient.Builder().proxy(proxy).build()
Можно указать как HTTP, так и SOCKS прокси
По умолчанию берется прокси IP, установленное в системных настройках
⁉️ Вопрос на засыпку: если на девайсе подключен VPN (корпоративный, конечно), то на какой IP пойдет трафик?
Принудительный выбор сети — Wi-Fi vs Cellular
Если у юзера включен и мобильный интернет, и Wi-Fi, через что пойдет трафик?
Оказывается, можно указать самому:
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManagerval mobileNetworkRequest = NetworkRequest.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .build()val networkCallback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { val client = OkHttpClient.Builder() // создаем OkHttp на мобильном интернете .socketFactory(network.socketFactory) .build() }}connectivityManager.requestNetwork(mobileNetworkRequest, networkCallback)
⁉️ Аккуратно: пользователи могут не обрадоваться расходу мобильного интернета
Кастомный DNS-сервер
У гугл DNS опять устарели сервера и перестали отвечать? Давайте напишем свой локальный DNS-резолвер:
val client = OkHttpClient.Builder() .dns(object : Dns { override fun lookup(hostname: String): List { return try { when (hostname) { "api.example.com" -> listOf(InetAddress.getByName("192.168.1.100")) "cdn.example.com" -> listOf(InetAddress.getByName("10.0.0.50")) else -> Dns.SYSTEM.lookup(hostname) // fallback на системный DNS } } catch (e: Exception) { Dns.SYSTEM.lookup(hostname) } } }) .build()
⁉️ Аккуратно: локально зашитые IP имеют свойство устаревать, нужно не забывать о механизме ротации
Мясо и треш
Не повторяйте ничего из этого в продакшен коде (без присмотра взрослых / без фича флага на откат)
Подмена сетевого слоя
В OkHttp запрос выполняется внутри стандартных OkHttp Interceptor-ов:
-
ConnectInterceptor — резолвинг DNS, установка TCP/TLS соединения
-
CallServerInterceptor — отправляем и принимает хэдеры/body
-
CacheInterceptor — чтение из кэша
-
RetryAndFollowUpInterceptor — обрабатывает редиректы и ретраи
-
BridgeInterceptor — читает cookie из CookieJar
RealCall
Если вам вдруг понадобится выполнять запрос через другой сетевой движок (например, Cronet), можете выкинуть эти стандартные интерсепторы из цепочки и подсунуть свои
Красивый вариант — форкнуть OkHttp и заменить их в RealCall
Дешевый вариант без форка — подсунуть свои интерсепторы «перед» интерсепторами OkHttp, чтобы стандартные перестали вызываться, и оборвать в нем цепочку:
class CustomInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() // Своя логика: DNS, соединение, HTTP и т. д. val response = executeCustomRequest(request) // Не вызываем chain.proceed() // дальнейшие интерсепторы не вызываются return response }}val client = OkHttpClient.Builder() .addInterceptor(CustomInterceptor()) // обрывает цепочку и сам выполняет запрос// дальнейшие CallServerInterceptor / ConnectInterceptor / CacheInterceptor / RetryAndFollowUpInterceptor не вызываются .build()
Независимый запрос внутри Interceptor
Внутри любого интерсептора можно выполнить другой сетевой запрос:
class MyInterceptor( private val client: OkHttpClient) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { ... val response = client.newCall(request).execute() val result = response.use { it.body.string() } return ... }}
⁉️ Вопрос на засыпку: если выполнить запрос внутри запроса, то какая цепочка интерсепторов выполнится в итоге?
Ретрай
Хотите написать свою retry policy?
Можно ретраить запрос внутри Interceptor с помощью повторного вызова chain.proceed(…)
class RetryInterceptor( private val maxRetries: Int = 3) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() var response: Response? = null repeat(maxRetries + 1) { attempt -> response?.close() response = chain.proceed(request) if (response!!.isSuccessful || attempt == maxRetries) return response!! } return response!! }}
⁉️ Вопрос на засыпку: какие интерсепторы выполнятся, если поставить этот интерсептор в AppInterceptor‑ы? а если в NetworkInterceptor‑ы?
⁉️ Аккуратно: у OkHttp уже есть свои ретраи, включенные по умолчанию. Не переборщите
Приостановка запроса
OkHttp.Interceptor-ы не умеет в корутины, но саспендить то хочется
Например, чтобы спросить у пользователя разрешение или показать всплывашку перед произвольным запросом
Можно сделать так:
class MyInterceptor( private val client: OkHttpClient) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { runBlocking { // делаем грязь // например, просим пользователя пройти авторизацию } return chain.proceed(chain.request()) }}
⁉️ Аккуратно: это блокирует поток, на котором выполняется запрос
Отключение таймаута
Знаете, что мы не учли в предыдущих разделах?
На весь запрос установлен таймаут. По умолчанию примерно 30 секунд (connectTimeout + readTimeout + writeTimeout)
И пока мы развлекаемся (ретраим или блочим запрос), часики тикают
Но это можно исправить, давайте убьем таймер:
fun Interceptor.Chain.disableTimer() = (call().timeout() as? AsyncTimeout)?.exit()
Кастомная валидация SSL-сертификатов
Через кастомный X509TrustManager можно разрешать только определенные SSL‑сертификаты (SSL‑pinning) или запрещать определенные серты по публичному ключу (например, слитые недавно eMudhra)
class CustomTrustManager( private val blockedHash: String) : X509TrustManager { override fun checkServerTrusted( chain: Array, authType: String ) { ... val hash = MessageDigest.getInstance("SHA-256") .digest(chain[0].encoded) .joinToString("") { "%02x".format(it) } if (hash == blockedHash) throw CertificateException("Certificate blocked") ... }}
⁉️ Аккуратно: важно не разрешить лишние SSL‑серты на продакшен сборках, иначе ИБ заставят переделывать. Также бывает важно проверять всю цепочку сертификатов, а не только leaf
Выводы
OkHttp отлично закрывает базовые потребности
Но под конец 2k26 по‑прежнему:
-
не поддерживает http3
-
не дает гибкого коробочного механизма настройки retry policy
-
не поддерживает корутины / suspend в интерсепторах
-
имеет неочевидное разделение на AppInterceptor‑ы (обычные) и NetworkInterceptor‑ы
-
не имеет явного разделения на Request и Response интерсепторы (в отличие от Ktor и других движков)
Надо активнее вайбкодить фичи, коллеги
Мой блог с остальными постами по теме: https://t.me/dolgo_polo_dev
Источник: habr.com