The Jerusalem PostTo understand Auschwitz, EJA first brings Europe's leaders back to Kristallnacht - interviewDaily MaverickRENEWABLES CRISIS : Pele’s governance test: Why a private boardroom matters to SA’s energy transitionZDF heuteEntdecken Sie das ZDF-NachrichtenstudioComplete SportsAmapakabo Worried By Abia Warriors Wastefulness In Games, Relying On A Single Player For GoalsRolling StoneIs This Pete Hegseth’s Last Stand?SudinfoQue se passe-t-il à la Maison-Blanche ? Les services de sécurité interviennent après une incident, le bâtiment placé en confinement !ESPNNFL Week 10 betting odds for every gameVanguardADP denies third-term agenda by Yabagi SaniNotJustOkSecret Lyrics by Veeiye & TelzWirtualna PolskaMarsz ciszy w Krakowie. Będą prezydent i premierABC NewsDOJ leadership shakeup continues as Civil Division chief steps downBillboardPaul Anka, Stevie Wonder, Cher & More Kennedy Center Honorees Who Were Teenagers When They First Hit the Billboard Hot 100 (Full List)
The Daily Newsstand · Free, Always
Monday, November 9, 2026

Экстримальное использование OkHttp

Translate
нейпрослоп картина, а статья нет

нейпрослоп картина, а статья нет

Обзорная статья для начинающих сетевиков, чей предыдущий опыт с 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 ConnectivityManager

val 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<InetAddress> {
            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

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<X509Certificate>,
        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

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.