From 4a425f7397eda82fd465fdd08b718bba799c2cd0 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 5 Oct 2025 10:05:57 +0100 Subject: [PATCH 01/21] Allow OkHttpClient overrides in Interceptor --- .../main/kotlin/okhttp3/TestValueFactory.kt | 1 + .../kotlin/okhttp3/Interceptor.kt | 5 ++ .../kotlin/okhttp3/OkHttpClient.kt | 1 + .../okhttp3/internal/connection/RealCall.kt | 3 +- .../internal/http/RealInterceptorChain.kt | 65 ++++++++++++++++++- .../kotlin/okhttp3/KotlinSourceModernTest.kt | 4 ++ .../internal/connection/RouteSelectorTest.kt | 1 + 7 files changed, 78 insertions(+), 2 deletions(-) diff --git a/okhttp-testing-support/src/main/kotlin/okhttp3/TestValueFactory.kt b/okhttp-testing-support/src/main/kotlin/okhttp3/TestValueFactory.kt index 6d2e4c9e1209..babbc25a4310 100644 --- a/okhttp-testing-support/src/main/kotlin/okhttp3/TestValueFactory.kt +++ b/okhttp-testing-support/src/main/kotlin/okhttp3/TestValueFactory.kt @@ -197,6 +197,7 @@ class TestValueFactory : Closeable { connectTimeoutMillis = 10_000, readTimeoutMillis = 10_000, writeTimeoutMillis = 10_000, + clientOverrides = null, ) fun newRoutePlanner( diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt index bfa1f582326c..6010f9122168 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt @@ -17,6 +17,7 @@ package okhttp3 import java.io.IOException import java.util.concurrent.TimeUnit +import javax.net.SocketFactory /** * Observes, modifies, and potentially short-circuits requests going out and the corresponding @@ -108,5 +109,9 @@ fun interface Interceptor { timeout: Int, unit: TimeUnit, ): Chain + + fun withDns(dns: Dns?): Chain + + fun withSocketFactory(socketFactory: SocketFactory?): Chain } } diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/OkHttpClient.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/OkHttpClient.kt index de3e75d5e701..f2f27d6a3555 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/OkHttpClient.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/OkHttpClient.kt @@ -312,6 +312,7 @@ open class OkHttpClient internal constructor( * Creates an [Address] of out of the provided [HttpUrl] * that uses this client’s DNS, TLS, and proxy configuration. */ + @Deprecated("Not intended to be public API on OkHttpClient") fun address(url: HttpUrl): Address { var useSslSocketFactory: SSLSocketFactory? = null var useHostnameVerifier: HostnameVerifier? = null diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt index 7d6f9755a5fa..9edcfc9e89a9 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt @@ -201,6 +201,7 @@ class RealCall( connectTimeoutMillis = client.connectTimeoutMillis, readTimeoutMillis = client.readTimeoutMillis, writeTimeoutMillis = client.writeTimeoutMillis, + clientOverrides = null, ) var calledNoMoreExchanges = false @@ -256,7 +257,7 @@ class RealCall( pingIntervalMillis = client.pingIntervalMillis, retryOnConnectionFailure = client.retryOnConnectionFailure, fastFallback = client.fastFallback, - address = client.address(request.url), + address = chain.address(request.url), connectionUser = CallConnectionUser(this, connectionPool.connectionListener, chain), routeDatabase = client.routeDatabase, ) diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt index e0a294271ddb..97ca10f15b98 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt @@ -17,8 +17,15 @@ package okhttp3.internal.http import java.io.IOException import java.util.concurrent.TimeUnit +import javax.net.SocketFactory +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.SSLSocketFactory +import okhttp3.Address import okhttp3.Call +import okhttp3.CertificatePinner import okhttp3.Connection +import okhttp3.Dns +import okhttp3.HttpUrl import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response @@ -33,7 +40,7 @@ import okhttp3.internal.connection.RealCall * If the chain is for an application interceptor then [exchange] must be null. Otherwise it is for * a network interceptor and [exchange] must be non-null. */ -class RealInterceptorChain( +class RealInterceptorChain constructor( internal val call: RealCall, private val interceptors: List, private val index: Int, @@ -42,6 +49,7 @@ class RealInterceptorChain( internal val connectTimeoutMillis: Int, internal val readTimeoutMillis: Int, internal val writeTimeoutMillis: Int, + internal val clientOverrides: ClientOverrides? ) : Interceptor.Chain { private var calls: Int = 0 @@ -52,6 +60,7 @@ class RealInterceptorChain( connectTimeoutMillis: Int = this.connectTimeoutMillis, readTimeoutMillis: Int = this.readTimeoutMillis, writeTimeoutMillis: Int = this.writeTimeoutMillis, + clientOverrides: ClientOverrides? = this.clientOverrides, ) = RealInterceptorChain( call, interceptors, @@ -61,6 +70,7 @@ class RealInterceptorChain( connectTimeoutMillis, readTimeoutMillis, writeTimeoutMillis, + clientOverrides, ) override fun connection(): Connection? = exchange?.connection @@ -98,6 +108,24 @@ class RealInterceptorChain( return copy(writeTimeoutMillis = checkDuration("writeTimeout", timeout.toLong(), unit)) } + override fun withDns(dns: Dns?): Interceptor.Chain { + return withClientOverrides { + copy(dns = dns) + } + } + + override fun withSocketFactory(socketFactory: SocketFactory?): Interceptor.Chain { + return withClientOverrides { + copy(socketFactory = socketFactory) + } + } + + private fun withClientOverrides(overrides: ClientOverrides.() -> ClientOverrides): Interceptor.Chain { + check(exchange == null) { "ClientOverrides can't be adjusted in a network interceptor" } + + return copy(clientOverrides = clientOverrides) + } + override fun call(): Call = call override fun request(): Request = request @@ -135,4 +163,39 @@ class RealInterceptorChain( return response } + + /** + * Creates an [Address] of out of the provided [HttpUrl] + * that uses this client’s DNS, TLS, and proxy configuration. + */ + fun address(url: HttpUrl): Address { + var useSslSocketFactory: SSLSocketFactory? = null + var useHostnameVerifier: HostnameVerifier? = null + var useCertificatePinner: CertificatePinner? = null + if (url.isHttps) { + useSslSocketFactory = call.client.sslSocketFactory + useHostnameVerifier = call.client.hostnameVerifier + useCertificatePinner = call.client.certificatePinner + } + + return Address( + uriHost = url.host, + uriPort = url.port, + dns = clientOverrides?.dns ?: call.client.dns, + socketFactory = clientOverrides?.socketFactory ?: call.client.socketFactory, + sslSocketFactory = useSslSocketFactory, + hostnameVerifier = useHostnameVerifier, + certificatePinner = useCertificatePinner, + proxyAuthenticator = call.client.proxyAuthenticator, + proxy = call.client.proxy, + protocols = call.client.protocols, + connectionSpecs = call.client.connectionSpecs, + proxySelector = call.client.proxySelector, + ) + } + + data class ClientOverrides( + val dns: Dns? = null, + val socketFactory: SocketFactory? = null, + ) } diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt index decada189402..f381cb3d8d65 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt @@ -1344,5 +1344,9 @@ class KotlinSourceModernTest { timeout: Int, unit: TimeUnit, ): Interceptor.Chain = TODO() + + override fun withDns(dns: Dns?): Interceptor.Chain = TODO() + + override fun withSocketFactory(socketFactory: SocketFactory?): Interceptor.Chain = TODO() } } diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/internal/connection/RouteSelectorTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/internal/connection/RouteSelectorTest.kt index d61136b646d2..524682c37b13 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/internal/connection/RouteSelectorTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/internal/connection/RouteSelectorTest.kt @@ -567,6 +567,7 @@ class RouteSelectorTest { connectTimeoutMillis = 10_000, readTimeoutMillis = 10_000, writeTimeoutMillis = 10_000, + clientOverrides = null, ) companion object { From 2dfddae5d04fc4b54f55d2c7811e66e4e9529dbe Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 5 Oct 2025 10:18:34 +0100 Subject: [PATCH 02/21] cleanup --- .../okhttp3/internal/http/RealInterceptorChain.kt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt index 97ca10f15b98..7a018ee15064 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt @@ -49,7 +49,7 @@ class RealInterceptorChain constructor( internal val connectTimeoutMillis: Int, internal val readTimeoutMillis: Int, internal val writeTimeoutMillis: Int, - internal val clientOverrides: ClientOverrides? + internal val clientOverrides: ClientOverrides?, ) : Interceptor.Chain { private var calls: Int = 0 @@ -108,17 +108,15 @@ class RealInterceptorChain constructor( return copy(writeTimeoutMillis = checkDuration("writeTimeout", timeout.toLong(), unit)) } - override fun withDns(dns: Dns?): Interceptor.Chain { - return withClientOverrides { + override fun withDns(dns: Dns?): Interceptor.Chain = + withClientOverrides { copy(dns = dns) } - } - override fun withSocketFactory(socketFactory: SocketFactory?): Interceptor.Chain { - return withClientOverrides { + override fun withSocketFactory(socketFactory: SocketFactory?): Interceptor.Chain = + withClientOverrides { copy(socketFactory = socketFactory) } - } private fun withClientOverrides(overrides: ClientOverrides.() -> ClientOverrides): Interceptor.Chain { check(exchange == null) { "ClientOverrides can't be adjusted in a network interceptor" } From 7abf8eecd68c5105866ab29e5c46e4c5a130aa32 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Fri, 10 Oct 2025 22:15:21 +0100 Subject: [PATCH 03/21] all fields --- .../main/kotlin/okhttp3/TestValueFactory.kt | 5 +- .../kotlin/okhttp3/Interceptor.kt | 66 +++++- .../okhttp3/internal/connection/RealCall.kt | 7 +- .../internal/http/RealInterceptorChain.kt | 209 +++++++++++++++--- .../kotlin/okhttp3/KotlinSourceModernTest.kt | 94 +++++++- 5 files changed, 340 insertions(+), 41 deletions(-) diff --git a/okhttp-testing-support/src/main/kotlin/okhttp3/TestValueFactory.kt b/okhttp-testing-support/src/main/kotlin/okhttp3/TestValueFactory.kt index af2ab7d9320d..b3a2d5a0b80a 100644 --- a/okhttp-testing-support/src/main/kotlin/okhttp3/TestValueFactory.kt +++ b/okhttp-testing-support/src/main/kotlin/okhttp3/TestValueFactory.kt @@ -170,10 +170,7 @@ class TestValueFactory : Closeable { index = 0, exchange = null, request = call.request(), - connectTimeoutMillis = 10_000, - readTimeoutMillis = 10_000, - writeTimeoutMillis = 10_000, - clientOverrides = null, + client = call.client, ) fun newRoutePlanner( diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt index 6010f9122168..495042a53bbc 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt @@ -16,8 +16,14 @@ package okhttp3 import java.io.IOException +import java.net.Proxy +import java.net.ProxySelector import java.util.concurrent.TimeUnit import javax.net.SocketFactory +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.X509TrustManager +import okhttp3.internal.tls.CertificateChainCleaner /** * Observes, modifies, and potentially short-circuits requests going out and the corresponding @@ -110,8 +116,64 @@ fun interface Interceptor { unit: TimeUnit, ): Chain - fun withDns(dns: Dns?): Chain + val dns: Dns - fun withSocketFactory(socketFactory: SocketFactory?): Chain + fun withDns(dns: Dns): Chain + + val socketFactory: SocketFactory + + fun withSocketFactory(socketFactory: SocketFactory): Chain + + val retryOnConnectionFailure: Boolean + + fun withRetryOnConnectionFailure(retryOnConnectionFailure: Boolean): Chain + + val authenticator: Authenticator + + fun withAuthenticator(authenticator: Authenticator): Chain + + val cookieJar: CookieJar + + fun withCookieJar(cookieJar: CookieJar): Chain + + val cache: Cache? + + fun withCache(cache: Cache): Chain + + val proxy: Proxy? + + fun withProxy(proxy: Proxy): Chain + + val proxySelector: ProxySelector + + fun withProxySelector(proxySelector: ProxySelector): Chain + + val proxyAuthenticator: Authenticator + + fun withProxyAuthenticator(proxyAuthenticator: Authenticator): Chain + + val sslSocketFactory: SSLSocketFactory + + fun withSslSocketFactory(sslSocketFactory: SSLSocketFactory): Chain + + val x509TrustManager: X509TrustManager? + + fun withX509TrustManager(x509TrustManager: X509TrustManager): Chain + + val hostnameVerifier: HostnameVerifier + + fun withHostnameVerifier(hostnameVerifier: HostnameVerifier): Chain + + val certificatePinner: CertificatePinner + + fun withCertificatePinner(certificatePinner: CertificatePinner): Chain + + val certificateChainCleaner: CertificateChainCleaner? + + fun withCertificateChainCleaner(certificateChainCleaner: CertificateChainCleaner): Chain + + val connectionPool: ConnectionPool + + fun withConnectionPool(connectionPool: ConnectionPool): Chain } } diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt index 08b44b1f5a02..dc2efd9101e0 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt @@ -198,10 +198,7 @@ class RealCall( index = 0, exchange = null, request = originalRequest, - connectTimeoutMillis = client.connectTimeoutMillis, - readTimeoutMillis = client.readTimeoutMillis, - writeTimeoutMillis = client.writeTimeoutMillis, - clientOverrides = null, + client = client, ) var calledNoMoreExchanges = false @@ -257,7 +254,7 @@ class RealCall( pingIntervalMillis = client.pingIntervalMillis, retryOnConnectionFailure = client.retryOnConnectionFailure, fastFallback = client.fastFallback, - address = client.address(request.url), + address = chain.address(request.url), routeDatabase = client.routeDatabase, call = this, request = request, diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt index 7a018ee15064..c96a92c98b0e 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt @@ -16,22 +16,31 @@ package okhttp3.internal.http import java.io.IOException +import java.net.Proxy +import java.net.ProxySelector import java.util.concurrent.TimeUnit import javax.net.SocketFactory import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.X509TrustManager import okhttp3.Address +import okhttp3.Authenticator +import okhttp3.Cache import okhttp3.Call import okhttp3.CertificatePinner import okhttp3.Connection +import okhttp3.ConnectionPool +import okhttp3.CookieJar import okhttp3.Dns import okhttp3.HttpUrl import okhttp3.Interceptor +import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import okhttp3.internal.checkDuration import okhttp3.internal.connection.Exchange import okhttp3.internal.connection.RealCall +import okhttp3.internal.tls.CertificateChainCleaner /** * A concrete interceptor chain that carries the entire interceptor chain: all application @@ -40,7 +49,7 @@ import okhttp3.internal.connection.RealCall * If the chain is for an application interceptor then [exchange] must be null. Otherwise it is for * a network interceptor and [exchange] must be non-null. */ -class RealInterceptorChain constructor( +class RealInterceptorChain( internal val call: RealCall, private val interceptors: List, private val index: Int, @@ -49,8 +58,55 @@ class RealInterceptorChain constructor( internal val connectTimeoutMillis: Int, internal val readTimeoutMillis: Int, internal val writeTimeoutMillis: Int, - internal val clientOverrides: ClientOverrides?, + override val authenticator: Authenticator, + override val cache: Cache?, + override val certificateChainCleaner: CertificateChainCleaner?, + override val certificatePinner: CertificatePinner, + override val connectionPool: ConnectionPool, + override val cookieJar: CookieJar, + override val dns: Dns, + override val hostnameVerifier: HostnameVerifier, + override val proxy: Proxy?, + override val proxyAuthenticator: Authenticator, + override val proxySelector: ProxySelector, + override val retryOnConnectionFailure: Boolean, + override val socketFactory: SocketFactory, + override val sslSocketFactory: SSLSocketFactory, + override val x509TrustManager: X509TrustManager?, ) : Interceptor.Chain { + internal constructor( + call: RealCall, + interceptors: List, + index: Int, + exchange: Nothing?, + request: Request, + client: OkHttpClient, + ) : this( + call, + interceptors, + index, + exchange, + request, + client.connectTimeoutMillis, + client.readTimeoutMillis, + client.writeTimeoutMillis, + client.authenticator, + client.cache, + client.certificateChainCleaner, + client.certificatePinner, + client.connectionPool, + client.cookieJar, + client.dns, + client.hostnameVerifier, + client.proxy, + client.proxyAuthenticator, + client.proxySelector, + client.retryOnConnectionFailure, + client.socketFactory, + client.sslSocketFactory, + client.x509TrustManager, + ) + private var calls: Int = 0 internal fun copy( @@ -60,7 +116,21 @@ class RealInterceptorChain constructor( connectTimeoutMillis: Int = this.connectTimeoutMillis, readTimeoutMillis: Int = this.readTimeoutMillis, writeTimeoutMillis: Int = this.writeTimeoutMillis, - clientOverrides: ClientOverrides? = this.clientOverrides, + authenticator: Authenticator = this.authenticator, + cache: Cache? = this.cache, + certificateChainCleaner: CertificateChainCleaner? = this.certificateChainCleaner, + certificatePinner: CertificatePinner = this.certificatePinner, + connectionPool: ConnectionPool = this.connectionPool, + cookieJar: CookieJar = this.cookieJar, + dns: Dns = this.dns, + hostnameVerifier: HostnameVerifier = this.hostnameVerifier, + proxy: Proxy? = this.proxy, + proxyAuthenticator: Authenticator = this.proxyAuthenticator, + proxySelector: ProxySelector = this.proxySelector, + retryOnConnectionFailure: Boolean = this.retryOnConnectionFailure, + socketFactory: SocketFactory = this.socketFactory, + sslSocketFactory: SSLSocketFactory = this.sslSocketFactory, + x509TrustManager: X509TrustManager? = this.x509TrustManager, ) = RealInterceptorChain( call, interceptors, @@ -70,7 +140,21 @@ class RealInterceptorChain constructor( connectTimeoutMillis, readTimeoutMillis, writeTimeoutMillis, - clientOverrides, + authenticator, + cache, + certificateChainCleaner, + certificatePinner, + connectionPool, + cookieJar, + dns, + hostnameVerifier, + proxy, + proxyAuthenticator, + proxySelector, + retryOnConnectionFailure, + socketFactory, + sslSocketFactory, + x509TrustManager, ) override fun connection(): Connection? = exchange?.connection @@ -108,20 +192,94 @@ class RealInterceptorChain constructor( return copy(writeTimeoutMillis = checkDuration("writeTimeout", timeout.toLong(), unit)) } - override fun withDns(dns: Dns?): Interceptor.Chain = - withClientOverrides { - copy(dns = dns) - } + override fun withDns(dns: Dns): Interceptor.Chain { + check(exchange == null) { "dns can't be adjusted in a network interceptor" } - override fun withSocketFactory(socketFactory: SocketFactory?): Interceptor.Chain = - withClientOverrides { - copy(socketFactory = socketFactory) - } + return copy(dns = dns) + } + + override fun withSocketFactory(socketFactory: SocketFactory): Interceptor.Chain { + check(exchange == null) { "socketFactory can't be adjusted in a network interceptor" } + + return copy(socketFactory = socketFactory) + } - private fun withClientOverrides(overrides: ClientOverrides.() -> ClientOverrides): Interceptor.Chain { - check(exchange == null) { "ClientOverrides can't be adjusted in a network interceptor" } + override fun withRetryOnConnectionFailure(retryOnConnectionFailure: Boolean): Interceptor.Chain { + check(exchange == null) { "retryOnConnectionFailure can't be adjusted in a network interceptor" } - return copy(clientOverrides = clientOverrides) + return copy(retryOnConnectionFailure = retryOnConnectionFailure) + } + + override fun withAuthenticator(authenticator: Authenticator): Interceptor.Chain { + check(exchange == null) { "authenticator can't be adjusted in a network interceptor" } + + return copy(authenticator = authenticator) + } + + override fun withCookieJar(cookieJar: CookieJar): Interceptor.Chain { + check(exchange == null) { "cookieJar can't be adjusted in a network interceptor" } + + return copy(cookieJar = cookieJar) + } + + override fun withCache(cache: Cache): Interceptor.Chain { + check(exchange == null) { "cache can't be adjusted in a network interceptor" } + + return copy(cache = cache) + } + + override fun withProxy(proxy: Proxy): Interceptor.Chain { + check(exchange == null) { "proxy can't be adjusted in a network interceptor" } + + return copy(proxy = proxy) + } + + override fun withProxySelector(proxySelector: ProxySelector): Interceptor.Chain { + check(exchange == null) { "proxySelector can't be adjusted in a network interceptor" } + + return copy(proxySelector = proxySelector) + } + + override fun withProxyAuthenticator(proxyAuthenticator: Authenticator): Interceptor.Chain { + check(exchange == null) { "proxyAuthenticator can't be adjusted in a network interceptor" } + + return copy(proxyAuthenticator = proxyAuthenticator) + } + + override fun withSslSocketFactory(sslSocketFactory: SSLSocketFactory): Interceptor.Chain { + check(exchange == null) { "sslSocketFactory can't be adjusted in a network interceptor" } + + return copy(sslSocketFactory = sslSocketFactory) + } + + override fun withX509TrustManager(x509TrustManager: X509TrustManager): Interceptor.Chain { + check(exchange == null) { "x509TrustManager can't be adjusted in a network interceptor" } + + return copy(x509TrustManager = x509TrustManager) + } + + override fun withHostnameVerifier(hostnameVerifier: HostnameVerifier): Interceptor.Chain { + check(exchange == null) { "hostnameVerifier can't be adjusted in a network interceptor" } + + return copy(hostnameVerifier = hostnameVerifier) + } + + override fun withCertificatePinner(certificatePinner: CertificatePinner): Interceptor.Chain { + check(exchange == null) { "certificatePinner can't be adjusted in a network interceptor" } + + return copy(certificatePinner = certificatePinner) + } + + override fun withCertificateChainCleaner(certificateChainCleaner: CertificateChainCleaner): Interceptor.Chain { + check(exchange == null) { "certificateChainCleaner can't be adjusted in a network interceptor" } + + return copy(certificateChainCleaner = certificateChainCleaner) + } + + override fun withConnectionPool(connectionPool: ConnectionPool): Interceptor.Chain { + check(exchange == null) { "connectionPool can't be adjusted in a network interceptor" } + + return copy(connectionPool = connectionPool) } override fun call(): Call = call @@ -171,29 +329,24 @@ class RealInterceptorChain constructor( var useHostnameVerifier: HostnameVerifier? = null var useCertificatePinner: CertificatePinner? = null if (url.isHttps) { - useSslSocketFactory = call.client.sslSocketFactory - useHostnameVerifier = call.client.hostnameVerifier - useCertificatePinner = call.client.certificatePinner + useSslSocketFactory = this.sslSocketFactory + useHostnameVerifier = this.hostnameVerifier + useCertificatePinner = this.certificatePinner } return Address( uriHost = url.host, uriPort = url.port, - dns = clientOverrides?.dns ?: call.client.dns, - socketFactory = clientOverrides?.socketFactory ?: call.client.socketFactory, + dns = dns, + socketFactory = socketFactory, sslSocketFactory = useSslSocketFactory, hostnameVerifier = useHostnameVerifier, certificatePinner = useCertificatePinner, - proxyAuthenticator = call.client.proxyAuthenticator, - proxy = call.client.proxy, + proxyAuthenticator = proxyAuthenticator, + proxy = proxy, protocols = call.client.protocols, connectionSpecs = call.client.connectionSpecs, - proxySelector = call.client.proxySelector, + proxySelector = proxySelector, ) } - - data class ClientOverrides( - val dns: Dns? = null, - val socketFactory: SocketFactory? = null, - ) } diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt index f381cb3d8d65..38cad249d374 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt @@ -60,6 +60,7 @@ import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.internal.authenticator.JavaNetAuthenticator import okhttp3.internal.http2.Settings import okhttp3.internal.proxy.NullProxySelector +import okhttp3.internal.tls.CertificateChainCleaner import okhttp3.internal.tls.OkHostnameVerifier import okhttp3.java.net.cookiejar.JavaNetCookieJar import okhttp3.logging.HttpLoggingInterceptor @@ -1345,8 +1346,97 @@ class KotlinSourceModernTest { unit: TimeUnit, ): Interceptor.Chain = TODO() - override fun withDns(dns: Dns?): Interceptor.Chain = TODO() + override val dns: Dns + get() = TODO() + + override val socketFactory: SocketFactory + get() = TODO() + + override val retryOnConnectionFailure: Boolean + get() = TODO() + override val authenticator: Authenticator + get() = TODO() + override val cookieJar: CookieJar + get() = TODO() + override val cache: Cache? + get() = TODO() + override val proxy: Proxy? + get() = TODO() + override val proxySelector: ProxySelector + get() = TODO() + override val proxyAuthenticator: Authenticator + get() = TODO() + override val sslSocketFactory: SSLSocketFactory + get() = TODO() + override val x509TrustManager: X509TrustManager + get() = TODO() + override val hostnameVerifier: HostnameVerifier + get() = TODO() + override val certificatePinner: CertificatePinner + get() = TODO() + override val certificateChainCleaner: CertificateChainCleaner + get() = TODO() + override val connectionPool: ConnectionPool + get() = TODO() + + override fun withDns(dns: Dns): Interceptor.Chain { + TODO() + } + + override fun withSocketFactory(socketFactory: SocketFactory): Interceptor.Chain { + TODO() + } + + override fun withRetryOnConnectionFailure(retryOnConnectionFailure: Boolean): Interceptor.Chain { + TODO() + } + + override fun withAuthenticator(authenticator: Authenticator): Interceptor.Chain { + TODO() + } + + override fun withCookieJar(cookieJar: CookieJar): Interceptor.Chain { + TODO() + } + + override fun withCache(cache: Cache): Interceptor.Chain { + TODO() + } + + override fun withProxy(proxy: Proxy): Interceptor.Chain { + TODO() + } + + override fun withProxySelector(proxySelector: ProxySelector): Interceptor.Chain { + TODO() + } + + override fun withProxyAuthenticator(proxyAuthenticator: Authenticator): Interceptor.Chain { + TODO() + } + + override fun withSslSocketFactory(sslSocketFactory: SSLSocketFactory): Interceptor.Chain { + TODO() + } + + override fun withX509TrustManager(x509TrustManager: X509TrustManager): Interceptor.Chain { + TODO() + } - override fun withSocketFactory(socketFactory: SocketFactory?): Interceptor.Chain = TODO() + override fun withHostnameVerifier(hostnameVerifier: HostnameVerifier): Interceptor.Chain { + TODO() + } + + override fun withCertificatePinner(certificatePinner: CertificatePinner): Interceptor.Chain { + TODO() + } + + override fun withCertificateChainCleaner(certificateChainCleaner: CertificateChainCleaner): Interceptor.Chain { + TODO() + } + + override fun withConnectionPool(connectionPool: ConnectionPool): Interceptor.Chain { + TODO() + } } } From a964f639371cae15ad4ea27f783af02ab250412a Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 9 Nov 2025 13:18:59 +0000 Subject: [PATCH 04/21] Add test --- .../kotlin/okhttp3/Interceptor.kt | 136 ++++++- .../kotlin/okhttp3/OkHttpClient.kt | 1 - .../internal/http/RealInterceptorChain.kt | 4 +- .../okhttp3/InterceptorOverridesTest.kt | 374 ++++++++++++++++++ 4 files changed, 509 insertions(+), 6 deletions(-) create mode 100644 okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt index 495042a53bbc..7432d7584424 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt @@ -82,6 +82,9 @@ fun interface Interceptor { } interface Chain { + /** + * Returns the request being executed. + */ fun request(): Request @Throws(IOException::class) @@ -89,91 +92,218 @@ fun interface Interceptor { /** * Returns the connection the request will be executed on. This is only available in the chains - * of network interceptors; for application interceptors this is always null. + * of network interceptors. For application interceptors this is always null. */ fun connection(): Connection? + /** + * Returns the `Call` to which this chain belongs. + */ fun call(): Call + /** + * Returns the connect timeout in milliseconds. + */ fun connectTimeoutMillis(): Int + /** + * Returns a new chain with the specified connect timeout. + */ fun withConnectTimeout( timeout: Int, unit: TimeUnit, ): Chain + /** + * Returns the read timeout in milliseconds. + */ fun readTimeoutMillis(): Int + /** + * Returns a new chain with the specified read timeout. + */ fun withReadTimeout( timeout: Int, unit: TimeUnit, ): Chain + /** + * Returns the write timeout in milliseconds. + */ fun writeTimeoutMillis(): Int + /** + * Returns a new chain with the specified write timeout. + */ fun withWriteTimeout( timeout: Int, unit: TimeUnit, ): Chain + /** + * Get the [DNS] instance for the OkHttpClient, or an override from the Call.Chain. + */ val dns: Dns + /** + * Override the [DNS] for the Call.Chain. + * + * @throws IllegalStateException if this is a Network Interceptor, since the override is too late. + */ fun withDns(dns: Dns): Chain + /** + * Returns the [SocketFactory] for the OkHttpClient, or an override from the Call.Chain. + */ val socketFactory: SocketFactory + /** + * Override the [SocketFactory] for the Call.Chain. + * + * @throws IllegalStateException if this is a Network Interceptor, since the override is too late. + */ fun withSocketFactory(socketFactory: SocketFactory): Chain + /** + * Returns true if the call should retry on connection failures. + */ val retryOnConnectionFailure: Boolean + /** + * Returns a new chain with the specified retry on connection failure setting. + */ fun withRetryOnConnectionFailure(retryOnConnectionFailure: Boolean): Chain + /** + * Returns the [Authenticator] for the OkHttpClient, or an override from the Call.Chain. + */ val authenticator: Authenticator + /** + * Override the [Authenticator] for the Call.Chain. + * + * @throws IllegalStateException if this is a Network Interceptor, since the override is too late. + */ fun withAuthenticator(authenticator: Authenticator): Chain + /** + * Returns the [CookieJar] for the OkHttpClient, or an override from the Call.Chain. + */ val cookieJar: CookieJar + /** + * Returns a new chain with the specified [CookieJar]. + */ fun withCookieJar(cookieJar: CookieJar): Chain + /** + * Returns the [Cache] for the OkHttpClient, or an override from the Call.Chain. + */ val cache: Cache? - fun withCache(cache: Cache): Chain + /** + * Override the [Cache] for the Call.Chain. + * + * @throws IllegalStateException if this is a Network Interceptor, since the override is too late. + */ + fun withCache(cache: Cache?): Chain + /** + * Returns the [Proxy] for the OkHttpClient, or an override from the Call.Chain. + */ val proxy: Proxy? - fun withProxy(proxy: Proxy): Chain + /** + * Returns a new chain with the specified [Proxy]. + */ + fun withProxy(proxy: Proxy?): Chain + /** + * Returns the [ProxySelector] for the OkHttpClient, or an override from the Call.Chain. + */ val proxySelector: ProxySelector + /** + * Override the [ProxySelector] for the Call.Chain. + * + * @throws IllegalStateException if this is a Network Interceptor, since the override is too late. + */ fun withProxySelector(proxySelector: ProxySelector): Chain + /** + * Returns the proxy [Authenticator] for the OkHttpClient, or an override from the Call.Chain. + */ val proxyAuthenticator: Authenticator + /** + * Returns a new chain with the specified proxy [Authenticator]. + */ fun withProxyAuthenticator(proxyAuthenticator: Authenticator): Chain + /** + * Returns the [SSLSocketFactory] for the OkHttpClient, or an override from the Call.Chain. + */ val sslSocketFactory: SSLSocketFactory + /** + * Returns a new chain with the specified [SSLSocketFactory]. + * + * @throws IllegalStateException if this is a Network Interceptor, since the override is too late. + */ fun withSslSocketFactory(sslSocketFactory: SSLSocketFactory): Chain + /** + * Returns the [X509TrustManager] for the OkHttpClient, or an override from the Call.Chain. + */ val x509TrustManager: X509TrustManager? + /** + * Returns a new chain with the specified [X509TrustManager]. + */ fun withX509TrustManager(x509TrustManager: X509TrustManager): Chain + /** + * Returns the [HostnameVerifier] for the OkHttpClient, or an override from the Call.Chain. + */ val hostnameVerifier: HostnameVerifier + /** + * Override the [HostnameVerifier] for the Call.Chain. + * + * @throws IllegalStateException if this is a Network Interceptor, since the override is too late. + */ fun withHostnameVerifier(hostnameVerifier: HostnameVerifier): Chain + /** + * Returns the [CertificatePinner] for the OkHttpClient, or an override from the Call.Chain. + */ val certificatePinner: CertificatePinner + /** + * Returns a new chain with the specified [CertificatePinner]. + */ fun withCertificatePinner(certificatePinner: CertificatePinner): Chain + /** + * Returns the [CertificateChainCleaner] for the OkHttpClient, or an override from the Call.Chain. + */ val certificateChainCleaner: CertificateChainCleaner? + /** + * Override the [CertificateChainCleaner] for the Call.Chain. + * + * @throws IllegalStateException if this is a Network Interceptor, since the override is too late. + */ fun withCertificateChainCleaner(certificateChainCleaner: CertificateChainCleaner): Chain + /** + * Returns the [ConnectionPool] for the OkHttpClient, or an override from the Call.Chain. + */ val connectionPool: ConnectionPool + /** + * Returns a new chain with the specified [ConnectionPool]. + */ fun withConnectionPool(connectionPool: ConnectionPool): Chain } } diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/OkHttpClient.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/OkHttpClient.kt index 1764718a1105..6ab5bcf7d374 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/OkHttpClient.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/OkHttpClient.kt @@ -303,7 +303,6 @@ open class OkHttpClient internal constructor( * Creates an [Address] of out of the provided [HttpUrl] * that uses this client’s DNS, TLS, and proxy configuration. */ - @Deprecated("Not intended to be public API on OkHttpClient") fun address(url: HttpUrl): Address { var useSslSocketFactory: SSLSocketFactory? = null var useHostnameVerifier: HostnameVerifier? = null diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt index c96a92c98b0e..ce24b7f69b49 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt @@ -222,13 +222,13 @@ class RealInterceptorChain( return copy(cookieJar = cookieJar) } - override fun withCache(cache: Cache): Interceptor.Chain { + override fun withCache(cache: Cache?): Interceptor.Chain { check(exchange == null) { "cache can't be adjusted in a network interceptor" } return copy(cache = cache) } - override fun withProxy(proxy: Proxy): Interceptor.Chain { + override fun withProxy(proxy: Proxy?): Interceptor.Chain { check(exchange == null) { "proxy can't be adjusted in a network interceptor" } return copy(proxy = proxy) diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt new file mode 100644 index 000000000000..ecd0e5982298 --- /dev/null +++ b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt @@ -0,0 +1,374 @@ +package okhttp3 + +import app.cash.burst.Burst +import app.cash.burst.burstValues +import assertk.assertFailure +import assertk.assertThat +import assertk.assertions.hasMessage +import assertk.assertions.isEqualTo +import assertk.assertions.isFalse +import assertk.assertions.isNotSameInstanceAs +import assertk.assertions.isTrue +import java.net.ProxySelector +import java.net.SocketAddress +import java.net.URI +import java.security.cert.X509Certificate +import java.util.Locale +import java.util.Locale.getDefault +import java.util.concurrent.TimeUnit +import javax.net.SocketFactory +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.X509TrustManager +import kotlin.time.Duration.Companion.minutes +import mockwebserver3.MockResponse +import mockwebserver3.MockWebServer +import mockwebserver3.junit5.StartStop +import okhttp3.internal.platform.Platform +import okhttp3.internal.tls.CertificateChainCleaner +import okhttp3.testing.PlatformRule +import okio.Path.Companion.toPath +import okio.fakefilesystem.FakeFileSystem +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension + +@Burst +class InterceptorOverridesTest { + @RegisterExtension + val platform = PlatformRule() + + @StartStop + private val server = MockWebServer() + + // Can't use test instance with overrides + private var client = OkHttpClient() + + @Test + fun testOverrideInApplicationInterceptor( + override: OverrideParam = burstValues( + OverrideParam.Authenticator, + OverrideParam.Cache, + OverrideParam.CertificateChainCleaner, + OverrideParam.CertificatePinner, + OverrideParam.ConnectTimeout, + OverrideParam.ConnectionPool, + OverrideParam.CookieJar, + OverrideParam.Dns, + OverrideParam.HostnameVerifier, + OverrideParam.Proxy, + OverrideParam.ProxyAuthenticator, + OverrideParam.ProxySelector, + OverrideParam.ReadTimeout, + OverrideParam.RetryOnConnectionFailure, + OverrideParam.SocketFactory, + OverrideParam.SslSocketFactory, + OverrideParam.WriteTimeout, + OverrideParam.X509TrustManager, + ), + isDefault: Boolean + ) { + fun Override.testApplicationInterceptor(chain: Interceptor.Chain): Response { + val defaultValue = chain.value() + assertThat(isDefaultValue(chain.value())).isTrue() + val withOverride = chain.withOverride(nonDefaultValue) + assertThat(chain).isNotSameInstanceAs(withOverride) + assertThat(isDefaultValue(withOverride.value())).isFalse() + + return if (isDefault) { + val withDefault = withOverride.withOverride(defaultValue) + assertThat(isDefaultValue(withDefault.value())).isTrue() + withOverride.proceed(chain.request()) + } else { + withOverride.proceed(chain.request()) + } + } + + with(override.override) { + client = client.newBuilder().addInterceptor { chain -> + testApplicationInterceptor(chain) + } + .addNetworkInterceptor { chain -> + assertThat(isDefaultValue(chain.value())).isFalse() + chain.proceed(chain.request()) + } + .build() + + server.enqueue( + MockResponse(), + ) + val response = client.newCall(Request(server.url("/"))).execute() + } + } + + @Test + fun testOverrideInNetworkInterceptor( + override: OverrideParam = burstValues( + OverrideParam.Authenticator, + OverrideParam.Cache, + OverrideParam.CertificateChainCleaner, + OverrideParam.CertificatePinner, + OverrideParam.ConnectTimeout, + OverrideParam.ConnectionPool, + OverrideParam.CookieJar, + OverrideParam.Dns, + OverrideParam.HostnameVerifier, + OverrideParam.Proxy, + OverrideParam.ProxyAuthenticator, + OverrideParam.ProxySelector, + OverrideParam.ReadTimeout, + OverrideParam.RetryOnConnectionFailure, + OverrideParam.SocketFactory, + OverrideParam.SslSocketFactory, + OverrideParam.WriteTimeout, + OverrideParam.X509TrustManager, + ) + ) { + with(override.override) { + client = client.newBuilder() + .addNetworkInterceptor { chain -> + assertThat(isDefaultValue(chain.value())).isTrue() + + assertFailure { chain.withOverride(nonDefaultValue) }.hasMessage("${override.paramName} can't be adjusted in a network interceptor") + + chain.proceed(chain.request()) + } + .build() + + server.enqueue( + MockResponse(), + ) + val response = client.newCall(Request(server.url("/"))).execute() + } + } + + enum class OverrideParam(val override: Override<*>) { + Authenticator(Override.AuthenticatorOverride), + Cache(Override.CacheOverride), + CertificateChainCleaner(Override.CertificateChainCleanerOverride), + CertificatePinner(Override.CertificatePinnerOverride), + ConnectTimeout(Override.ConnectTimeoutOverride) { + override val paramName: String + get() = "Timeouts" + }, + ConnectionPool(Override.ConnectionPoolOverride), + CookieJar(Override.CookieJarOverride), + Dns(Override.DnsOverride), + HostnameVerifier(Override.HostnameVerifierOverride), + Proxy(Override.ProxyOverride), + ProxyAuthenticator(Override.ProxyAuthenticatorOverride), + ProxySelector(Override.ProxySelectorOverride), + ReadTimeout(Override.ReadTimeoutOverride) { + override val paramName: String + get() = "Timeouts" + }, + RetryOnConnectionFailure(Override.RetryOnConnectionFailureOverride), + SocketFactory(Override.SocketFactoryOverride), + SslSocketFactory(Override.SslSocketFactoryOverride), + WriteTimeout(Override.WriteTimeoutOverride) { + override val paramName: String + get() = "Timeouts" + }, + X509TrustManager(Override.X509TrustManagerOverride); + + open val paramName: String + get() = name.replaceFirstChar { it.lowercase(getDefault()) } + } + + sealed interface Override { + fun Interceptor.Chain.value(): T + + fun Interceptor.Chain.withOverride(value: T): Interceptor.Chain + + val nonDefaultValue: T + + fun isDefaultValue(value: T): Boolean + + object DnsOverride : Override { + override fun Interceptor.Chain.value(): Dns = dns + override fun Interceptor.Chain.withOverride(value: Dns): Interceptor.Chain = withDns(value) + override val nonDefaultValue: Dns = Dns { Dns.SYSTEM.lookup(it) } + override fun isDefaultValue(value: Dns): Boolean = value === Dns.SYSTEM + } + + object SocketFactoryOverride : Override { + override fun Interceptor.Chain.value(): SocketFactory = socketFactory + override fun Interceptor.Chain.withOverride(value: SocketFactory): Interceptor.Chain = withSocketFactory(value) + override val nonDefaultValue: SocketFactory = object : DelegatingSocketFactory(getDefault()) {} + override fun isDefaultValue(value: SocketFactory): Boolean = value === SocketFactory.getDefault() + } + + object AuthenticatorOverride : Override { + override fun Interceptor.Chain.value(): Authenticator = authenticator + override fun Interceptor.Chain.withOverride(value: Authenticator): Interceptor.Chain = withAuthenticator(value) + override val nonDefaultValue: Authenticator = Authenticator { route, response -> null } + override fun isDefaultValue(value: Authenticator): Boolean = value === Authenticator.NONE + } + + object CookieJarOverride : Override { + override fun Interceptor.Chain.value(): CookieJar = cookieJar + override fun Interceptor.Chain.withOverride(value: CookieJar): Interceptor.Chain = withCookieJar(value) + override val nonDefaultValue: CookieJar = object : CookieJar { + override fun saveFromResponse(url: HttpUrl, cookies: List) { + } + + override fun loadForRequest(url: HttpUrl): List { + return emptyList() + } + } + + override fun isDefaultValue(value: CookieJar): Boolean = value === CookieJar.NO_COOKIES + } + + object CacheOverride : Override { + override fun Interceptor.Chain.value(): Cache? = cache + override fun Interceptor.Chain.withOverride(value: Cache?): Interceptor.Chain = withCache(value) + override val nonDefaultValue: Cache = Cache(FakeFileSystem(), "/cash".toPath(), 1) + override fun isDefaultValue(value: Cache?): Boolean = value == null + } + + object ProxyOverride : Override { + override fun Interceptor.Chain.value(): java.net.Proxy? = proxy + override fun Interceptor.Chain.withOverride(value: java.net.Proxy?): Interceptor.Chain = withProxy(value) + override val nonDefaultValue: java.net.Proxy? = java.net.Proxy.NO_PROXY + override fun isDefaultValue(value: java.net.Proxy?): Boolean = value == null + } + + object ProxySelectorOverride : Override { + override fun Interceptor.Chain.value(): ProxySelector = proxySelector + override fun Interceptor.Chain.withOverride(value: ProxySelector): Interceptor.Chain = withProxySelector(value) + override val nonDefaultValue: ProxySelector = object : ProxySelector() { + override fun select(uri: URI?): MutableList { + return mutableListOf(java.net.Proxy.NO_PROXY) + } + + override fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: java.io.IOException?) { + } + } + + override fun isDefaultValue(value: ProxySelector): Boolean = value === ProxySelector.getDefault() + } + + object ProxyAuthenticatorOverride : Override { + override fun Interceptor.Chain.value(): Authenticator = proxyAuthenticator + override fun Interceptor.Chain.withOverride(value: Authenticator): Interceptor.Chain = + withProxyAuthenticator(value) + + override val nonDefaultValue: Authenticator = Authenticator { route, response -> null } + override fun isDefaultValue(value: Authenticator): Boolean = value === Authenticator.NONE + } + + object SslSocketFactoryOverride : Override { + override fun Interceptor.Chain.value(): SSLSocketFactory = sslSocketFactory + override fun Interceptor.Chain.withOverride(value: SSLSocketFactory): Interceptor.Chain = + withSslSocketFactory(value) + + override val nonDefaultValue: SSLSocketFactory = object : + DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) {} + + override fun isDefaultValue(value: SSLSocketFactory): Boolean { + return value !is DelegatingSSLSocketFactory + } + } + + object X509TrustManagerOverride : Override { + override fun Interceptor.Chain.value(): X509TrustManager? = x509TrustManager + override fun Interceptor.Chain.withOverride(value: X509TrustManager?): Interceptor.Chain = + withX509TrustManager(value!!) + + override val nonDefaultValue: X509TrustManager = object : X509TrustManager { + override fun checkClientTrusted( + x509Certificates: Array, + s: String, + ) { + } + + override fun checkServerTrusted( + x509Certificates: Array, + s: String, + ) { + } + + override fun getAcceptedIssuers(): Array = arrayOf() + } + + override fun isDefaultValue(value: X509TrustManager?): Boolean = !value?.javaClass?.name.orEmpty().startsWith("okhttp") + } + + object HostnameVerifierOverride : Override { + override fun Interceptor.Chain.value(): HostnameVerifier = hostnameVerifier + override fun Interceptor.Chain.withOverride(value: HostnameVerifier): Interceptor.Chain = + withHostnameVerifier(value) + + override val nonDefaultValue: HostnameVerifier = HostnameVerifier { _, _ -> true } + override fun isDefaultValue(value: HostnameVerifier): Boolean = value === okhttp3.internal.tls.OkHostnameVerifier + } + + object CertificatePinnerOverride : Override { + override fun Interceptor.Chain.value(): CertificatePinner = certificatePinner + override fun Interceptor.Chain.withOverride(value: CertificatePinner): Interceptor.Chain = + withCertificatePinner(value) + + override val nonDefaultValue: CertificatePinner = CertificatePinner.Builder() + .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") + .build() + + override fun isDefaultValue(value: CertificatePinner): Boolean = value.pins.isEmpty() + } + + object CertificateChainCleanerOverride : Override { + override fun Interceptor.Chain.value(): CertificateChainCleaner? = certificateChainCleaner + override fun Interceptor.Chain.withOverride(value: CertificateChainCleaner?): Interceptor.Chain = + withCertificateChainCleaner(value!!) + + override val nonDefaultValue: CertificateChainCleaner = + CertificateChainCleaner.get(Platform.get().platformTrustManager()) + + override fun isDefaultValue(value: CertificateChainCleaner?): Boolean = + value !== nonDefaultValue + } + + object ConnectionPoolOverride : Override { + override fun Interceptor.Chain.value(): ConnectionPool = connectionPool + override fun Interceptor.Chain.withOverride(value: ConnectionPool): Interceptor.Chain = withConnectionPool(value) + override val nonDefaultValue: ConnectionPool = ConnectionPool(keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES) + override fun isDefaultValue(value: ConnectionPool): Boolean = + value.delegate.keepAliveDurationNs == 5.minutes.inWholeNanoseconds + } + + object ConnectTimeoutOverride : Override { + override fun Interceptor.Chain.value(): Int = connectTimeoutMillis() + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = + withConnectTimeout(value, TimeUnit.MILLISECONDS) + + override val nonDefaultValue: Int = 5000 + override fun isDefaultValue(value: Int): Boolean = value == 10000 + } + + object ReadTimeoutOverride : Override { + override fun Interceptor.Chain.value(): Int = readTimeoutMillis() + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = + withReadTimeout(value, TimeUnit.MILLISECONDS) + + override val nonDefaultValue: Int = 5000 + override fun isDefaultValue(value: Int): Boolean = value == 10000 + } + + object WriteTimeoutOverride : Override { + override fun Interceptor.Chain.value(): Int = writeTimeoutMillis() + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = + withWriteTimeout(value, TimeUnit.MILLISECONDS) + + override val nonDefaultValue: Int = 5000 + override fun isDefaultValue(value: Int): Boolean = value == 10000 + } + + object RetryOnConnectionFailureOverride : Override { + override fun Interceptor.Chain.value(): Boolean = retryOnConnectionFailure + override fun Interceptor.Chain.withOverride(value: Boolean): Interceptor.Chain = + withRetryOnConnectionFailure(value) + + override val nonDefaultValue: Boolean = false + override fun isDefaultValue(value: Boolean): Boolean = value + } + } +} From 76d00233a5a82d7cb07103b99602c6f3bfed9e6d Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 9 Nov 2025 13:24:43 +0000 Subject: [PATCH 05/21] Add test --- .../kotlin/okhttp3/internal/connection/RealCall.kt | 3 +++ okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt index 3435dd1cb33a..7dc53d47c1a1 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt @@ -274,11 +274,14 @@ class RealCall( RealRoutePlanner( taskRunner = client.taskRunner, connectionPool = connectionPool, + // TODO check if override should be used readTimeoutMillis = client.readTimeoutMillis, + // TODO check if override should be used writeTimeoutMillis = client.writeTimeoutMillis, socketConnectTimeoutMillis = chain.connectTimeoutMillis, socketReadTimeoutMillis = chain.readTimeoutMillis, pingIntervalMillis = client.pingIntervalMillis, + // TODO check if override should be used retryOnConnectionFailure = client.retryOnConnectionFailure, fastFallback = client.fastFallback, address = chain.address(request.url), diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt index ecd0e5982298..e5899888649c 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt @@ -5,7 +5,6 @@ import app.cash.burst.burstValues import assertk.assertFailure import assertk.assertThat import assertk.assertions.hasMessage -import assertk.assertions.isEqualTo import assertk.assertions.isFalse import assertk.assertions.isNotSameInstanceAs import assertk.assertions.isTrue @@ -13,7 +12,6 @@ import java.net.ProxySelector import java.net.SocketAddress import java.net.URI import java.security.cert.X509Certificate -import java.util.Locale import java.util.Locale.getDefault import java.util.concurrent.TimeUnit import javax.net.SocketFactory From dba52bc3db46e166759b1220c215cc0ae5dec685 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 9 Nov 2025 13:30:34 +0000 Subject: [PATCH 06/21] cleanup --- .../okhttp3/InterceptorOverridesTest.kt | 287 ++++++++++-------- 1 file changed, 168 insertions(+), 119 deletions(-) diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt index e5899888649c..939eab1ccde1 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt @@ -43,27 +43,28 @@ class InterceptorOverridesTest { @Test fun testOverrideInApplicationInterceptor( - override: OverrideParam = burstValues( - OverrideParam.Authenticator, - OverrideParam.Cache, - OverrideParam.CertificateChainCleaner, - OverrideParam.CertificatePinner, - OverrideParam.ConnectTimeout, - OverrideParam.ConnectionPool, - OverrideParam.CookieJar, - OverrideParam.Dns, - OverrideParam.HostnameVerifier, - OverrideParam.Proxy, - OverrideParam.ProxyAuthenticator, - OverrideParam.ProxySelector, - OverrideParam.ReadTimeout, - OverrideParam.RetryOnConnectionFailure, - OverrideParam.SocketFactory, - OverrideParam.SslSocketFactory, - OverrideParam.WriteTimeout, - OverrideParam.X509TrustManager, - ), - isDefault: Boolean + override: OverrideParam = + burstValues( + OverrideParam.Authenticator, + OverrideParam.Cache, + OverrideParam.CertificateChainCleaner, + OverrideParam.CertificatePinner, + OverrideParam.ConnectTimeout, + OverrideParam.ConnectionPool, + OverrideParam.CookieJar, + OverrideParam.Dns, + OverrideParam.HostnameVerifier, + OverrideParam.Proxy, + OverrideParam.ProxyAuthenticator, + OverrideParam.ProxySelector, + OverrideParam.ReadTimeout, + OverrideParam.RetryOnConnectionFailure, + OverrideParam.SocketFactory, + OverrideParam.SslSocketFactory, + OverrideParam.WriteTimeout, + OverrideParam.X509TrustManager, + ), + isDefault: Boolean, ) { fun Override.testApplicationInterceptor(chain: Interceptor.Chain): Response { val defaultValue = chain.value() @@ -73,8 +74,8 @@ class InterceptorOverridesTest { assertThat(isDefaultValue(withOverride.value())).isFalse() return if (isDefault) { - val withDefault = withOverride.withOverride(defaultValue) - assertThat(isDefaultValue(withDefault.value())).isTrue() + val withDefault = withOverride.withOverride(defaultValue) + assertThat(isDefaultValue(withDefault.value())).isTrue() withOverride.proceed(chain.request()) } else { withOverride.proceed(chain.request()) @@ -82,14 +83,15 @@ class InterceptorOverridesTest { } with(override.override) { - client = client.newBuilder().addInterceptor { chain -> - testApplicationInterceptor(chain) - } - .addNetworkInterceptor { chain -> - assertThat(isDefaultValue(chain.value())).isFalse() - chain.proceed(chain.request()) - } - .build() + client = + client + .newBuilder() + .addInterceptor { chain -> + testApplicationInterceptor(chain) + }.addNetworkInterceptor { chain -> + assertThat(isDefaultValue(chain.value())).isFalse() + chain.proceed(chain.request()) + }.build() server.enqueue( MockResponse(), @@ -100,37 +102,43 @@ class InterceptorOverridesTest { @Test fun testOverrideInNetworkInterceptor( - override: OverrideParam = burstValues( - OverrideParam.Authenticator, - OverrideParam.Cache, - OverrideParam.CertificateChainCleaner, - OverrideParam.CertificatePinner, - OverrideParam.ConnectTimeout, - OverrideParam.ConnectionPool, - OverrideParam.CookieJar, - OverrideParam.Dns, - OverrideParam.HostnameVerifier, - OverrideParam.Proxy, - OverrideParam.ProxyAuthenticator, - OverrideParam.ProxySelector, - OverrideParam.ReadTimeout, - OverrideParam.RetryOnConnectionFailure, - OverrideParam.SocketFactory, - OverrideParam.SslSocketFactory, - OverrideParam.WriteTimeout, - OverrideParam.X509TrustManager, - ) + override: OverrideParam = + burstValues( + OverrideParam.Authenticator, + OverrideParam.Cache, + OverrideParam.CertificateChainCleaner, + OverrideParam.CertificatePinner, + OverrideParam.ConnectTimeout, + OverrideParam.ConnectionPool, + OverrideParam.CookieJar, + OverrideParam.Dns, + OverrideParam.HostnameVerifier, + OverrideParam.Proxy, + OverrideParam.ProxyAuthenticator, + OverrideParam.ProxySelector, + OverrideParam.ReadTimeout, + OverrideParam.RetryOnConnectionFailure, + OverrideParam.SocketFactory, + OverrideParam.SslSocketFactory, + OverrideParam.WriteTimeout, + OverrideParam.X509TrustManager, + ), ) { with(override.override) { - client = client.newBuilder() - .addNetworkInterceptor { chain -> - assertThat(isDefaultValue(chain.value())).isTrue() + client = + client + .newBuilder() + .addNetworkInterceptor { chain -> + assertThat(isDefaultValue(chain.value())).isTrue() - assertFailure { chain.withOverride(nonDefaultValue) }.hasMessage("${override.paramName} can't be adjusted in a network interceptor") + assertFailure { + chain.withOverride( + nonDefaultValue, + ) + }.hasMessage("${override.paramName} can't be adjusted in a network interceptor") - chain.proceed(chain.request()) - } - .build() + chain.proceed(chain.request()) + }.build() server.enqueue( MockResponse(), @@ -139,7 +147,9 @@ class InterceptorOverridesTest { } } - enum class OverrideParam(val override: Override<*>) { + enum class OverrideParam( + val override: Override<*>, + ) { Authenticator(Override.AuthenticatorOverride), Cache(Override.CacheOverride), CertificateChainCleaner(Override.CertificateChainCleanerOverride), @@ -166,7 +176,8 @@ class InterceptorOverridesTest { override val paramName: String get() = "Timeouts" }, - X509TrustManager(Override.X509TrustManagerOverride); + X509TrustManager(Override.X509TrustManagerOverride), + ; open val paramName: String get() = name.replaceFirstChar { it.lowercase(getDefault()) } @@ -183,189 +194,227 @@ class InterceptorOverridesTest { object DnsOverride : Override { override fun Interceptor.Chain.value(): Dns = dns + override fun Interceptor.Chain.withOverride(value: Dns): Interceptor.Chain = withDns(value) + override val nonDefaultValue: Dns = Dns { Dns.SYSTEM.lookup(it) } + override fun isDefaultValue(value: Dns): Boolean = value === Dns.SYSTEM } object SocketFactoryOverride : Override { override fun Interceptor.Chain.value(): SocketFactory = socketFactory + override fun Interceptor.Chain.withOverride(value: SocketFactory): Interceptor.Chain = withSocketFactory(value) + override val nonDefaultValue: SocketFactory = object : DelegatingSocketFactory(getDefault()) {} + override fun isDefaultValue(value: SocketFactory): Boolean = value === SocketFactory.getDefault() } object AuthenticatorOverride : Override { override fun Interceptor.Chain.value(): Authenticator = authenticator + override fun Interceptor.Chain.withOverride(value: Authenticator): Interceptor.Chain = withAuthenticator(value) + override val nonDefaultValue: Authenticator = Authenticator { route, response -> null } + override fun isDefaultValue(value: Authenticator): Boolean = value === Authenticator.NONE } object CookieJarOverride : Override { override fun Interceptor.Chain.value(): CookieJar = cookieJar + override fun Interceptor.Chain.withOverride(value: CookieJar): Interceptor.Chain = withCookieJar(value) - override val nonDefaultValue: CookieJar = object : CookieJar { - override fun saveFromResponse(url: HttpUrl, cookies: List) { - } - override fun loadForRequest(url: HttpUrl): List { - return emptyList() + override val nonDefaultValue: CookieJar = + object : CookieJar { + override fun saveFromResponse( + url: HttpUrl, + cookies: List, + ) { + } + + override fun loadForRequest(url: HttpUrl): List = emptyList() } - } override fun isDefaultValue(value: CookieJar): Boolean = value === CookieJar.NO_COOKIES } object CacheOverride : Override { override fun Interceptor.Chain.value(): Cache? = cache + override fun Interceptor.Chain.withOverride(value: Cache?): Interceptor.Chain = withCache(value) + override val nonDefaultValue: Cache = Cache(FakeFileSystem(), "/cash".toPath(), 1) + override fun isDefaultValue(value: Cache?): Boolean = value == null } object ProxyOverride : Override { override fun Interceptor.Chain.value(): java.net.Proxy? = proxy + override fun Interceptor.Chain.withOverride(value: java.net.Proxy?): Interceptor.Chain = withProxy(value) + override val nonDefaultValue: java.net.Proxy? = java.net.Proxy.NO_PROXY + override fun isDefaultValue(value: java.net.Proxy?): Boolean = value == null } object ProxySelectorOverride : Override { override fun Interceptor.Chain.value(): ProxySelector = proxySelector + override fun Interceptor.Chain.withOverride(value: ProxySelector): Interceptor.Chain = withProxySelector(value) - override val nonDefaultValue: ProxySelector = object : ProxySelector() { - override fun select(uri: URI?): MutableList { - return mutableListOf(java.net.Proxy.NO_PROXY) - } - override fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: java.io.IOException?) { + override val nonDefaultValue: ProxySelector = + object : ProxySelector() { + override fun select(uri: URI?): MutableList = mutableListOf(java.net.Proxy.NO_PROXY) + + override fun connectFailed( + uri: URI?, + sa: SocketAddress?, + ioe: java.io.IOException?, + ) { + } } - } override fun isDefaultValue(value: ProxySelector): Boolean = value === ProxySelector.getDefault() } object ProxyAuthenticatorOverride : Override { override fun Interceptor.Chain.value(): Authenticator = proxyAuthenticator - override fun Interceptor.Chain.withOverride(value: Authenticator): Interceptor.Chain = - withProxyAuthenticator(value) + + override fun Interceptor.Chain.withOverride(value: Authenticator): Interceptor.Chain = withProxyAuthenticator(value) override val nonDefaultValue: Authenticator = Authenticator { route, response -> null } + override fun isDefaultValue(value: Authenticator): Boolean = value === Authenticator.NONE } object SslSocketFactoryOverride : Override { override fun Interceptor.Chain.value(): SSLSocketFactory = sslSocketFactory - override fun Interceptor.Chain.withOverride(value: SSLSocketFactory): Interceptor.Chain = - withSslSocketFactory(value) - override val nonDefaultValue: SSLSocketFactory = object : - DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) {} + override fun Interceptor.Chain.withOverride(value: SSLSocketFactory): Interceptor.Chain = withSslSocketFactory(value) - override fun isDefaultValue(value: SSLSocketFactory): Boolean { - return value !is DelegatingSSLSocketFactory - } + override val nonDefaultValue: SSLSocketFactory = + object : + DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) {} + + override fun isDefaultValue(value: SSLSocketFactory): Boolean = value !is DelegatingSSLSocketFactory } object X509TrustManagerOverride : Override { override fun Interceptor.Chain.value(): X509TrustManager? = x509TrustManager - override fun Interceptor.Chain.withOverride(value: X509TrustManager?): Interceptor.Chain = - withX509TrustManager(value!!) - - override val nonDefaultValue: X509TrustManager = object : X509TrustManager { - override fun checkClientTrusted( - x509Certificates: Array, - s: String, - ) { - } - override fun checkServerTrusted( - x509Certificates: Array, - s: String, - ) { - } + override fun Interceptor.Chain.withOverride(value: X509TrustManager?): Interceptor.Chain = withX509TrustManager(value!!) - override fun getAcceptedIssuers(): Array = arrayOf() - } + override val nonDefaultValue: X509TrustManager = + object : X509TrustManager { + override fun checkClientTrusted( + x509Certificates: Array, + s: String, + ) { + } + + override fun checkServerTrusted( + x509Certificates: Array, + s: String, + ) { + } + + override fun getAcceptedIssuers(): Array = arrayOf() + } - override fun isDefaultValue(value: X509TrustManager?): Boolean = !value?.javaClass?.name.orEmpty().startsWith("okhttp") + override fun isDefaultValue(value: X509TrustManager?): Boolean = + !value + ?.javaClass + ?.name + .orEmpty() + .startsWith("okhttp") } object HostnameVerifierOverride : Override { override fun Interceptor.Chain.value(): HostnameVerifier = hostnameVerifier - override fun Interceptor.Chain.withOverride(value: HostnameVerifier): Interceptor.Chain = - withHostnameVerifier(value) + + override fun Interceptor.Chain.withOverride(value: HostnameVerifier): Interceptor.Chain = withHostnameVerifier(value) override val nonDefaultValue: HostnameVerifier = HostnameVerifier { _, _ -> true } + override fun isDefaultValue(value: HostnameVerifier): Boolean = value === okhttp3.internal.tls.OkHostnameVerifier } object CertificatePinnerOverride : Override { override fun Interceptor.Chain.value(): CertificatePinner = certificatePinner - override fun Interceptor.Chain.withOverride(value: CertificatePinner): Interceptor.Chain = - withCertificatePinner(value) - override val nonDefaultValue: CertificatePinner = CertificatePinner.Builder() - .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") - .build() + override fun Interceptor.Chain.withOverride(value: CertificatePinner): Interceptor.Chain = withCertificatePinner(value) + + override val nonDefaultValue: CertificatePinner = + CertificatePinner + .Builder() + .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") + .build() override fun isDefaultValue(value: CertificatePinner): Boolean = value.pins.isEmpty() } object CertificateChainCleanerOverride : Override { override fun Interceptor.Chain.value(): CertificateChainCleaner? = certificateChainCleaner - override fun Interceptor.Chain.withOverride(value: CertificateChainCleaner?): Interceptor.Chain = - withCertificateChainCleaner(value!!) + + override fun Interceptor.Chain.withOverride(value: CertificateChainCleaner?): Interceptor.Chain = withCertificateChainCleaner(value!!) override val nonDefaultValue: CertificateChainCleaner = CertificateChainCleaner.get(Platform.get().platformTrustManager()) - override fun isDefaultValue(value: CertificateChainCleaner?): Boolean = - value !== nonDefaultValue + override fun isDefaultValue(value: CertificateChainCleaner?): Boolean = value !== nonDefaultValue } object ConnectionPoolOverride : Override { override fun Interceptor.Chain.value(): ConnectionPool = connectionPool + override fun Interceptor.Chain.withOverride(value: ConnectionPool): Interceptor.Chain = withConnectionPool(value) + override val nonDefaultValue: ConnectionPool = ConnectionPool(keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES) - override fun isDefaultValue(value: ConnectionPool): Boolean = - value.delegate.keepAliveDurationNs == 5.minutes.inWholeNanoseconds + + override fun isDefaultValue(value: ConnectionPool): Boolean = value.delegate.keepAliveDurationNs == 5.minutes.inWholeNanoseconds } object ConnectTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = connectTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = - withConnectTimeout(value, TimeUnit.MILLISECONDS) + + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withConnectTimeout(value, TimeUnit.MILLISECONDS) override val nonDefaultValue: Int = 5000 + override fun isDefaultValue(value: Int): Boolean = value == 10000 } object ReadTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = readTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = - withReadTimeout(value, TimeUnit.MILLISECONDS) + + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withReadTimeout(value, TimeUnit.MILLISECONDS) override val nonDefaultValue: Int = 5000 + override fun isDefaultValue(value: Int): Boolean = value == 10000 } object WriteTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = writeTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = - withWriteTimeout(value, TimeUnit.MILLISECONDS) + + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withWriteTimeout(value, TimeUnit.MILLISECONDS) override val nonDefaultValue: Int = 5000 + override fun isDefaultValue(value: Int): Boolean = value == 10000 } object RetryOnConnectionFailureOverride : Override { override fun Interceptor.Chain.value(): Boolean = retryOnConnectionFailure - override fun Interceptor.Chain.withOverride(value: Boolean): Interceptor.Chain = - withRetryOnConnectionFailure(value) + + override fun Interceptor.Chain.withOverride(value: Boolean): Interceptor.Chain = withRetryOnConnectionFailure(value) override val nonDefaultValue: Boolean = false + override fun isDefaultValue(value: Boolean): Boolean = value } } From e175b8e73d401b479ddcc09d7e0ff1f82a1188d0 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 9 Nov 2025 13:41:26 +0000 Subject: [PATCH 07/21] Fix tests --- okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt | 4 ++-- .../src/commonJvmAndroid/kotlin/okhttp3/OkHttpClient.kt | 2 +- .../kotlin/okhttp3/internal/http/RealInterceptorChain.kt | 8 ++++---- .../jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt | 8 ++++---- .../src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt | 6 +++--- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt index 7432d7584424..80986adc73aa 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt @@ -243,14 +243,14 @@ fun interface Interceptor { /** * Returns the [SSLSocketFactory] for the OkHttpClient, or an override from the Call.Chain. */ - val sslSocketFactory: SSLSocketFactory + val sslSocketFactory: SSLSocketFactory? /** * Returns a new chain with the specified [SSLSocketFactory]. * * @throws IllegalStateException if this is a Network Interceptor, since the override is too late. */ - fun withSslSocketFactory(sslSocketFactory: SSLSocketFactory): Chain + fun withSslSocketFactory(sslSocketFactory: SSLSocketFactory?): Chain /** * Returns the [X509TrustManager] for the OkHttpClient, or an override from the Call.Chain. diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/OkHttpClient.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/OkHttpClient.kt index 6ab5bcf7d374..ebdcafad3fe9 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/OkHttpClient.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/OkHttpClient.kt @@ -201,7 +201,7 @@ open class OkHttpClient internal constructor( @get:JvmName("socketFactory") val socketFactory: SocketFactory = builder.socketFactory - private val sslSocketFactoryOrNull: SSLSocketFactory? + internal val sslSocketFactoryOrNull: SSLSocketFactory? @get:JvmName("sslSocketFactory") val sslSocketFactory: SSLSocketFactory diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt index ce24b7f69b49..e775185bb448 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt @@ -71,7 +71,7 @@ class RealInterceptorChain( override val proxySelector: ProxySelector, override val retryOnConnectionFailure: Boolean, override val socketFactory: SocketFactory, - override val sslSocketFactory: SSLSocketFactory, + override val sslSocketFactory: SSLSocketFactory?, override val x509TrustManager: X509TrustManager?, ) : Interceptor.Chain { internal constructor( @@ -103,7 +103,7 @@ class RealInterceptorChain( client.proxySelector, client.retryOnConnectionFailure, client.socketFactory, - client.sslSocketFactory, + client.sslSocketFactoryOrNull, client.x509TrustManager, ) @@ -129,7 +129,7 @@ class RealInterceptorChain( proxySelector: ProxySelector = this.proxySelector, retryOnConnectionFailure: Boolean = this.retryOnConnectionFailure, socketFactory: SocketFactory = this.socketFactory, - sslSocketFactory: SSLSocketFactory = this.sslSocketFactory, + sslSocketFactory: SSLSocketFactory? = this.sslSocketFactory, x509TrustManager: X509TrustManager? = this.x509TrustManager, ) = RealInterceptorChain( call, @@ -246,7 +246,7 @@ class RealInterceptorChain( return copy(proxyAuthenticator = proxyAuthenticator) } - override fun withSslSocketFactory(sslSocketFactory: SSLSocketFactory): Interceptor.Chain { + override fun withSslSocketFactory(sslSocketFactory: SSLSocketFactory?): Interceptor.Chain { check(exchange == null) { "sslSocketFactory can't be adjusted in a network interceptor" } return copy(sslSocketFactory = sslSocketFactory) diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt index 939eab1ccde1..d8d35a65e4b0 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt @@ -291,16 +291,16 @@ class InterceptorOverridesTest { override fun isDefaultValue(value: Authenticator): Boolean = value === Authenticator.NONE } - object SslSocketFactoryOverride : Override { - override fun Interceptor.Chain.value(): SSLSocketFactory = sslSocketFactory + object SslSocketFactoryOverride : Override { + override fun Interceptor.Chain.value(): SSLSocketFactory? = sslSocketFactory - override fun Interceptor.Chain.withOverride(value: SSLSocketFactory): Interceptor.Chain = withSslSocketFactory(value) + override fun Interceptor.Chain.withOverride(value: SSLSocketFactory?): Interceptor.Chain = withSslSocketFactory(value) override val nonDefaultValue: SSLSocketFactory = object : DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) {} - override fun isDefaultValue(value: SSLSocketFactory): Boolean = value !is DelegatingSSLSocketFactory + override fun isDefaultValue(value: SSLSocketFactory?): Boolean = value !is DelegatingSSLSocketFactory } object X509TrustManagerOverride : Override { diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt index 7b9bd294a9ef..28d0b7a4a606 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt @@ -1416,11 +1416,11 @@ class KotlinSourceModernTest { TODO() } - override fun withCache(cache: Cache): Interceptor.Chain { + override fun withCache(cache: Cache?): Interceptor.Chain { TODO() } - override fun withProxy(proxy: Proxy): Interceptor.Chain { + override fun withProxy(proxy: Proxy?): Interceptor.Chain { TODO() } @@ -1432,7 +1432,7 @@ class KotlinSourceModernTest { TODO() } - override fun withSslSocketFactory(sslSocketFactory: SSLSocketFactory): Interceptor.Chain { + override fun withSslSocketFactory(sslSocketFactory: SSLSocketFactory?): Interceptor.Chain { TODO() } From 638a2de4a20a633f2b34399c75983ec75a2fd22c Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 9 Nov 2025 15:43:10 +0000 Subject: [PATCH 08/21] Fix api checks --- okhttp/api/android/okhttp.api | 30 ++++++++++++++++++++++++++++++ okhttp/api/jvm/okhttp.api | 30 ++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/okhttp/api/android/okhttp.api b/okhttp/api/android/okhttp.api index 91980d116b2f..5b84b1d347c8 100644 --- a/okhttp/api/android/okhttp.api +++ b/okhttp/api/android/okhttp.api @@ -749,12 +749,42 @@ public abstract interface class okhttp3/Interceptor$Chain { public abstract fun call ()Lokhttp3/Call; public abstract fun connectTimeoutMillis ()I public abstract fun connection ()Lokhttp3/Connection; + public abstract fun getAuthenticator ()Lokhttp3/Authenticator; + public abstract fun getCache ()Lokhttp3/Cache; + public abstract fun getCertificateChainCleaner ()Lokhttp3/internal/tls/CertificateChainCleaner; + public abstract fun getCertificatePinner ()Lokhttp3/CertificatePinner; + public abstract fun getConnectionPool ()Lokhttp3/ConnectionPool; + public abstract fun getCookieJar ()Lokhttp3/CookieJar; + public abstract fun getDns ()Lokhttp3/Dns; + public abstract fun getHostnameVerifier ()Ljavax/net/ssl/HostnameVerifier; + public abstract fun getProxy ()Ljava/net/Proxy; + public abstract fun getProxyAuthenticator ()Lokhttp3/Authenticator; + public abstract fun getProxySelector ()Ljava/net/ProxySelector; + public abstract fun getRetryOnConnectionFailure ()Z + public abstract fun getSocketFactory ()Ljavax/net/SocketFactory; + public abstract fun getSslSocketFactory ()Ljavax/net/ssl/SSLSocketFactory; + public abstract fun getX509TrustManager ()Ljavax/net/ssl/X509TrustManager; public abstract fun proceed (Lokhttp3/Request;)Lokhttp3/Response; public abstract fun readTimeoutMillis ()I public abstract fun request ()Lokhttp3/Request; + public abstract fun withAuthenticator (Lokhttp3/Authenticator;)Lokhttp3/Interceptor$Chain; + public abstract fun withCache (Lokhttp3/Cache;)Lokhttp3/Interceptor$Chain; + public abstract fun withCertificateChainCleaner (Lokhttp3/internal/tls/CertificateChainCleaner;)Lokhttp3/Interceptor$Chain; + public abstract fun withCertificatePinner (Lokhttp3/CertificatePinner;)Lokhttp3/Interceptor$Chain; public abstract fun withConnectTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; + public abstract fun withConnectionPool (Lokhttp3/ConnectionPool;)Lokhttp3/Interceptor$Chain; + public abstract fun withCookieJar (Lokhttp3/CookieJar;)Lokhttp3/Interceptor$Chain; + public abstract fun withDns (Lokhttp3/Dns;)Lokhttp3/Interceptor$Chain; + public abstract fun withHostnameVerifier (Ljavax/net/ssl/HostnameVerifier;)Lokhttp3/Interceptor$Chain; + public abstract fun withProxy (Ljava/net/Proxy;)Lokhttp3/Interceptor$Chain; + public abstract fun withProxyAuthenticator (Lokhttp3/Authenticator;)Lokhttp3/Interceptor$Chain; + public abstract fun withProxySelector (Ljava/net/ProxySelector;)Lokhttp3/Interceptor$Chain; public abstract fun withReadTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; + public abstract fun withRetryOnConnectionFailure (Z)Lokhttp3/Interceptor$Chain; + public abstract fun withSocketFactory (Ljavax/net/SocketFactory;)Lokhttp3/Interceptor$Chain; + public abstract fun withSslSocketFactory (Ljavax/net/ssl/SSLSocketFactory;)Lokhttp3/Interceptor$Chain; public abstract fun withWriteTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; + public abstract fun withX509TrustManager (Ljavax/net/ssl/X509TrustManager;)Lokhttp3/Interceptor$Chain; public abstract fun writeTimeoutMillis ()I } diff --git a/okhttp/api/jvm/okhttp.api b/okhttp/api/jvm/okhttp.api index 493b859afb9a..a9375a66b806 100644 --- a/okhttp/api/jvm/okhttp.api +++ b/okhttp/api/jvm/okhttp.api @@ -749,12 +749,42 @@ public abstract interface class okhttp3/Interceptor$Chain { public abstract fun call ()Lokhttp3/Call; public abstract fun connectTimeoutMillis ()I public abstract fun connection ()Lokhttp3/Connection; + public abstract fun getAuthenticator ()Lokhttp3/Authenticator; + public abstract fun getCache ()Lokhttp3/Cache; + public abstract fun getCertificateChainCleaner ()Lokhttp3/internal/tls/CertificateChainCleaner; + public abstract fun getCertificatePinner ()Lokhttp3/CertificatePinner; + public abstract fun getConnectionPool ()Lokhttp3/ConnectionPool; + public abstract fun getCookieJar ()Lokhttp3/CookieJar; + public abstract fun getDns ()Lokhttp3/Dns; + public abstract fun getHostnameVerifier ()Ljavax/net/ssl/HostnameVerifier; + public abstract fun getProxy ()Ljava/net/Proxy; + public abstract fun getProxyAuthenticator ()Lokhttp3/Authenticator; + public abstract fun getProxySelector ()Ljava/net/ProxySelector; + public abstract fun getRetryOnConnectionFailure ()Z + public abstract fun getSocketFactory ()Ljavax/net/SocketFactory; + public abstract fun getSslSocketFactory ()Ljavax/net/ssl/SSLSocketFactory; + public abstract fun getX509TrustManager ()Ljavax/net/ssl/X509TrustManager; public abstract fun proceed (Lokhttp3/Request;)Lokhttp3/Response; public abstract fun readTimeoutMillis ()I public abstract fun request ()Lokhttp3/Request; + public abstract fun withAuthenticator (Lokhttp3/Authenticator;)Lokhttp3/Interceptor$Chain; + public abstract fun withCache (Lokhttp3/Cache;)Lokhttp3/Interceptor$Chain; + public abstract fun withCertificateChainCleaner (Lokhttp3/internal/tls/CertificateChainCleaner;)Lokhttp3/Interceptor$Chain; + public abstract fun withCertificatePinner (Lokhttp3/CertificatePinner;)Lokhttp3/Interceptor$Chain; public abstract fun withConnectTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; + public abstract fun withConnectionPool (Lokhttp3/ConnectionPool;)Lokhttp3/Interceptor$Chain; + public abstract fun withCookieJar (Lokhttp3/CookieJar;)Lokhttp3/Interceptor$Chain; + public abstract fun withDns (Lokhttp3/Dns;)Lokhttp3/Interceptor$Chain; + public abstract fun withHostnameVerifier (Ljavax/net/ssl/HostnameVerifier;)Lokhttp3/Interceptor$Chain; + public abstract fun withProxy (Ljava/net/Proxy;)Lokhttp3/Interceptor$Chain; + public abstract fun withProxyAuthenticator (Lokhttp3/Authenticator;)Lokhttp3/Interceptor$Chain; + public abstract fun withProxySelector (Ljava/net/ProxySelector;)Lokhttp3/Interceptor$Chain; public abstract fun withReadTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; + public abstract fun withRetryOnConnectionFailure (Z)Lokhttp3/Interceptor$Chain; + public abstract fun withSocketFactory (Ljavax/net/SocketFactory;)Lokhttp3/Interceptor$Chain; + public abstract fun withSslSocketFactory (Ljavax/net/ssl/SSLSocketFactory;)Lokhttp3/Interceptor$Chain; public abstract fun withWriteTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; + public abstract fun withX509TrustManager (Ljavax/net/ssl/X509TrustManager;)Lokhttp3/Interceptor$Chain; public abstract fun writeTimeoutMillis ()I } From b1473e73234d1d525dfb337cf880c1a03dc610a1 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Tue, 9 Dec 2025 08:56:38 +0000 Subject: [PATCH 09/21] Fixes and start on test --- gradle/libs.versions.toml | 2 +- .../kotlin/okhttp3/Interceptor.kt | 29 +-- .../okhttp3/internal/connection/RealCall.kt | 9 +- .../internal/http/RealInterceptorChain.kt | 40 ++-- .../okhttp3/InterceptorOverridesTest.kt | 181 +++++++++++++++--- .../kotlin/okhttp3/KotlinSourceModernTest.kt | 25 +-- 6 files changed, 190 insertions(+), 96 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4b917af5ddce..a4f2c88cdcc4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.13.1" +agp = "8.12.0" biz-aQute-bnd = "7.1.0" checkStyle = "12.2.0" com-squareup-moshi = "1.15.2" diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt index 80986adc73aa..17ca5ebca103 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt @@ -110,7 +110,7 @@ fun interface Interceptor { * Returns a new chain with the specified connect timeout. */ fun withConnectTimeout( - timeout: Int, + timeout: Long, unit: TimeUnit, ): Chain @@ -123,7 +123,7 @@ fun interface Interceptor { * Returns a new chain with the specified read timeout. */ fun withReadTimeout( - timeout: Int, + timeout: Long, unit: TimeUnit, ): Chain @@ -136,7 +136,7 @@ fun interface Interceptor { * Returns a new chain with the specified write timeout. */ fun withWriteTimeout( - timeout: Int, + timeout: Long, unit: TimeUnit, ): Chain @@ -243,24 +243,19 @@ fun interface Interceptor { /** * Returns the [SSLSocketFactory] for the OkHttpClient, or an override from the Call.Chain. */ - val sslSocketFactory: SSLSocketFactory? + val sslSocketFactoryOrNull: SSLSocketFactory? /** * Returns a new chain with the specified [SSLSocketFactory]. * * @throws IllegalStateException if this is a Network Interceptor, since the override is too late. */ - fun withSslSocketFactory(sslSocketFactory: SSLSocketFactory?): Chain + fun withSslSocketFactory(sslSocketFactory: SSLSocketFactory?, x509TrustManager: X509TrustManager?): Chain /** * Returns the [X509TrustManager] for the OkHttpClient, or an override from the Call.Chain. */ - val x509TrustManager: X509TrustManager? - - /** - * Returns a new chain with the specified [X509TrustManager]. - */ - fun withX509TrustManager(x509TrustManager: X509TrustManager): Chain + val x509TrustManagerOrNull: X509TrustManager? /** * Returns the [HostnameVerifier] for the OkHttpClient, or an override from the Call.Chain. @@ -284,18 +279,6 @@ fun interface Interceptor { */ fun withCertificatePinner(certificatePinner: CertificatePinner): Chain - /** - * Returns the [CertificateChainCleaner] for the OkHttpClient, or an override from the Call.Chain. - */ - val certificateChainCleaner: CertificateChainCleaner? - - /** - * Override the [CertificateChainCleaner] for the Call.Chain. - * - * @throws IllegalStateException if this is a Network Interceptor, since the override is too late. - */ - fun withCertificateChainCleaner(certificateChainCleaner: CertificateChainCleaner): Chain - /** * Returns the [ConnectionPool] for the OkHttpClient, or an override from the Call.Chain. */ diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt index 7dc53d47c1a1..41ad57cb58f4 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt @@ -274,15 +274,12 @@ class RealCall( RealRoutePlanner( taskRunner = client.taskRunner, connectionPool = connectionPool, - // TODO check if override should be used - readTimeoutMillis = client.readTimeoutMillis, - // TODO check if override should be used - writeTimeoutMillis = client.writeTimeoutMillis, + readTimeoutMillis = chain.readTimeoutMillis, + writeTimeoutMillis = chain.writeTimeoutMillis, socketConnectTimeoutMillis = chain.connectTimeoutMillis, socketReadTimeoutMillis = chain.readTimeoutMillis, pingIntervalMillis = client.pingIntervalMillis, - // TODO check if override should be used - retryOnConnectionFailure = client.retryOnConnectionFailure, + retryOnConnectionFailure = chain.retryOnConnectionFailure, fastFallback = client.fastFallback, address = chain.address(request.url), routeDatabase = client.routeDatabase, diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt index e775185bb448..29b742d69406 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt @@ -40,7 +40,6 @@ import okhttp3.Response import okhttp3.internal.checkDuration import okhttp3.internal.connection.Exchange import okhttp3.internal.connection.RealCall -import okhttp3.internal.tls.CertificateChainCleaner /** * A concrete interceptor chain that carries the entire interceptor chain: all application @@ -60,7 +59,6 @@ class RealInterceptorChain( internal val writeTimeoutMillis: Int, override val authenticator: Authenticator, override val cache: Cache?, - override val certificateChainCleaner: CertificateChainCleaner?, override val certificatePinner: CertificatePinner, override val connectionPool: ConnectionPool, override val cookieJar: CookieJar, @@ -71,8 +69,8 @@ class RealInterceptorChain( override val proxySelector: ProxySelector, override val retryOnConnectionFailure: Boolean, override val socketFactory: SocketFactory, - override val sslSocketFactory: SSLSocketFactory?, - override val x509TrustManager: X509TrustManager?, + override val sslSocketFactoryOrNull: SSLSocketFactory?, + override val x509TrustManagerOrNull: X509TrustManager?, ) : Interceptor.Chain { internal constructor( call: RealCall, @@ -92,7 +90,6 @@ class RealInterceptorChain( client.writeTimeoutMillis, client.authenticator, client.cache, - client.certificateChainCleaner, client.certificatePinner, client.connectionPool, client.cookieJar, @@ -118,7 +115,6 @@ class RealInterceptorChain( writeTimeoutMillis: Int = this.writeTimeoutMillis, authenticator: Authenticator = this.authenticator, cache: Cache? = this.cache, - certificateChainCleaner: CertificateChainCleaner? = this.certificateChainCleaner, certificatePinner: CertificatePinner = this.certificatePinner, connectionPool: ConnectionPool = this.connectionPool, cookieJar: CookieJar = this.cookieJar, @@ -129,8 +125,8 @@ class RealInterceptorChain( proxySelector: ProxySelector = this.proxySelector, retryOnConnectionFailure: Boolean = this.retryOnConnectionFailure, socketFactory: SocketFactory = this.socketFactory, - sslSocketFactory: SSLSocketFactory? = this.sslSocketFactory, - x509TrustManager: X509TrustManager? = this.x509TrustManager, + sslSocketFactory: SSLSocketFactory? = this.sslSocketFactoryOrNull, + x509TrustManager: X509TrustManager? = this.x509TrustManagerOrNull, ) = RealInterceptorChain( call, interceptors, @@ -142,7 +138,6 @@ class RealInterceptorChain( writeTimeoutMillis, authenticator, cache, - certificateChainCleaner, certificatePinner, connectionPool, cookieJar, @@ -162,7 +157,7 @@ class RealInterceptorChain( override fun connectTimeoutMillis(): Int = connectTimeoutMillis override fun withConnectTimeout( - timeout: Int, + timeout: Long, unit: TimeUnit, ): Interceptor.Chain { check(exchange == null) { "Timeouts can't be adjusted in a network interceptor" } @@ -173,7 +168,7 @@ class RealInterceptorChain( override fun readTimeoutMillis(): Int = readTimeoutMillis override fun withReadTimeout( - timeout: Int, + timeout: Long, unit: TimeUnit, ): Interceptor.Chain { check(exchange == null) { "Timeouts can't be adjusted in a network interceptor" } @@ -184,7 +179,7 @@ class RealInterceptorChain( override fun writeTimeoutMillis(): Int = writeTimeoutMillis override fun withWriteTimeout( - timeout: Int, + timeout: Long, unit: TimeUnit, ): Interceptor.Chain { check(exchange == null) { "Timeouts can't be adjusted in a network interceptor" } @@ -246,16 +241,13 @@ class RealInterceptorChain( return copy(proxyAuthenticator = proxyAuthenticator) } - override fun withSslSocketFactory(sslSocketFactory: SSLSocketFactory?): Interceptor.Chain { + override fun withSslSocketFactory( + sslSocketFactory: SSLSocketFactory?, + x509TrustManager: X509TrustManager? + ): Interceptor.Chain { check(exchange == null) { "sslSocketFactory can't be adjusted in a network interceptor" } - return copy(sslSocketFactory = sslSocketFactory) - } - - override fun withX509TrustManager(x509TrustManager: X509TrustManager): Interceptor.Chain { - check(exchange == null) { "x509TrustManager can't be adjusted in a network interceptor" } - - return copy(x509TrustManager = x509TrustManager) + return copy(sslSocketFactory = sslSocketFactory, x509TrustManager = x509TrustManager) } override fun withHostnameVerifier(hostnameVerifier: HostnameVerifier): Interceptor.Chain { @@ -270,12 +262,6 @@ class RealInterceptorChain( return copy(certificatePinner = certificatePinner) } - override fun withCertificateChainCleaner(certificateChainCleaner: CertificateChainCleaner): Interceptor.Chain { - check(exchange == null) { "certificateChainCleaner can't be adjusted in a network interceptor" } - - return copy(certificateChainCleaner = certificateChainCleaner) - } - override fun withConnectionPool(connectionPool: ConnectionPool): Interceptor.Chain { check(exchange == null) { "connectionPool can't be adjusted in a network interceptor" } @@ -329,7 +315,7 @@ class RealInterceptorChain( var useHostnameVerifier: HostnameVerifier? = null var useCertificatePinner: CertificatePinner? = null if (url.isHttps) { - useSslSocketFactory = this.sslSocketFactory + useSslSocketFactory = this.sslSocketFactoryOrNull useHostnameVerifier = this.hostnameVerifier useCertificatePinner = this.certificatePinner } diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt index d8d35a65e4b0..29780fca0666 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt @@ -8,7 +8,10 @@ import assertk.assertions.hasMessage import assertk.assertions.isFalse import assertk.assertions.isNotSameInstanceAs import assertk.assertions.isTrue +import java.net.InetSocketAddress +import java.net.Proxy import java.net.ProxySelector +import java.net.Socket import java.net.SocketAddress import java.net.URI import java.security.cert.X509Certificate @@ -16,15 +19,19 @@ import java.util.Locale.getDefault import java.util.concurrent.TimeUnit import javax.net.SocketFactory import javax.net.ssl.HostnameVerifier +import javax.net.ssl.SSLSocket import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509TrustManager import kotlin.time.Duration.Companion.minutes import mockwebserver3.MockResponse import mockwebserver3.MockWebServer import mockwebserver3.junit5.StartStop +import okhttp3.internal.connection.ConnectionListener import okhttp3.internal.platform.Platform import okhttp3.internal.tls.CertificateChainCleaner import okhttp3.testing.PlatformRule +import okio.ForwardingFileSystem +import okio.Path import okio.Path.Companion.toPath import okio.fakefilesystem.FakeFileSystem import org.junit.jupiter.api.Test @@ -47,7 +54,6 @@ class InterceptorOverridesTest { burstValues( OverrideParam.Authenticator, OverrideParam.Cache, - OverrideParam.CertificateChainCleaner, OverrideParam.CertificatePinner, OverrideParam.ConnectTimeout, OverrideParam.ConnectionPool, @@ -97,6 +103,7 @@ class InterceptorOverridesTest { MockResponse(), ) val response = client.newCall(Request(server.url("/"))).execute() + response.close() } } @@ -106,7 +113,6 @@ class InterceptorOverridesTest { burstValues( OverrideParam.Authenticator, OverrideParam.Cache, - OverrideParam.CertificateChainCleaner, OverrideParam.CertificatePinner, OverrideParam.ConnectTimeout, OverrideParam.ConnectionPool, @@ -144,15 +150,20 @@ class InterceptorOverridesTest { MockResponse(), ) val response = client.newCall(Request(server.url("/"))).execute() + response.close() } } + @Test + fun testCanOverrideBadImplementations() { + TODO() + } + enum class OverrideParam( val override: Override<*>, ) { Authenticator(Override.AuthenticatorOverride), Cache(Override.CacheOverride), - CertificateChainCleaner(Override.CertificateChainCleanerOverride), CertificatePinner(Override.CertificatePinnerOverride), ConnectTimeout(Override.ConnectTimeoutOverride) { override val paramName: String @@ -180,7 +191,7 @@ class InterceptorOverridesTest { ; open val paramName: String - get() = name.replaceFirstChar { it.lowercase(getDefault()) } + get() = override.paramName ?: name.replaceFirstChar { it.lowercase(getDefault()) } } sealed interface Override { @@ -188,8 +199,15 @@ class InterceptorOverridesTest { fun Interceptor.Chain.withOverride(value: T): Interceptor.Chain + fun OkHttpClient.Builder.withOverride(value: T): OkHttpClient.Builder + + val paramName: String? + get() = null + val nonDefaultValue: T + val badValue: T + fun isDefaultValue(value: T): Boolean object DnsOverride : Override { @@ -197,8 +215,12 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.withOverride(value: Dns): Interceptor.Chain = withDns(value) + override fun OkHttpClient.Builder.withOverride(value: Dns): OkHttpClient.Builder = dns(value) + override val nonDefaultValue: Dns = Dns { Dns.SYSTEM.lookup(it) } + override val badValue: Dns = Dns { TODO() } + override fun isDefaultValue(value: Dns): Boolean = value === Dns.SYSTEM } @@ -207,8 +229,14 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.withOverride(value: SocketFactory): Interceptor.Chain = withSocketFactory(value) + override fun OkHttpClient.Builder.withOverride(value: SocketFactory): OkHttpClient.Builder = socketFactory(value) + override val nonDefaultValue: SocketFactory = object : DelegatingSocketFactory(getDefault()) {} + override val badValue: SocketFactory = object : DelegatingSocketFactory(getDefault()) { + override fun configureSocket(socket: Socket): Socket = TODO() + } + override fun isDefaultValue(value: SocketFactory): Boolean = value === SocketFactory.getDefault() } @@ -217,8 +245,12 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.withOverride(value: Authenticator): Interceptor.Chain = withAuthenticator(value) + override fun OkHttpClient.Builder.withOverride(value: Authenticator): OkHttpClient.Builder = authenticator(value) + override val nonDefaultValue: Authenticator = Authenticator { route, response -> null } + override val badValue: Authenticator = Authenticator { route, response -> TODO() } + override fun isDefaultValue(value: Authenticator): Boolean = value === Authenticator.NONE } @@ -227,6 +259,8 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.withOverride(value: CookieJar): Interceptor.Chain = withCookieJar(value) + override fun OkHttpClient.Builder.withOverride(value: CookieJar): OkHttpClient.Builder = cookieJar(value) + override val nonDefaultValue: CookieJar = object : CookieJar { override fun saveFromResponse( @@ -238,6 +272,17 @@ class InterceptorOverridesTest { override fun loadForRequest(url: HttpUrl): List = emptyList() } + override val badValue: CookieJar = + object : CookieJar { + override fun saveFromResponse( + url: HttpUrl, + cookies: List, + ) { + } + + override fun loadForRequest(url: HttpUrl): List = TODO() + } + override fun isDefaultValue(value: CookieJar): Boolean = value === CookieJar.NO_COOKIES } @@ -246,8 +291,18 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.withOverride(value: Cache?): Interceptor.Chain = withCache(value) + override fun OkHttpClient.Builder.withOverride(value: Cache?): OkHttpClient.Builder = cache(value) + override val nonDefaultValue: Cache = Cache(FakeFileSystem(), "/cash".toPath(), 1) + override val badValue: Cache = Cache(object : ForwardingFileSystem(FakeFileSystem()) { + override fun onPathParameter( + path: Path, + functionName: String, + parameterName: String + ): Path = TODO() + }, "/cash".toPath(), 1) + override fun isDefaultValue(value: Cache?): Boolean = value == null } @@ -256,8 +311,12 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.withOverride(value: java.net.Proxy?): Interceptor.Chain = withProxy(value) + override fun OkHttpClient.Builder.withOverride(value: java.net.Proxy?): OkHttpClient.Builder = proxy(value) + override val nonDefaultValue: java.net.Proxy? = java.net.Proxy.NO_PROXY + override val badValue: java.net.Proxy? = java.net.Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved("proxy.example.com", 1003)) + override fun isDefaultValue(value: java.net.Proxy?): Boolean = value == null } @@ -266,9 +325,23 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.withOverride(value: ProxySelector): Interceptor.Chain = withProxySelector(value) + override fun OkHttpClient.Builder.withOverride(value: ProxySelector): OkHttpClient.Builder = proxySelector(value) + override val nonDefaultValue: ProxySelector = object : ProxySelector() { - override fun select(uri: URI?): MutableList = mutableListOf(java.net.Proxy.NO_PROXY) + override fun select(uri: URI?): MutableList = mutableListOf(java.net.Proxy.NO_PROXY) + + override fun connectFailed( + uri: URI?, + sa: SocketAddress?, + ioe: java.io.IOException?, + ) { + } + } + + override val badValue: ProxySelector = + object : ProxySelector() { + override fun select(uri: URI?): MutableList = TODO() override fun connectFailed( uri: URI?, @@ -286,27 +359,43 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.withOverride(value: Authenticator): Interceptor.Chain = withProxyAuthenticator(value) + override fun OkHttpClient.Builder.withOverride(value: Authenticator): OkHttpClient.Builder = proxyAuthenticator(value) + override val nonDefaultValue: Authenticator = Authenticator { route, response -> null } + override val badValue: Authenticator = Authenticator { route, response -> TODO() } + override fun isDefaultValue(value: Authenticator): Boolean = value === Authenticator.NONE } object SslSocketFactoryOverride : Override { - override fun Interceptor.Chain.value(): SSLSocketFactory? = sslSocketFactory + override fun Interceptor.Chain.value(): SSLSocketFactory? = sslSocketFactoryOrNull + + override fun Interceptor.Chain.withOverride(value: SSLSocketFactory?): Interceptor.Chain = withSslSocketFactory(value, x509TrustManagerOrNull) - override fun Interceptor.Chain.withOverride(value: SSLSocketFactory?): Interceptor.Chain = withSslSocketFactory(value) + override fun OkHttpClient.Builder.withOverride(value: SSLSocketFactory?): OkHttpClient.Builder = sslSocketFactory(value!!, x509TrustManagerOrNull!!) override val nonDefaultValue: SSLSocketFactory = object : DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) {} + override val badValue: SSLSocketFactory = + object : + DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) { + override fun configureSocket(sslSocket: SSLSocket): SSLSocket = TODO() + } + override fun isDefaultValue(value: SSLSocketFactory?): Boolean = value !is DelegatingSSLSocketFactory } object X509TrustManagerOverride : Override { - override fun Interceptor.Chain.value(): X509TrustManager? = x509TrustManager + override val paramName: String = "sslSocketFactory" + + override fun Interceptor.Chain.value(): X509TrustManager? = x509TrustManagerOrNull + + override fun Interceptor.Chain.withOverride(value: X509TrustManager?): Interceptor.Chain = withSslSocketFactory(sslSocketFactoryOrNull, value) - override fun Interceptor.Chain.withOverride(value: X509TrustManager?): Interceptor.Chain = withX509TrustManager(value!!) + override fun OkHttpClient.Builder.withOverride(value: X509TrustManager?): OkHttpClient.Builder = sslSocketFactory(sslSocketFactoryOrNull!!, value!!) override val nonDefaultValue: X509TrustManager = object : X509TrustManager { @@ -325,6 +414,23 @@ class InterceptorOverridesTest { override fun getAcceptedIssuers(): Array = arrayOf() } + override val badValue: X509TrustManager = + object : X509TrustManager { + override fun checkClientTrusted( + x509Certificates: Array, + s: String, + ) { + } + + override fun checkServerTrusted( + x509Certificates: Array, + s: String, + ) { + } + + override fun getAcceptedIssuers(): Array = TODO() + } + override fun isDefaultValue(value: X509TrustManager?): Boolean = !value ?.javaClass @@ -338,8 +444,12 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.withOverride(value: HostnameVerifier): Interceptor.Chain = withHostnameVerifier(value) + override fun OkHttpClient.Builder.withOverride(value: HostnameVerifier): OkHttpClient.Builder = hostnameVerifier(value) + override val nonDefaultValue: HostnameVerifier = HostnameVerifier { _, _ -> true } + override val badValue: HostnameVerifier = HostnameVerifier { _, _ -> TODO() } + override fun isDefaultValue(value: HostnameVerifier): Boolean = value === okhttp3.internal.tls.OkHostnameVerifier } @@ -348,24 +458,21 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.withOverride(value: CertificatePinner): Interceptor.Chain = withCertificatePinner(value) + override fun OkHttpClient.Builder.withOverride(value: CertificatePinner): OkHttpClient.Builder = certificatePinner(value) + override val nonDefaultValue: CertificatePinner = CertificatePinner .Builder() .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") .build() - override fun isDefaultValue(value: CertificatePinner): Boolean = value.pins.isEmpty() - } - - object CertificateChainCleanerOverride : Override { - override fun Interceptor.Chain.value(): CertificateChainCleaner? = certificateChainCleaner - - override fun Interceptor.Chain.withOverride(value: CertificateChainCleaner?): Interceptor.Chain = withCertificateChainCleaner(value!!) - - override val nonDefaultValue: CertificateChainCleaner = - CertificateChainCleaner.get(Platform.get().platformTrustManager()) + override val badValue: CertificatePinner = + CertificatePinner + .Builder() + .add("localhost", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") + .build() - override fun isDefaultValue(value: CertificateChainCleaner?): Boolean = value !== nonDefaultValue + override fun isDefaultValue(value: CertificatePinner): Boolean = value.pins.isEmpty() } object ConnectionPoolOverride : Override { @@ -373,38 +480,61 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.withOverride(value: ConnectionPool): Interceptor.Chain = withConnectionPool(value) + override fun OkHttpClient.Builder.withOverride(value: ConnectionPool): OkHttpClient.Builder = connectionPool(value) + override val nonDefaultValue: ConnectionPool = ConnectionPool(keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES) + override val badValue: ConnectionPool = ConnectionPool(keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES, connectionListener = object : ConnectionListener() { + override fun connectStart(route: Route, call: Call): Unit = TODO() + }) + override fun isDefaultValue(value: ConnectionPool): Boolean = value.delegate.keepAliveDurationNs == 5.minutes.inWholeNanoseconds } object ConnectTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = connectTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withConnectTimeout(value, TimeUnit.MILLISECONDS) + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withConnectTimeout(value.toLong(), TimeUnit.MILLISECONDS) + + override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = connectTimeout(value.toLong(), TimeUnit.MILLISECONDS) override val nonDefaultValue: Int = 5000 + override val badValue: Int + get() = throw UnsupportedOperationException() + override fun isDefaultValue(value: Int): Boolean = value == 10000 } object ReadTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = readTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withReadTimeout(value, TimeUnit.MILLISECONDS) + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withReadTimeout(value.toLong(), TimeUnit.MILLISECONDS) + + override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = readTimeout(value.toLong(), TimeUnit.MILLISECONDS) override val nonDefaultValue: Int = 5000 + override val badValue: Int + get() = throw UnsupportedOperationException() + + override fun isDefaultValue(value: Int): Boolean = value == 10000 } object WriteTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = writeTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withWriteTimeout(value, TimeUnit.MILLISECONDS) + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withWriteTimeout(value.toLong(), TimeUnit.MILLISECONDS) + + override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = writeTimeout(value.toLong(), TimeUnit.MILLISECONDS) override val nonDefaultValue: Int = 5000 + override val badValue: Int + get() = throw UnsupportedOperationException() + + override fun isDefaultValue(value: Int): Boolean = value == 10000 } @@ -413,8 +543,13 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.withOverride(value: Boolean): Interceptor.Chain = withRetryOnConnectionFailure(value) + override fun OkHttpClient.Builder.withOverride(value: Boolean): OkHttpClient.Builder = retryOnConnectionFailure(value) + override val nonDefaultValue: Boolean = false + override val badValue: Boolean + get() = throw UnsupportedOperationException() + override fun isDefaultValue(value: Boolean): Boolean = value } } diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt index 28d0b7a4a606..43828141e78f 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt @@ -1345,21 +1345,21 @@ class KotlinSourceModernTest { override fun connectTimeoutMillis(): Int = TODO() override fun withConnectTimeout( - timeout: Int, + timeout: Long, unit: TimeUnit, ): Interceptor.Chain = TODO() override fun readTimeoutMillis(): Int = TODO() override fun withReadTimeout( - timeout: Int, + timeout: Long, unit: TimeUnit, ): Interceptor.Chain = TODO() override fun writeTimeoutMillis(): Int = TODO() override fun withWriteTimeout( - timeout: Int, + timeout: Long, unit: TimeUnit, ): Interceptor.Chain = TODO() @@ -1383,16 +1383,14 @@ class KotlinSourceModernTest { get() = TODO() override val proxyAuthenticator: Authenticator get() = TODO() - override val sslSocketFactory: SSLSocketFactory + override val sslSocketFactoryOrNull: SSLSocketFactory get() = TODO() - override val x509TrustManager: X509TrustManager + override val x509TrustManagerOrNull: X509TrustManager get() = TODO() override val hostnameVerifier: HostnameVerifier get() = TODO() override val certificatePinner: CertificatePinner get() = TODO() - override val certificateChainCleaner: CertificateChainCleaner - get() = TODO() override val connectionPool: ConnectionPool get() = TODO() @@ -1432,11 +1430,10 @@ class KotlinSourceModernTest { TODO() } - override fun withSslSocketFactory(sslSocketFactory: SSLSocketFactory?): Interceptor.Chain { - TODO() - } - - override fun withX509TrustManager(x509TrustManager: X509TrustManager): Interceptor.Chain { + override fun withSslSocketFactory( + sslSocketFactory: SSLSocketFactory?, + x509TrustManager: X509TrustManager? + ): Interceptor.Chain { TODO() } @@ -1448,10 +1445,6 @@ class KotlinSourceModernTest { TODO() } - override fun withCertificateChainCleaner(certificateChainCleaner: CertificateChainCleaner): Interceptor.Chain { - TODO() - } - override fun withConnectionPool(connectionPool: ConnectionPool): Interceptor.Chain { TODO() } From d13d0ee66be11f648b45cce6d926a5a02a9532c5 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Wed, 10 Dec 2025 21:52:33 +0000 Subject: [PATCH 10/21] Add more tests --- .../okhttp3/InterceptorOverridesTest.kt | 428 ++++++++++-------- 1 file changed, 238 insertions(+), 190 deletions(-) diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt index 29780fca0666..19771a070379 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt @@ -26,9 +26,9 @@ import kotlin.time.Duration.Companion.minutes import mockwebserver3.MockResponse import mockwebserver3.MockWebServer import mockwebserver3.junit5.StartStop +import okhttp3.Headers.Companion.headersOf import okhttp3.internal.connection.ConnectionListener import okhttp3.internal.platform.Platform -import okhttp3.internal.tls.CertificateChainCleaner import okhttp3.testing.PlatformRule import okio.ForwardingFileSystem import okio.Path @@ -48,28 +48,29 @@ class InterceptorOverridesTest { // Can't use test instance with overrides private var client = OkHttpClient() + private val handshakeCertificates = platform.localhostHandshakeCertificates() + @Test fun testOverrideInApplicationInterceptor( - override: OverrideParam = - burstValues( - OverrideParam.Authenticator, - OverrideParam.Cache, - OverrideParam.CertificatePinner, - OverrideParam.ConnectTimeout, - OverrideParam.ConnectionPool, - OverrideParam.CookieJar, - OverrideParam.Dns, - OverrideParam.HostnameVerifier, - OverrideParam.Proxy, - OverrideParam.ProxyAuthenticator, - OverrideParam.ProxySelector, - OverrideParam.ReadTimeout, - OverrideParam.RetryOnConnectionFailure, - OverrideParam.SocketFactory, - OverrideParam.SslSocketFactory, - OverrideParam.WriteTimeout, - OverrideParam.X509TrustManager, - ), + override: OverrideParam = burstValues( + OverrideParam.Authenticator, + OverrideParam.Cache, + OverrideParam.CertificatePinner, + OverrideParam.ConnectTimeout, + OverrideParam.ConnectionPool, + OverrideParam.CookieJar, + OverrideParam.Dns, + OverrideParam.HostnameVerifier, + OverrideParam.Proxy, + OverrideParam.ProxyAuthenticator, + OverrideParam.ProxySelector, + OverrideParam.ReadTimeout, + OverrideParam.RetryOnConnectionFailure, + OverrideParam.SocketFactory, + OverrideParam.SslSocketFactory, + OverrideParam.WriteTimeout, + OverrideParam.X509TrustManager, + ), isDefault: Boolean, ) { fun Override.testApplicationInterceptor(chain: Interceptor.Chain): Response { @@ -89,15 +90,12 @@ class InterceptorOverridesTest { } with(override.override) { - client = - client - .newBuilder() - .addInterceptor { chain -> - testApplicationInterceptor(chain) - }.addNetworkInterceptor { chain -> - assertThat(isDefaultValue(chain.value())).isFalse() - chain.proceed(chain.request()) - }.build() + client = client.newBuilder().addInterceptor { chain -> + testApplicationInterceptor(chain) + }.addNetworkInterceptor { chain -> + assertThat(isDefaultValue(chain.value())).isFalse() + chain.proceed(chain.request()) + }.build() server.enqueue( MockResponse(), @@ -109,42 +107,38 @@ class InterceptorOverridesTest { @Test fun testOverrideInNetworkInterceptor( - override: OverrideParam = - burstValues( - OverrideParam.Authenticator, - OverrideParam.Cache, - OverrideParam.CertificatePinner, - OverrideParam.ConnectTimeout, - OverrideParam.ConnectionPool, - OverrideParam.CookieJar, - OverrideParam.Dns, - OverrideParam.HostnameVerifier, - OverrideParam.Proxy, - OverrideParam.ProxyAuthenticator, - OverrideParam.ProxySelector, - OverrideParam.ReadTimeout, - OverrideParam.RetryOnConnectionFailure, - OverrideParam.SocketFactory, - OverrideParam.SslSocketFactory, - OverrideParam.WriteTimeout, - OverrideParam.X509TrustManager, - ), + override: OverrideParam = burstValues( + OverrideParam.Authenticator, + OverrideParam.Cache, + OverrideParam.CertificatePinner, + OverrideParam.ConnectTimeout, + OverrideParam.ConnectionPool, + OverrideParam.CookieJar, + OverrideParam.Dns, + OverrideParam.HostnameVerifier, + OverrideParam.Proxy, + OverrideParam.ProxyAuthenticator, + OverrideParam.ProxySelector, + OverrideParam.ReadTimeout, + OverrideParam.RetryOnConnectionFailure, + OverrideParam.SocketFactory, + OverrideParam.SslSocketFactory, + OverrideParam.WriteTimeout, + OverrideParam.X509TrustManager, + ), ) { with(override.override) { - client = - client - .newBuilder() - .addNetworkInterceptor { chain -> - assertThat(isDefaultValue(chain.value())).isTrue() + client = client.newBuilder().addNetworkInterceptor { chain -> + assertThat(isDefaultValue(chain.value())).isTrue() - assertFailure { - chain.withOverride( - nonDefaultValue, - ) - }.hasMessage("${override.paramName} can't be adjusted in a network interceptor") + assertFailure { + chain.withOverride( + nonDefaultValue, + ) + }.hasMessage("${override.paramName} can't be adjusted in a network interceptor") - chain.proceed(chain.request()) - }.build() + chain.proceed(chain.request()) + }.build() server.enqueue( MockResponse(), @@ -155,40 +149,83 @@ class InterceptorOverridesTest { } @Test - fun testCanOverrideBadImplementations() { - TODO() + fun testOverrideBadImplementation( + override: OverrideParam = burstValues( + OverrideParam.Authenticator, + OverrideParam.Cache, + OverrideParam.CertificatePinner, +// OverrideParam.ConnectTimeout, + OverrideParam.ConnectionPool, + OverrideParam.CookieJar, + OverrideParam.Dns, + OverrideParam.HostnameVerifier, + OverrideParam.Proxy, + OverrideParam.ProxyAuthenticator, + OverrideParam.ProxySelector, +// OverrideParam.ReadTimeout, + OverrideParam.RetryOnConnectionFailure, + OverrideParam.SocketFactory, + OverrideParam.SslSocketFactory, +// OverrideParam.WriteTimeout, + OverrideParam.X509TrustManager, + ), + ) { + enableTls() + + with(override.override) { + client = client.newBuilder().apply { + if (override == OverrideParam.ProxyAuthenticator) { + proxy(server.proxyAddress) + + server.enqueue( + MockResponse.Builder().code(407).headers(headersOf("Proxy-Authenticate", "Basic realm=\"localhost\"")) + .inTunnel().build(), + ) + } else if (override == OverrideParam.Authenticator) { + server.enqueue( + MockResponse.Builder().code(401).build(), + ) + } + }.withOverride(badValue).addInterceptor { chain -> + chain + .withOverride(nonDefaultValue) + .proceed(chain.request()) + }.build() + + server.enqueue( + MockResponse(), + ) + val response = client.newCall(Request(server.url("/"))).execute() + response.close() + } } enum class OverrideParam( val override: Override<*>, ) { - Authenticator(Override.AuthenticatorOverride), - Cache(Override.CacheOverride), - CertificatePinner(Override.CertificatePinnerOverride), - ConnectTimeout(Override.ConnectTimeoutOverride) { + Authenticator(Override.AuthenticatorOverride), Cache(Override.CacheOverride), CertificatePinner(Override.CertificatePinnerOverride), ConnectTimeout( + Override.ConnectTimeoutOverride + ) { override val paramName: String get() = "Timeouts" }, - ConnectionPool(Override.ConnectionPoolOverride), - CookieJar(Override.CookieJarOverride), - Dns(Override.DnsOverride), - HostnameVerifier(Override.HostnameVerifierOverride), - Proxy(Override.ProxyOverride), - ProxyAuthenticator(Override.ProxyAuthenticatorOverride), - ProxySelector(Override.ProxySelectorOverride), - ReadTimeout(Override.ReadTimeoutOverride) { + ConnectionPool(Override.ConnectionPoolOverride), CookieJar(Override.CookieJarOverride), Dns(Override.DnsOverride), HostnameVerifier( + Override.HostnameVerifierOverride + ), + Proxy(Override.ProxyOverride), ProxyAuthenticator(Override.ProxyAuthenticatorOverride), ProxySelector(Override.ProxySelectorOverride), ReadTimeout( + Override.ReadTimeoutOverride + ) { override val paramName: String get() = "Timeouts" }, - RetryOnConnectionFailure(Override.RetryOnConnectionFailureOverride), - SocketFactory(Override.SocketFactoryOverride), - SslSocketFactory(Override.SslSocketFactoryOverride), + RetryOnConnectionFailure(Override.RetryOnConnectionFailureOverride), SocketFactory(Override.SocketFactoryOverride), SslSocketFactory( + Override.SslSocketFactoryOverride + ), WriteTimeout(Override.WriteTimeoutOverride) { override val paramName: String get() = "Timeouts" }, - X509TrustManager(Override.X509TrustManagerOverride), - ; + X509TrustManager(Override.X509TrustManagerOverride), ; open val paramName: String get() = override.paramName ?: name.replaceFirstChar { it.lowercase(getDefault()) } @@ -247,7 +284,7 @@ class InterceptorOverridesTest { override fun OkHttpClient.Builder.withOverride(value: Authenticator): OkHttpClient.Builder = authenticator(value) - override val nonDefaultValue: Authenticator = Authenticator { route, response -> null } + override val nonDefaultValue: Authenticator = Authenticator { route, response -> response.request } override val badValue: Authenticator = Authenticator { route, response -> TODO() } @@ -261,28 +298,26 @@ class InterceptorOverridesTest { override fun OkHttpClient.Builder.withOverride(value: CookieJar): OkHttpClient.Builder = cookieJar(value) - override val nonDefaultValue: CookieJar = - object : CookieJar { - override fun saveFromResponse( - url: HttpUrl, - cookies: List, - ) { - } - - override fun loadForRequest(url: HttpUrl): List = emptyList() + override val nonDefaultValue: CookieJar = object : CookieJar { + override fun saveFromResponse( + url: HttpUrl, + cookies: List, + ) { } - override val badValue: CookieJar = - object : CookieJar { - override fun saveFromResponse( - url: HttpUrl, - cookies: List, - ) { - } + override fun loadForRequest(url: HttpUrl): List = emptyList() + } - override fun loadForRequest(url: HttpUrl): List = TODO() + override val badValue: CookieJar = object : CookieJar { + override fun saveFromResponse( + url: HttpUrl, + cookies: List, + ) { } + override fun loadForRequest(url: HttpUrl): List = TODO() + } + override fun isDefaultValue(value: CookieJar): Boolean = value === CookieJar.NO_COOKIES } @@ -297,9 +332,7 @@ class InterceptorOverridesTest { override val badValue: Cache = Cache(object : ForwardingFileSystem(FakeFileSystem()) { override fun onPathParameter( - path: Path, - functionName: String, - parameterName: String + path: Path, functionName: String, parameterName: String ): Path = TODO() }, "/cash".toPath(), 1) @@ -315,7 +348,8 @@ class InterceptorOverridesTest { override val nonDefaultValue: java.net.Proxy? = java.net.Proxy.NO_PROXY - override val badValue: java.net.Proxy? = java.net.Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved("proxy.example.com", 1003)) + override val badValue: java.net.Proxy? = + java.net.Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved("proxy.example.com", 1003)) override fun isDefaultValue(value: java.net.Proxy?): Boolean = value == null } @@ -327,29 +361,27 @@ class InterceptorOverridesTest { override fun OkHttpClient.Builder.withOverride(value: ProxySelector): OkHttpClient.Builder = proxySelector(value) - override val nonDefaultValue: ProxySelector = - object : ProxySelector() { - override fun select(uri: URI?): MutableList = mutableListOf(java.net.Proxy.NO_PROXY) + override val nonDefaultValue: ProxySelector = object : ProxySelector() { + override fun select(uri: URI?): MutableList = mutableListOf(java.net.Proxy.NO_PROXY) - override fun connectFailed( - uri: URI?, - sa: SocketAddress?, - ioe: java.io.IOException?, - ) { - } + override fun connectFailed( + uri: URI?, + sa: SocketAddress?, + ioe: java.io.IOException?, + ) { } + } - override val badValue: ProxySelector = - object : ProxySelector() { - override fun select(uri: URI?): MutableList = TODO() + override val badValue: ProxySelector = object : ProxySelector() { + override fun select(uri: URI?): MutableList = TODO() - override fun connectFailed( - uri: URI?, - sa: SocketAddress?, - ioe: java.io.IOException?, - ) { - } + override fun connectFailed( + uri: URI?, + sa: SocketAddress?, + ioe: java.io.IOException?, + ) { } + } override fun isDefaultValue(value: ProxySelector): Boolean = value === ProxySelector.getDefault() } @@ -357,11 +389,13 @@ class InterceptorOverridesTest { object ProxyAuthenticatorOverride : Override { override fun Interceptor.Chain.value(): Authenticator = proxyAuthenticator - override fun Interceptor.Chain.withOverride(value: Authenticator): Interceptor.Chain = withProxyAuthenticator(value) + override fun Interceptor.Chain.withOverride(value: Authenticator): Interceptor.Chain = + withProxyAuthenticator(value) - override fun OkHttpClient.Builder.withOverride(value: Authenticator): OkHttpClient.Builder = proxyAuthenticator(value) + override fun OkHttpClient.Builder.withOverride(value: Authenticator): OkHttpClient.Builder = + proxyAuthenticator(value) - override val nonDefaultValue: Authenticator = Authenticator { route, response -> null } + override val nonDefaultValue: Authenticator = Authenticator { route, response -> response.request } override val badValue: Authenticator = Authenticator { route, response -> TODO() } @@ -371,17 +405,17 @@ class InterceptorOverridesTest { object SslSocketFactoryOverride : Override { override fun Interceptor.Chain.value(): SSLSocketFactory? = sslSocketFactoryOrNull - override fun Interceptor.Chain.withOverride(value: SSLSocketFactory?): Interceptor.Chain = withSslSocketFactory(value, x509TrustManagerOrNull) + override fun Interceptor.Chain.withOverride(value: SSLSocketFactory?): Interceptor.Chain = + withSslSocketFactory(value, x509TrustManagerOrNull) - override fun OkHttpClient.Builder.withOverride(value: SSLSocketFactory?): OkHttpClient.Builder = sslSocketFactory(value!!, x509TrustManagerOrNull!!) + override fun OkHttpClient.Builder.withOverride(value: SSLSocketFactory?): OkHttpClient.Builder = + sslSocketFactory(value!!, x509TrustManagerOrNull!!) - override val nonDefaultValue: SSLSocketFactory = - object : - DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) {} + override val nonDefaultValue: SSLSocketFactory = object : + DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) {} override val badValue: SSLSocketFactory = - object : - DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) { + object : DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) { override fun configureSocket(sslSocket: SSLSocket): SSLSocket = TODO() } @@ -393,58 +427,56 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.value(): X509TrustManager? = x509TrustManagerOrNull - override fun Interceptor.Chain.withOverride(value: X509TrustManager?): Interceptor.Chain = withSslSocketFactory(sslSocketFactoryOrNull, value) + override fun Interceptor.Chain.withOverride(value: X509TrustManager?): Interceptor.Chain = + withSslSocketFactory(sslSocketFactoryOrNull, value) - override fun OkHttpClient.Builder.withOverride(value: X509TrustManager?): OkHttpClient.Builder = sslSocketFactory(sslSocketFactoryOrNull!!, value!!) + override fun OkHttpClient.Builder.withOverride(value: X509TrustManager?): OkHttpClient.Builder = + sslSocketFactory(sslSocketFactoryOrNull!!, value!!) - override val nonDefaultValue: X509TrustManager = - object : X509TrustManager { - override fun checkClientTrusted( - x509Certificates: Array, - s: String, - ) { - } + override val nonDefaultValue: X509TrustManager = object : X509TrustManager { + override fun checkClientTrusted( + x509Certificates: Array, + s: String, + ) { + } - override fun checkServerTrusted( - x509Certificates: Array, - s: String, - ) { - } + override fun checkServerTrusted( + x509Certificates: Array, + s: String, + ) { + } - override fun getAcceptedIssuers(): Array = arrayOf() + override fun getAcceptedIssuers(): Array = arrayOf() + } + + override val badValue: X509TrustManager = object : X509TrustManager { + override fun checkClientTrusted( + x509Certificates: Array, + s: String, + ) { } - override val badValue: X509TrustManager = - object : X509TrustManager { - override fun checkClientTrusted( - x509Certificates: Array, - s: String, - ) { - } - - override fun checkServerTrusted( - x509Certificates: Array, - s: String, - ) { - } - - override fun getAcceptedIssuers(): Array = TODO() + override fun checkServerTrusted( + x509Certificates: Array, + s: String, + ) { } + override fun getAcceptedIssuers(): Array = TODO() + } + override fun isDefaultValue(value: X509TrustManager?): Boolean = - !value - ?.javaClass - ?.name - .orEmpty() - .startsWith("okhttp") + !value?.javaClass?.name.orEmpty().startsWith("okhttp") } object HostnameVerifierOverride : Override { override fun Interceptor.Chain.value(): HostnameVerifier = hostnameVerifier - override fun Interceptor.Chain.withOverride(value: HostnameVerifier): Interceptor.Chain = withHostnameVerifier(value) + override fun Interceptor.Chain.withOverride(value: HostnameVerifier): Interceptor.Chain = + withHostnameVerifier(value) - override fun OkHttpClient.Builder.withOverride(value: HostnameVerifier): OkHttpClient.Builder = hostnameVerifier(value) + override fun OkHttpClient.Builder.withOverride(value: HostnameVerifier): OkHttpClient.Builder = + hostnameVerifier(value) override val nonDefaultValue: HostnameVerifier = HostnameVerifier { _, _ -> true } @@ -456,21 +488,18 @@ class InterceptorOverridesTest { object CertificatePinnerOverride : Override { override fun Interceptor.Chain.value(): CertificatePinner = certificatePinner - override fun Interceptor.Chain.withOverride(value: CertificatePinner): Interceptor.Chain = withCertificatePinner(value) + override fun Interceptor.Chain.withOverride(value: CertificatePinner): Interceptor.Chain = + withCertificatePinner(value) - override fun OkHttpClient.Builder.withOverride(value: CertificatePinner): OkHttpClient.Builder = certificatePinner(value) + override fun OkHttpClient.Builder.withOverride(value: CertificatePinner): OkHttpClient.Builder = + certificatePinner(value) override val nonDefaultValue: CertificatePinner = - CertificatePinner - .Builder() - .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") + CertificatePinner.Builder().add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") .build() override val badValue: CertificatePinner = - CertificatePinner - .Builder() - .add("localhost", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") - .build() + CertificatePinner.Builder().add("localhost", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=").build() override fun isDefaultValue(value: CertificatePinner): Boolean = value.pins.isEmpty() } @@ -480,23 +509,28 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.withOverride(value: ConnectionPool): Interceptor.Chain = withConnectionPool(value) - override fun OkHttpClient.Builder.withOverride(value: ConnectionPool): OkHttpClient.Builder = connectionPool(value) + override fun OkHttpClient.Builder.withOverride(value: ConnectionPool): OkHttpClient.Builder = + connectionPool(value) override val nonDefaultValue: ConnectionPool = ConnectionPool(keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES) - override val badValue: ConnectionPool = ConnectionPool(keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES, connectionListener = object : ConnectionListener() { - override fun connectStart(route: Route, call: Call): Unit = TODO() - }) + override val badValue: ConnectionPool = ConnectionPool( + keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES, connectionListener = object : ConnectionListener() { + override fun connectStart(route: Route, call: Call): Unit = TODO() + }) - override fun isDefaultValue(value: ConnectionPool): Boolean = value.delegate.keepAliveDurationNs == 5.minutes.inWholeNanoseconds + override fun isDefaultValue(value: ConnectionPool): Boolean = + value.delegate.keepAliveDurationNs == 5.minutes.inWholeNanoseconds } object ConnectTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = connectTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withConnectTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = + withConnectTimeout(value.toLong(), TimeUnit.MILLISECONDS) - override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = connectTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = + connectTimeout(value.toLong(), TimeUnit.MILLISECONDS) override val nonDefaultValue: Int = 5000 @@ -509,9 +543,11 @@ class InterceptorOverridesTest { object ReadTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = readTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withReadTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = + withReadTimeout(value.toLong(), TimeUnit.MILLISECONDS) - override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = readTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = + readTimeout(value.toLong(), TimeUnit.MILLISECONDS) override val nonDefaultValue: Int = 5000 @@ -525,9 +561,11 @@ class InterceptorOverridesTest { object WriteTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = writeTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withWriteTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = + withWriteTimeout(value.toLong(), TimeUnit.MILLISECONDS) - override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = writeTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = + writeTimeout(value.toLong(), TimeUnit.MILLISECONDS) override val nonDefaultValue: Int = 5000 @@ -541,9 +579,11 @@ class InterceptorOverridesTest { object RetryOnConnectionFailureOverride : Override { override fun Interceptor.Chain.value(): Boolean = retryOnConnectionFailure - override fun Interceptor.Chain.withOverride(value: Boolean): Interceptor.Chain = withRetryOnConnectionFailure(value) + override fun Interceptor.Chain.withOverride(value: Boolean): Interceptor.Chain = + withRetryOnConnectionFailure(value) - override fun OkHttpClient.Builder.withOverride(value: Boolean): OkHttpClient.Builder = retryOnConnectionFailure(value) + override fun OkHttpClient.Builder.withOverride(value: Boolean): OkHttpClient.Builder = + retryOnConnectionFailure(value) override val nonDefaultValue: Boolean = false @@ -553,4 +593,12 @@ class InterceptorOverridesTest { override fun isDefaultValue(value: Boolean): Boolean = value } } + + private fun enableTls() { + client = client.newBuilder().sslSocketFactory( + handshakeCertificates.sslSocketFactory(), + handshakeCertificates.trustManager, + ).hostnameVerifier(RecordingHostnameVerifier()).build() + server.useHttps(handshakeCertificates.sslSocketFactory()) + } } From c2b74795a7136a7a610032148e99ce5f05c4a14e Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Wed, 10 Dec 2025 21:57:05 +0000 Subject: [PATCH 11/21] comments --- .../commonJvmAndroid/kotlin/okhttp3/Interceptor.kt | 3 --- .../kotlin/okhttp3/InterceptorOverridesTest.kt | 11 +++++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt index 17ca5ebca103..626981e1c67e 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt @@ -82,9 +82,6 @@ fun interface Interceptor { } interface Chain { - /** - * Returns the request being executed. - */ fun request(): Request @Throws(IOException::class) diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt index 19771a070379..cc0518a6deb8 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt @@ -50,6 +50,10 @@ class InterceptorOverridesTest { private val handshakeCertificates = platform.localhostHandshakeCertificates() + /** + * Test that we can override in a Application Interceptor, purely by seeing that the chain reports + * the override in a Network Interceptor. + */ @Test fun testOverrideInApplicationInterceptor( override: OverrideParam = burstValues( @@ -105,6 +109,9 @@ class InterceptorOverridesTest { } } + /** + * Test that we can't override in a Network Interceptor, which will throw an exception. + */ @Test fun testOverrideInNetworkInterceptor( override: OverrideParam = burstValues( @@ -148,6 +155,10 @@ class InterceptorOverridesTest { } } + /** + * Test that if we set a bad implementation on the OkHttpClient directly, that we can avoid the failure + * by setting a good override. + */ @Test fun testOverrideBadImplementation( override: OverrideParam = burstValues( From 27a8d608966bf29d2f4e7cdc07f3c0bdd17e38fb Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Wed, 10 Dec 2025 21:59:38 +0000 Subject: [PATCH 12/21] comments --- .../kotlin/okhttp3/InterceptorOverridesTest.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt index cc0518a6deb8..9d0d8e7951dd 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt @@ -197,11 +197,15 @@ class InterceptorOverridesTest { MockResponse.Builder().code(401).build(), ) } - }.withOverride(badValue).addInterceptor { chain -> - chain - .withOverride(nonDefaultValue) - .proceed(chain.request()) - }.build() + } + // Set the bad override directly on the client + .withOverride(badValue) + .addInterceptor { chain -> + // the only way to stop a bad override of a client is with a good override of an interceptor + chain + .withOverride(nonDefaultValue) + .proceed(chain.request()) + }.build() server.enqueue( MockResponse(), From c28672a32ab1b37ad17d1b6de2b3f3a563933898 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Wed, 10 Dec 2025 23:07:11 +0000 Subject: [PATCH 13/21] Add tests to work through --- .../kotlin/okhttp3/Interceptor.kt | 10 +- .../internal/cache/CacheInterceptor.kt | 2 +- .../okhttp3/internal/connection/RealCall.kt | 4 +- .../internal/http/BridgeInterceptor.kt | 8 +- .../internal/http/RealInterceptorChain.kt | 8 +- .../http/RetryAndFollowUpInterceptor.kt | 9 +- .../okhttp3/InterceptorOverridesTest.kt | 571 +++++++++++------- .../kotlin/okhttp3/KotlinSourceModernTest.kt | 6 +- 8 files changed, 356 insertions(+), 262 deletions(-) diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt index 626981e1c67e..ede5732b236e 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt @@ -247,7 +247,10 @@ fun interface Interceptor { * * @throws IllegalStateException if this is a Network Interceptor, since the override is too late. */ - fun withSslSocketFactory(sslSocketFactory: SSLSocketFactory?, x509TrustManager: X509TrustManager?): Chain + fun withSslSocketFactory( + sslSocketFactory: SSLSocketFactory?, + x509TrustManager: X509TrustManager?, + ): Chain /** * Returns the [X509TrustManager] for the OkHttpClient, or an override from the Call.Chain. @@ -280,10 +283,5 @@ fun interface Interceptor { * Returns the [ConnectionPool] for the OkHttpClient, or an override from the Call.Chain. */ val connectionPool: ConnectionPool - - /** - * Returns a new chain with the specified [ConnectionPool]. - */ - fun withConnectionPool(connectionPool: ConnectionPool): Chain } } diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/CacheInterceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/CacheInterceptor.kt index e76c977c2eb8..9ec3ad774dc8 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/CacheInterceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/CacheInterceptor.kt @@ -42,10 +42,10 @@ import okio.buffer /** Serves requests from the cache and writes responses to the cache. */ class CacheInterceptor( internal val call: RealCall, - internal val cache: Cache?, ) : Interceptor { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { + val cache = chain.cache val cacheCandidate = cache?.get(chain.request().requestForCache()) val now = System.currentTimeMillis() diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt index 41ad57cb58f4..c8e5f619ac16 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt @@ -210,8 +210,8 @@ class RealCall( val interceptors = mutableListOf() interceptors += client.interceptors interceptors += RetryAndFollowUpInterceptor(client) - interceptors += BridgeInterceptor(client.cookieJar) - interceptors += CacheInterceptor(this, client.cache) + interceptors += BridgeInterceptor() + interceptors += CacheInterceptor(this) interceptors += ConnectInterceptor if (!forWebSocket) { interceptors += client.networkInterceptors diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/BridgeInterceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/BridgeInterceptor.kt index 78482aacbd38..338d0f07855a 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/BridgeInterceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/BridgeInterceptor.kt @@ -31,9 +31,7 @@ import okio.buffer * request. Then it proceeds to call the network. Finally it builds a user response from the network * response. */ -class BridgeInterceptor( - private val cookieJar: CookieJar, -) : Interceptor { +class BridgeInterceptor : Interceptor { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val userRequest = chain.request() @@ -72,7 +70,7 @@ class BridgeInterceptor( requestBuilder.header("Accept-Encoding", "gzip") } - val cookies = cookieJar.loadForRequest(userRequest.url) + val cookies = chain.cookieJar.loadForRequest(userRequest.url) if (cookies.isNotEmpty()) { requestBuilder.header("Cookie", cookieHeader(cookies)) } @@ -84,7 +82,7 @@ class BridgeInterceptor( val networkRequest = requestBuilder.build() val networkResponse = chain.proceed(networkRequest) - cookieJar.receiveHeaders(networkRequest.url, networkResponse.headers) + chain.cookieJar.receiveHeaders(networkRequest.url, networkResponse.headers) val responseBuilder = networkResponse diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt index 29b742d69406..41eff3792b9b 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt @@ -243,7 +243,7 @@ class RealInterceptorChain( override fun withSslSocketFactory( sslSocketFactory: SSLSocketFactory?, - x509TrustManager: X509TrustManager? + x509TrustManager: X509TrustManager?, ): Interceptor.Chain { check(exchange == null) { "sslSocketFactory can't be adjusted in a network interceptor" } @@ -262,12 +262,6 @@ class RealInterceptorChain( return copy(certificatePinner = certificatePinner) } - override fun withConnectionPool(connectionPool: ConnectionPool): Interceptor.Chain { - check(exchange == null) { "connectionPool can't be adjusted in a network interceptor" } - - return copy(connectionPool = connectionPool) - } - override fun call(): Call = call override fun request(): Request = request diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RetryAndFollowUpInterceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RetryAndFollowUpInterceptor.kt index d4ab34b059ed..73c7823f48da 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RetryAndFollowUpInterceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RetryAndFollowUpInterceptor.kt @@ -92,7 +92,7 @@ class RetryAndFollowUpInterceptor( .build() val exchange = call.interceptorScopedExchange - val followUp = followUpRequest(response, exchange) + val followUp = followUpRequest(response, exchange, chain) if (followUp == null) { if (exchange != null && exchange.isDuplex) { @@ -207,6 +207,7 @@ class RetryAndFollowUpInterceptor( private fun followUpRequest( userResponse: Response, exchange: Exchange?, + chain: Interceptor.Chain, ): Request? { val route = exchange?.connection?.route() val responseCode = userResponse.code @@ -218,10 +219,10 @@ class RetryAndFollowUpInterceptor( if (selectedProxy.type() != Proxy.Type.HTTP) { throw ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy") } - return client.proxyAuthenticator.authenticate(route, userResponse) + return chain.proxyAuthenticator.authenticate(route, userResponse) } - HTTP_UNAUTHORIZED -> return client.authenticator.authenticate(route, userResponse) + HTTP_UNAUTHORIZED -> return chain.authenticator.authenticate(route, userResponse) HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> { return buildRedirectRequest(userResponse, method) @@ -231,7 +232,7 @@ class RetryAndFollowUpInterceptor( // 408's are rare in practice, but some servers like HAProxy use this response code. The // spec says that we may repeat the request without modifications. Modern browsers also // repeat the request (even non-idempotent ones.) - if (!client.retryOnConnectionFailure) { + if (!chain.retryOnConnectionFailure) { // The application layer has directed us not to retry the request. return null } diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt index 9d0d8e7951dd..eec09cad22be 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt @@ -5,9 +5,12 @@ import app.cash.burst.burstValues import assertk.assertFailure import assertk.assertThat import assertk.assertions.hasMessage +import assertk.assertions.isFailure import assertk.assertions.isFalse import assertk.assertions.isNotSameInstanceAs +import assertk.assertions.isSuccess import assertk.assertions.isTrue +import assertk.fail import java.net.InetSocketAddress import java.net.Proxy import java.net.ProxySelector @@ -25,7 +28,9 @@ import javax.net.ssl.X509TrustManager import kotlin.time.Duration.Companion.minutes import mockwebserver3.MockResponse import mockwebserver3.MockWebServer +import mockwebserver3.SocketHandler import mockwebserver3.junit5.StartStop +import okhttp3.CertificatePinner.Companion.pin import okhttp3.Headers.Companion.headersOf import okhttp3.internal.connection.ConnectionListener import okhttp3.internal.platform.Platform @@ -36,6 +41,7 @@ import okio.Path.Companion.toPath import okio.fakefilesystem.FakeFileSystem import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension +import org.opentest4j.AssertionFailedError @Burst class InterceptorOverridesTest { @@ -46,7 +52,10 @@ class InterceptorOverridesTest { private val server = MockWebServer() // Can't use test instance with overrides - private var client = OkHttpClient() + private var client = + OkHttpClient + .Builder() + .build() private val handshakeCertificates = platform.localhostHandshakeCertificates() @@ -56,25 +65,25 @@ class InterceptorOverridesTest { */ @Test fun testOverrideInApplicationInterceptor( - override: OverrideParam = burstValues( - OverrideParam.Authenticator, - OverrideParam.Cache, - OverrideParam.CertificatePinner, - OverrideParam.ConnectTimeout, - OverrideParam.ConnectionPool, - OverrideParam.CookieJar, - OverrideParam.Dns, - OverrideParam.HostnameVerifier, - OverrideParam.Proxy, - OverrideParam.ProxyAuthenticator, - OverrideParam.ProxySelector, - OverrideParam.ReadTimeout, - OverrideParam.RetryOnConnectionFailure, - OverrideParam.SocketFactory, - OverrideParam.SslSocketFactory, - OverrideParam.WriteTimeout, - OverrideParam.X509TrustManager, - ), + override: OverrideParam = + burstValues( + OverrideParam.Authenticator, + OverrideParam.Cache, + OverrideParam.CertificatePinner, + OverrideParam.ConnectTimeout, + OverrideParam.CookieJar, + OverrideParam.Dns, + OverrideParam.HostnameVerifier, + OverrideParam.Proxy, + OverrideParam.ProxyAuthenticator, + OverrideParam.ProxySelector, + OverrideParam.ReadTimeout, + OverrideParam.RetryOnConnectionFailure, + OverrideParam.SocketFactory, + OverrideParam.SslSocketFactory, + OverrideParam.WriteTimeout, + OverrideParam.X509TrustManager, + ), isDefault: Boolean, ) { fun Override.testApplicationInterceptor(chain: Interceptor.Chain): Response { @@ -94,12 +103,15 @@ class InterceptorOverridesTest { } with(override.override) { - client = client.newBuilder().addInterceptor { chain -> - testApplicationInterceptor(chain) - }.addNetworkInterceptor { chain -> - assertThat(isDefaultValue(chain.value())).isFalse() - chain.proceed(chain.request()) - }.build() + client = + client + .newBuilder() + .addInterceptor { chain -> + testApplicationInterceptor(chain) + }.addNetworkInterceptor { chain -> + assertThat(isDefaultValue(chain.value())).isFalse() + chain.proceed(chain.request()) + }.build() server.enqueue( MockResponse(), @@ -114,38 +126,41 @@ class InterceptorOverridesTest { */ @Test fun testOverrideInNetworkInterceptor( - override: OverrideParam = burstValues( - OverrideParam.Authenticator, - OverrideParam.Cache, - OverrideParam.CertificatePinner, - OverrideParam.ConnectTimeout, - OverrideParam.ConnectionPool, - OverrideParam.CookieJar, - OverrideParam.Dns, - OverrideParam.HostnameVerifier, - OverrideParam.Proxy, - OverrideParam.ProxyAuthenticator, - OverrideParam.ProxySelector, - OverrideParam.ReadTimeout, - OverrideParam.RetryOnConnectionFailure, - OverrideParam.SocketFactory, - OverrideParam.SslSocketFactory, - OverrideParam.WriteTimeout, - OverrideParam.X509TrustManager, - ), + override: OverrideParam = + burstValues( + OverrideParam.Authenticator, + OverrideParam.Cache, + OverrideParam.CertificatePinner, + OverrideParam.ConnectTimeout, + OverrideParam.CookieJar, + OverrideParam.Dns, + OverrideParam.HostnameVerifier, + OverrideParam.Proxy, + OverrideParam.ProxyAuthenticator, + OverrideParam.ProxySelector, + OverrideParam.ReadTimeout, + OverrideParam.RetryOnConnectionFailure, + OverrideParam.SocketFactory, + OverrideParam.SslSocketFactory, + OverrideParam.WriteTimeout, + OverrideParam.X509TrustManager, + ), ) { with(override.override) { - client = client.newBuilder().addNetworkInterceptor { chain -> - assertThat(isDefaultValue(chain.value())).isTrue() + client = + client + .newBuilder() + .addNetworkInterceptor { chain -> + assertThat(isDefaultValue(chain.value())).isTrue() - assertFailure { - chain.withOverride( - nonDefaultValue, - ) - }.hasMessage("${override.paramName} can't be adjusted in a network interceptor") + assertFailure { + chain.withOverride( + nonDefaultValue, + ) + }.hasMessage("${override.paramName} can't be adjusted in a network interceptor") - chain.proceed(chain.request()) - }.build() + chain.proceed(chain.request()) + }.build() server.enqueue( MockResponse(), @@ -161,80 +176,181 @@ class InterceptorOverridesTest { */ @Test fun testOverrideBadImplementation( - override: OverrideParam = burstValues( - OverrideParam.Authenticator, - OverrideParam.Cache, - OverrideParam.CertificatePinner, -// OverrideParam.ConnectTimeout, - OverrideParam.ConnectionPool, - OverrideParam.CookieJar, - OverrideParam.Dns, - OverrideParam.HostnameVerifier, - OverrideParam.Proxy, - OverrideParam.ProxyAuthenticator, - OverrideParam.ProxySelector, -// OverrideParam.ReadTimeout, - OverrideParam.RetryOnConnectionFailure, - OverrideParam.SocketFactory, - OverrideParam.SslSocketFactory, -// OverrideParam.WriteTimeout, - OverrideParam.X509TrustManager, - ), + override: OverrideParam = + burstValues( + OverrideParam.Authenticator, + OverrideParam.Cache, + OverrideParam.CertificatePinner, + OverrideParam.ConnectTimeout, + OverrideParam.CookieJar, + OverrideParam.Dns, + OverrideParam.HostnameVerifier, + OverrideParam.Proxy, + OverrideParam.ProxyAuthenticator, + OverrideParam.ProxySelector, + OverrideParam.ReadTimeout, + OverrideParam.RetryOnConnectionFailure, + OverrideParam.SocketFactory, + OverrideParam.SslSocketFactory, + OverrideParam.WriteTimeout, + OverrideParam.X509TrustManager, + ), + testItFails: Boolean = true, ) { - enableTls() + when (override) { + OverrideParam.ProxyAuthenticator -> { + client = client.newBuilder().proxy(server.proxyAddress).build() + + server.enqueue( + MockResponse + .Builder() + .code(407) + .headers(headersOf("Proxy-Authenticate", "Basic realm=\"localhost\"")) + .inTunnel() + .build(), + ) + + overrideBadImplementation(override = override.override, testItFails = testItFails) + } - with(override.override) { - client = client.newBuilder().apply { - if (override == OverrideParam.ProxyAuthenticator) { - proxy(server.proxyAddress) - - server.enqueue( - MockResponse.Builder().code(407).headers(headersOf("Proxy-Authenticate", "Basic realm=\"localhost\"")) - .inTunnel().build(), - ) - } else if (override == OverrideParam.Authenticator) { - server.enqueue( - MockResponse.Builder().code(401).build(), - ) - } + OverrideParam.Authenticator -> { + server.enqueue( + MockResponse.Builder().code(401).build(), + ) + + overrideBadImplementation(override = override.override, testItFails = testItFails) + } + + OverrideParam.RetryOnConnectionFailure -> { + server.enqueue( + MockResponse.Builder().failHandshake().build(), + ) + + overrideBadImplementation( + override = Override.RetryOnConnectionFailureOverride, + testItFails = testItFails, + badValue = false, + goodValue = true, + ) + } + + OverrideParam.SslSocketFactory -> { + enableTls() + overrideBadImplementation( + override = Override.SslSocketFactoryOverride, + testItFails = testItFails, + goodValue = client.sslSocketFactory, + ) + } + + OverrideParam.X509TrustManager -> { + enableTls() + overrideBadImplementation( + override = Override.X509TrustManagerOverride, + testItFails = testItFails, + goodValue = client.x509TrustManager, + ) + } + + OverrideParam.HostnameVerifier -> { + enableTls() + overrideBadImplementation(override = override.override, testItFails = testItFails) } - // Set the bad override directly on the client - .withOverride(badValue) - .addInterceptor { chain -> - // the only way to stop a bad override of a client is with a good override of an interceptor - chain - .withOverride(nonDefaultValue) - .proceed(chain.request()) - }.build() + + OverrideParam.ReadTimeout -> { + server.enqueue( + MockResponse.Builder().bodyDelay(1, TimeUnit.SECONDS).build(), + ) + + overrideBadImplementation(override = override.override, testItFails = testItFails) + } + + OverrideParam.CertificatePinner -> { + enableTls() + + val pinner = + CertificatePinner + .Builder() + .add(server.hostName, pin(handshakeCertificates.trustManager.acceptedIssuers.first())) + .build() + + overrideBadImplementation(override = Override.CertificatePinnerOverride, testItFails = testItFails, goodValue = pinner) + } + + else -> { + overrideBadImplementation(override = override.override, testItFails = testItFails) + } + } + } + + private fun overrideBadImplementation( + override: Override, + testItFails: Boolean, + badValue: T = override.badValue, + goodValue: T = override.nonDefaultValue, + ) { + with(override) { + client = + client + .newBuilder() + // Set the bad override directly on the client + .withOverride(badValue) + .addInterceptor { chain -> + // the only way to stop a bad override of a client is with a good override of an interceptor + chain + .run { + if (testItFails) { + this + } else { + withOverride(goodValue) + } + }.proceed(chain.request()) + }.build() server.enqueue( MockResponse(), ) - val response = client.newCall(Request(server.url("/"))).execute() - response.close() + val call = client.newCall(Request(server.url("/"))) + val result = runCatching { call.execute().close() } + + if (testItFails) { + assertThat(result).isFailure() + } else { + result.getOrThrow() + } } } enum class OverrideParam( val override: Override<*>, ) { - Authenticator(Override.AuthenticatorOverride), Cache(Override.CacheOverride), CertificatePinner(Override.CertificatePinnerOverride), ConnectTimeout( - Override.ConnectTimeoutOverride + Authenticator(Override.AuthenticatorOverride), + Cache(Override.CacheOverride), + CertificatePinner(Override.CertificatePinnerOverride), + ConnectTimeout( + Override.ConnectTimeoutOverride, ) { override val paramName: String get() = "Timeouts" }, - ConnectionPool(Override.ConnectionPoolOverride), CookieJar(Override.CookieJarOverride), Dns(Override.DnsOverride), HostnameVerifier( - Override.HostnameVerifierOverride + CookieJar(Override.CookieJarOverride), + Dns(Override.DnsOverride), + HostnameVerifier( + Override.HostnameVerifierOverride, ), - Proxy(Override.ProxyOverride), ProxyAuthenticator(Override.ProxyAuthenticatorOverride), ProxySelector(Override.ProxySelectorOverride), ReadTimeout( - Override.ReadTimeoutOverride + Proxy(Override.ProxyOverride), + ProxyAuthenticator(Override.ProxyAuthenticatorOverride), + ProxySelector(Override.ProxySelectorOverride), + ReadTimeout( + Override.ReadTimeoutOverride, ) { override val paramName: String get() = "Timeouts" }, - RetryOnConnectionFailure(Override.RetryOnConnectionFailureOverride), SocketFactory(Override.SocketFactoryOverride), SslSocketFactory( - Override.SslSocketFactoryOverride + RetryOnConnectionFailure(Override.RetryOnConnectionFailureOverride), + SocketFactory(Override.SocketFactoryOverride), + SslSocketFactory( + Override.SslSocketFactoryOverride, ), WriteTimeout(Override.WriteTimeoutOverride) { override val paramName: String @@ -285,9 +401,10 @@ class InterceptorOverridesTest { override val nonDefaultValue: SocketFactory = object : DelegatingSocketFactory(getDefault()) {} - override val badValue: SocketFactory = object : DelegatingSocketFactory(getDefault()) { - override fun configureSocket(socket: Socket): Socket = TODO() - } + override val badValue: SocketFactory = + object : DelegatingSocketFactory(getDefault()) { + override fun configureSocket(socket: Socket): Socket = TODO() + } override fun isDefaultValue(value: SocketFactory): Boolean = value === SocketFactory.getDefault() } @@ -313,26 +430,28 @@ class InterceptorOverridesTest { override fun OkHttpClient.Builder.withOverride(value: CookieJar): OkHttpClient.Builder = cookieJar(value) - override val nonDefaultValue: CookieJar = object : CookieJar { - override fun saveFromResponse( - url: HttpUrl, - cookies: List, - ) { + override val nonDefaultValue: CookieJar = + object : CookieJar { + override fun saveFromResponse( + url: HttpUrl, + cookies: List, + ) { + } + + override fun loadForRequest(url: HttpUrl): List = emptyList() } - override fun loadForRequest(url: HttpUrl): List = emptyList() - } + override val badValue: CookieJar = + object : CookieJar { + override fun saveFromResponse( + url: HttpUrl, + cookies: List, + ) { + } - override val badValue: CookieJar = object : CookieJar { - override fun saveFromResponse( - url: HttpUrl, - cookies: List, - ) { + override fun loadForRequest(url: HttpUrl): List = TODO() } - override fun loadForRequest(url: HttpUrl): List = TODO() - } - override fun isDefaultValue(value: CookieJar): Boolean = value === CookieJar.NO_COOKIES } @@ -345,11 +464,18 @@ class InterceptorOverridesTest { override val nonDefaultValue: Cache = Cache(FakeFileSystem(), "/cash".toPath(), 1) - override val badValue: Cache = Cache(object : ForwardingFileSystem(FakeFileSystem()) { - override fun onPathParameter( - path: Path, functionName: String, parameterName: String - ): Path = TODO() - }, "/cash".toPath(), 1) + override val badValue: Cache = + Cache( + object : ForwardingFileSystem(FakeFileSystem()) { + override fun onPathParameter( + path: Path, + functionName: String, + parameterName: String, + ): Path = TODO() + }, + "/cash".toPath(), + 1, + ) override fun isDefaultValue(value: Cache?): Boolean = value == null } @@ -376,27 +502,29 @@ class InterceptorOverridesTest { override fun OkHttpClient.Builder.withOverride(value: ProxySelector): OkHttpClient.Builder = proxySelector(value) - override val nonDefaultValue: ProxySelector = object : ProxySelector() { - override fun select(uri: URI?): MutableList = mutableListOf(java.net.Proxy.NO_PROXY) + override val nonDefaultValue: ProxySelector = + object : ProxySelector() { + override fun select(uri: URI?): MutableList = mutableListOf(java.net.Proxy.NO_PROXY) - override fun connectFailed( - uri: URI?, - sa: SocketAddress?, - ioe: java.io.IOException?, - ) { + override fun connectFailed( + uri: URI?, + sa: SocketAddress?, + ioe: java.io.IOException?, + ) { + } } - } - override val badValue: ProxySelector = object : ProxySelector() { - override fun select(uri: URI?): MutableList = TODO() + override val badValue: ProxySelector = + object : ProxySelector() { + override fun select(uri: URI?): MutableList = TODO() - override fun connectFailed( - uri: URI?, - sa: SocketAddress?, - ioe: java.io.IOException?, - ) { + override fun connectFailed( + uri: URI?, + sa: SocketAddress?, + ioe: java.io.IOException?, + ) { + } } - } override fun isDefaultValue(value: ProxySelector): Boolean = value === ProxySelector.getDefault() } @@ -404,11 +532,9 @@ class InterceptorOverridesTest { object ProxyAuthenticatorOverride : Override { override fun Interceptor.Chain.value(): Authenticator = proxyAuthenticator - override fun Interceptor.Chain.withOverride(value: Authenticator): Interceptor.Chain = - withProxyAuthenticator(value) + override fun Interceptor.Chain.withOverride(value: Authenticator): Interceptor.Chain = withProxyAuthenticator(value) - override fun OkHttpClient.Builder.withOverride(value: Authenticator): OkHttpClient.Builder = - proxyAuthenticator(value) + override fun OkHttpClient.Builder.withOverride(value: Authenticator): OkHttpClient.Builder = proxyAuthenticator(value) override val nonDefaultValue: Authenticator = Authenticator { route, response -> response.request } @@ -426,8 +552,9 @@ class InterceptorOverridesTest { override fun OkHttpClient.Builder.withOverride(value: SSLSocketFactory?): OkHttpClient.Builder = sslSocketFactory(value!!, x509TrustManagerOrNull!!) - override val nonDefaultValue: SSLSocketFactory = object : - DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) {} + override val nonDefaultValue: SSLSocketFactory = + object : + DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) {} override val badValue: SSLSocketFactory = object : DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) { @@ -448,50 +575,54 @@ class InterceptorOverridesTest { override fun OkHttpClient.Builder.withOverride(value: X509TrustManager?): OkHttpClient.Builder = sslSocketFactory(sslSocketFactoryOrNull!!, value!!) - override val nonDefaultValue: X509TrustManager = object : X509TrustManager { - override fun checkClientTrusted( - x509Certificates: Array, - s: String, - ) { - } - - override fun checkServerTrusted( - x509Certificates: Array, - s: String, - ) { - } - - override fun getAcceptedIssuers(): Array = arrayOf() - } - - override val badValue: X509TrustManager = object : X509TrustManager { - override fun checkClientTrusted( - x509Certificates: Array, - s: String, - ) { + override val nonDefaultValue: X509TrustManager = + object : X509TrustManager { + override fun checkClientTrusted( + x509Certificates: Array, + s: String, + ) { + } + + override fun checkServerTrusted( + x509Certificates: Array, + s: String, + ) { + } + + override fun getAcceptedIssuers(): Array = arrayOf() } - override fun checkServerTrusted( - x509Certificates: Array, - s: String, - ) { + override val badValue: X509TrustManager = + object : X509TrustManager { + override fun checkClientTrusted( + x509Certificates: Array, + s: String, + ) { + } + + override fun checkServerTrusted( + x509Certificates: Array, + s: String, + ) { + } + + override fun getAcceptedIssuers(): Array = arrayOf() } - override fun getAcceptedIssuers(): Array = TODO() - } - override fun isDefaultValue(value: X509TrustManager?): Boolean = - !value?.javaClass?.name.orEmpty().startsWith("okhttp") + !value + ?.javaClass + ?.name + .orEmpty() + .startsWith("okhttp") } object HostnameVerifierOverride : Override { override fun Interceptor.Chain.value(): HostnameVerifier = hostnameVerifier - override fun Interceptor.Chain.withOverride(value: HostnameVerifier): Interceptor.Chain = - withHostnameVerifier(value) + override fun Interceptor.Chain.withOverride(value: HostnameVerifier): Interceptor.Chain = withHostnameVerifier(value) - override fun OkHttpClient.Builder.withOverride(value: HostnameVerifier): OkHttpClient.Builder = - hostnameVerifier(value) + override fun OkHttpClient.Builder.withOverride(value: HostnameVerifier): OkHttpClient.Builder = hostnameVerifier(value) override val nonDefaultValue: HostnameVerifier = HostnameVerifier { _, _ -> true } @@ -503,14 +634,14 @@ class InterceptorOverridesTest { object CertificatePinnerOverride : Override { override fun Interceptor.Chain.value(): CertificatePinner = certificatePinner - override fun Interceptor.Chain.withOverride(value: CertificatePinner): Interceptor.Chain = - withCertificatePinner(value) + override fun Interceptor.Chain.withOverride(value: CertificatePinner): Interceptor.Chain = withCertificatePinner(value) - override fun OkHttpClient.Builder.withOverride(value: CertificatePinner): OkHttpClient.Builder = - certificatePinner(value) + override fun OkHttpClient.Builder.withOverride(value: CertificatePinner): OkHttpClient.Builder = certificatePinner(value) override val nonDefaultValue: CertificatePinner = - CertificatePinner.Builder().add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") + CertificatePinner + .Builder() + .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") .build() override val badValue: CertificatePinner = @@ -519,30 +650,10 @@ class InterceptorOverridesTest { override fun isDefaultValue(value: CertificatePinner): Boolean = value.pins.isEmpty() } - object ConnectionPoolOverride : Override { - override fun Interceptor.Chain.value(): ConnectionPool = connectionPool - - override fun Interceptor.Chain.withOverride(value: ConnectionPool): Interceptor.Chain = withConnectionPool(value) - - override fun OkHttpClient.Builder.withOverride(value: ConnectionPool): OkHttpClient.Builder = - connectionPool(value) - - override val nonDefaultValue: ConnectionPool = ConnectionPool(keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES) - - override val badValue: ConnectionPool = ConnectionPool( - keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES, connectionListener = object : ConnectionListener() { - override fun connectStart(route: Route, call: Call): Unit = TODO() - }) - - override fun isDefaultValue(value: ConnectionPool): Boolean = - value.delegate.keepAliveDurationNs == 5.minutes.inWholeNanoseconds - } - object ConnectTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = connectTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = - withConnectTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withConnectTimeout(value.toLong(), TimeUnit.MILLISECONDS) override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = connectTimeout(value.toLong(), TimeUnit.MILLISECONDS) @@ -550,7 +661,7 @@ class InterceptorOverridesTest { override val nonDefaultValue: Int = 5000 override val badValue: Int - get() = throw UnsupportedOperationException() + get() = 1 override fun isDefaultValue(value: Int): Boolean = value == 10000 } @@ -558,17 +669,14 @@ class InterceptorOverridesTest { object ReadTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = readTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = - withReadTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withReadTimeout(value.toLong(), TimeUnit.MILLISECONDS) - override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = - readTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = readTimeout(value.toLong(), TimeUnit.MILLISECONDS) override val nonDefaultValue: Int = 5000 override val badValue: Int - get() = throw UnsupportedOperationException() - + get() = 1 override fun isDefaultValue(value: Int): Boolean = value == 10000 } @@ -576,17 +684,14 @@ class InterceptorOverridesTest { object WriteTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = writeTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = - withWriteTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withWriteTimeout(value.toLong(), TimeUnit.MILLISECONDS) - override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = - writeTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = writeTimeout(value.toLong(), TimeUnit.MILLISECONDS) override val nonDefaultValue: Int = 5000 override val badValue: Int - get() = throw UnsupportedOperationException() - + get() = 1 override fun isDefaultValue(value: Int): Boolean = value == 10000 } @@ -594,26 +699,28 @@ class InterceptorOverridesTest { object RetryOnConnectionFailureOverride : Override { override fun Interceptor.Chain.value(): Boolean = retryOnConnectionFailure - override fun Interceptor.Chain.withOverride(value: Boolean): Interceptor.Chain = - withRetryOnConnectionFailure(value) + override fun Interceptor.Chain.withOverride(value: Boolean): Interceptor.Chain = withRetryOnConnectionFailure(value) - override fun OkHttpClient.Builder.withOverride(value: Boolean): OkHttpClient.Builder = - retryOnConnectionFailure(value) + override fun OkHttpClient.Builder.withOverride(value: Boolean): OkHttpClient.Builder = retryOnConnectionFailure(value) override val nonDefaultValue: Boolean = false override val badValue: Boolean - get() = throw UnsupportedOperationException() + get() = false override fun isDefaultValue(value: Boolean): Boolean = value } } private fun enableTls() { - client = client.newBuilder().sslSocketFactory( - handshakeCertificates.sslSocketFactory(), - handshakeCertificates.trustManager, - ).hostnameVerifier(RecordingHostnameVerifier()).build() + client = + client + .newBuilder() + .sslSocketFactory( + handshakeCertificates.sslSocketFactory(), + handshakeCertificates.trustManager, + ).hostnameVerifier(RecordingHostnameVerifier()) + .build() server.useHttps(handshakeCertificates.sslSocketFactory()) } } diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt index 43828141e78f..a8da8ff5107b 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt @@ -1432,7 +1432,7 @@ class KotlinSourceModernTest { override fun withSslSocketFactory( sslSocketFactory: SSLSocketFactory?, - x509TrustManager: X509TrustManager? + x509TrustManager: X509TrustManager?, ): Interceptor.Chain { TODO() } @@ -1444,9 +1444,5 @@ class KotlinSourceModernTest { override fun withCertificatePinner(certificatePinner: CertificatePinner): Interceptor.Chain { TODO() } - - override fun withConnectionPool(connectionPool: ConnectionPool): Interceptor.Chain { - TODO() - } } } From 0c665c183e023fc4c30cd57abfdaa176d60e5933 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Wed, 10 Dec 2025 23:08:07 +0000 Subject: [PATCH 14/21] revert --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a4f2c88cdcc4..4b917af5ddce 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.12.0" +agp = "8.13.1" biz-aQute-bnd = "7.1.0" checkStyle = "12.2.0" com-squareup-moshi = "1.15.2" From d450c1b1938ebf80519d62a94249c80cb07f412c Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Thu, 11 Dec 2025 00:06:17 +0000 Subject: [PATCH 15/21] More testing --- .../kotlin/mockwebserver3/MockWebServer.kt | 10 +++++ .../kotlin/mockwebserver3/SocketEffect.kt | 5 +++ .../kotlin/okhttp3/Interceptor.kt | 5 +++ .../okhttp3/internal/connection/RealCall.kt | 2 +- .../internal/http/RealInterceptorChain.kt | 42 ++++++++++++++++--- .../okhttp3/InterceptorOverridesTest.kt | 40 +++++++++++++++--- .../kotlin/okhttp3/KotlinSourceModernTest.kt | 4 ++ 7 files changed, 97 insertions(+), 11 deletions(-) diff --git a/mockwebserver/src/main/kotlin/mockwebserver3/MockWebServer.kt b/mockwebserver/src/main/kotlin/mockwebserver3/MockWebServer.kt index 71b5d952a0c3..f3a82141e2cc 100644 --- a/mockwebserver/src/main/kotlin/mockwebserver3/MockWebServer.kt +++ b/mockwebserver/src/main/kotlin/mockwebserver3/MockWebServer.kt @@ -392,6 +392,10 @@ public class MockWebServer : Closeable { ) socket.close() } else { + if (peek.onRequestStart is SocketEffect.Delay) { + Thread.sleep(peek.onRequestStart.delayMs) + } + openClientSockets.add(socket) serveConnection(nextConnectionIndex++, socket, peek) } @@ -972,6 +976,12 @@ public class MockWebServer : Closeable { socket.sleepWhileOpen(TimeUnit.MINUTES.toNanos(60)) error("expected timeout") } + + is SocketEffect.Delay -> { + // Sleep for delay period. + socket.sleepWhileOpen(TimeUnit.MILLISECONDS.toNanos(effect.delayMs)) + return false + } } return true diff --git a/mockwebserver/src/main/kotlin/mockwebserver3/SocketEffect.kt b/mockwebserver/src/main/kotlin/mockwebserver3/SocketEffect.kt index c04cf3a0ce00..75cf61a68bf0 100644 --- a/mockwebserver/src/main/kotlin/mockwebserver3/SocketEffect.kt +++ b/mockwebserver/src/main/kotlin/mockwebserver3/SocketEffect.kt @@ -49,4 +49,9 @@ public sealed interface SocketEffect { /** Stop processing this. */ public object Stall : SocketEffect + + /** Delay before processing. */ + public class Delay( + public val delayMs: Long, + ) : SocketEffect } diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt index ede5732b236e..e0a0aa0e74bd 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt @@ -283,5 +283,10 @@ fun interface Interceptor { * Returns the [ConnectionPool] for the OkHttpClient, or an override from the Call.Chain. */ val connectionPool: ConnectionPool + + /** + * Returns a new chain with the specified [ConnectionPool]. + */ + fun withConnectionPool(connectionPool: ConnectionPool): Chain } } diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt index c8e5f619ac16..588f1d5cb3bb 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt @@ -273,7 +273,7 @@ class RealCall( val routePlanner = RealRoutePlanner( taskRunner = client.taskRunner, - connectionPool = connectionPool, + connectionPool = chain.connectionPool.delegate, readTimeoutMillis = chain.readTimeoutMillis, writeTimeoutMillis = chain.writeTimeoutMillis, socketConnectTimeoutMillis = chain.connectTimeoutMillis, diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt index 41eff3792b9b..2f0e495c8d6e 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt @@ -40,6 +40,7 @@ import okhttp3.Response import okhttp3.internal.checkDuration import okhttp3.internal.connection.Exchange import okhttp3.internal.connection.RealCall +import okhttp3.internal.tls.CertificateChainCleaner /** * A concrete interceptor chain that carries the entire interceptor chain: all application @@ -71,6 +72,7 @@ class RealInterceptorChain( override val socketFactory: SocketFactory, override val sslSocketFactoryOrNull: SSLSocketFactory?, override val x509TrustManagerOrNull: X509TrustManager?, + val certificateChainCleaner: CertificateChainCleaner?, ) : Interceptor.Chain { internal constructor( call: RealCall, @@ -102,6 +104,7 @@ class RealInterceptorChain( client.socketFactory, client.sslSocketFactoryOrNull, client.x509TrustManager, + client.certificateChainCleaner, ) private var calls: Int = 0 @@ -127,6 +130,7 @@ class RealInterceptorChain( socketFactory: SocketFactory = this.socketFactory, sslSocketFactory: SSLSocketFactory? = this.sslSocketFactoryOrNull, x509TrustManager: X509TrustManager? = this.x509TrustManagerOrNull, + certificateChainCleaner: CertificateChainCleaner? = this.certificateChainCleaner, ) = RealInterceptorChain( call, interceptors, @@ -150,6 +154,7 @@ class RealInterceptorChain( socketFactory, sslSocketFactory, x509TrustManager, + certificateChainCleaner, ) override fun connection(): Connection? = exchange?.connection @@ -162,7 +167,7 @@ class RealInterceptorChain( ): Interceptor.Chain { check(exchange == null) { "Timeouts can't be adjusted in a network interceptor" } - return copy(connectTimeoutMillis = checkDuration("connectTimeout", timeout.toLong(), unit)) + return copy(connectTimeoutMillis = checkDuration("connectTimeout", timeout, unit)) } override fun readTimeoutMillis(): Int = readTimeoutMillis @@ -173,7 +178,7 @@ class RealInterceptorChain( ): Interceptor.Chain { check(exchange == null) { "Timeouts can't be adjusted in a network interceptor" } - return copy(readTimeoutMillis = checkDuration("readTimeout", timeout.toLong(), unit)) + return copy(readTimeoutMillis = checkDuration("readTimeout", timeout, unit)) } override fun writeTimeoutMillis(): Int = writeTimeoutMillis @@ -184,7 +189,7 @@ class RealInterceptorChain( ): Interceptor.Chain { check(exchange == null) { "Timeouts can't be adjusted in a network interceptor" } - return copy(writeTimeoutMillis = checkDuration("writeTimeout", timeout.toLong(), unit)) + return copy(writeTimeoutMillis = checkDuration("writeTimeout", timeout, unit)) } override fun withDns(dns: Dns): Interceptor.Chain { @@ -247,7 +252,22 @@ class RealInterceptorChain( ): Interceptor.Chain { check(exchange == null) { "sslSocketFactory can't be adjusted in a network interceptor" } - return copy(sslSocketFactory = sslSocketFactory, x509TrustManager = x509TrustManager) + if (sslSocketFactory != null && x509TrustManager != null) { + val newCertificateChainCleaner = CertificateChainCleaner.get(x509TrustManager) + return copy( + sslSocketFactory = sslSocketFactory, + x509TrustManager = x509TrustManager, + certificateChainCleaner = newCertificateChainCleaner, + certificatePinner = certificatePinner.withCertificateChainCleaner(newCertificateChainCleaner) + ) + } else { + return copy( + sslSocketFactory = null, + x509TrustManager = null, + certificateChainCleaner = null, + ) + } + } override fun withHostnameVerifier(hostnameVerifier: HostnameVerifier): Interceptor.Chain { @@ -259,7 +279,19 @@ class RealInterceptorChain( override fun withCertificatePinner(certificatePinner: CertificatePinner): Interceptor.Chain { check(exchange == null) { "certificatePinner can't be adjusted in a network interceptor" } - return copy(certificatePinner = certificatePinner) + val newCertificatePinner = if (certificateChainCleaner != null) { + certificatePinner.withCertificateChainCleaner(certificateChainCleaner) + } else { + certificatePinner + } + + return copy(certificatePinner = newCertificatePinner) + } + + override fun withConnectionPool(connectionPool: ConnectionPool): Interceptor.Chain { + check(exchange == null) { "connectionPool can't be adjusted in a network interceptor" } + + return copy(connectionPool = connectionPool) } override fun call(): Call = call diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt index eec09cad22be..b3eae141d4f6 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt @@ -28,10 +28,11 @@ import javax.net.ssl.X509TrustManager import kotlin.time.Duration.Companion.minutes import mockwebserver3.MockResponse import mockwebserver3.MockWebServer -import mockwebserver3.SocketHandler +import mockwebserver3.SocketEffect import mockwebserver3.junit5.StartStop import okhttp3.CertificatePinner.Companion.pin import okhttp3.Headers.Companion.headersOf +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.internal.connection.ConnectionListener import okhttp3.internal.platform.Platform import okhttp3.testing.PlatformRule @@ -41,7 +42,6 @@ import okio.Path.Companion.toPath import okio.fakefilesystem.FakeFileSystem import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension -import org.opentest4j.AssertionFailedError @Burst class InterceptorOverridesTest { @@ -71,6 +71,7 @@ class InterceptorOverridesTest { OverrideParam.Cache, OverrideParam.CertificatePinner, OverrideParam.ConnectTimeout, + OverrideParam.ConnectionPool, OverrideParam.CookieJar, OverrideParam.Dns, OverrideParam.HostnameVerifier, @@ -132,6 +133,7 @@ class InterceptorOverridesTest { OverrideParam.Cache, OverrideParam.CertificatePinner, OverrideParam.ConnectTimeout, + OverrideParam.ConnectionPool, OverrideParam.CookieJar, OverrideParam.Dns, OverrideParam.HostnameVerifier, @@ -182,6 +184,7 @@ class InterceptorOverridesTest { OverrideParam.Cache, OverrideParam.CertificatePinner, OverrideParam.ConnectTimeout, + OverrideParam.ConnectionPool, OverrideParam.CookieJar, OverrideParam.Dns, OverrideParam.HostnameVerifier, @@ -257,9 +260,17 @@ class InterceptorOverridesTest { overrideBadImplementation(override = override.override, testItFails = testItFails) } + OverrideParam.WriteTimeout -> { + server.enqueue( + MockResponse.Builder().onRequestStart(SocketEffect.Delay(1000)).build(), + ) + + overrideBadImplementation(override = override.override, testItFails = testItFails, bodySize = 1024 * 256) + } + OverrideParam.ReadTimeout -> { server.enqueue( - MockResponse.Builder().bodyDelay(1, TimeUnit.SECONDS).build(), + MockResponse.Builder().bodyDelay(1, TimeUnit.SECONDS).body("Hello").build(), ) overrideBadImplementation(override = override.override, testItFails = testItFails) @@ -288,6 +299,7 @@ class InterceptorOverridesTest { testItFails: Boolean, badValue: T = override.badValue, goodValue: T = override.nonDefaultValue, + bodySize: Int? = null ) { with(override) { client = @@ -310,8 +322,8 @@ class InterceptorOverridesTest { server.enqueue( MockResponse(), ) - val call = client.newCall(Request(server.url("/"))) - val result = runCatching { call.execute().close() } + val call = client.newCall(Request(server.url("/"), body = bodySize?.let { ByteArray(bodySize).toRequestBody() })) + val result = runCatching { call.execute().body.bytes() } if (testItFails) { assertThat(result).isFailure() @@ -333,6 +345,7 @@ class InterceptorOverridesTest { override val paramName: String get() = "Timeouts" }, + ConnectionPool(Override.ConnectionPoolOverride), CookieJar(Override.CookieJarOverride), Dns(Override.DnsOverride), HostnameVerifier( @@ -604,6 +617,7 @@ class InterceptorOverridesTest { x509Certificates: Array, s: String, ) { + TODO() } override fun getAcceptedIssuers(): Array = arrayOf() @@ -650,6 +664,22 @@ class InterceptorOverridesTest { override fun isDefaultValue(value: CertificatePinner): Boolean = value.pins.isEmpty() } + object ConnectionPoolOverride : Override { + override fun Interceptor.Chain.value(): ConnectionPool = connectionPool + + override fun Interceptor.Chain.withOverride(value: ConnectionPool): Interceptor.Chain = withConnectionPool(value) + + override fun OkHttpClient.Builder.withOverride(value: ConnectionPool): OkHttpClient.Builder = connectionPool(value) + + override val nonDefaultValue: ConnectionPool = ConnectionPool(keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES) + + override val badValue: ConnectionPool = ConnectionPool(keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES, connectionListener = object : ConnectionListener() { + override fun connectStart(route: Route, call: Call): Unit = TODO() + }) + + override fun isDefaultValue(value: ConnectionPool): Boolean = value.delegate.keepAliveDurationNs == 5.minutes.inWholeNanoseconds + } + object ConnectTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = connectTimeoutMillis() diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt index a8da8ff5107b..95d5c706ddfb 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt @@ -1444,5 +1444,9 @@ class KotlinSourceModernTest { override fun withCertificatePinner(certificatePinner: CertificatePinner): Interceptor.Chain { TODO() } + + override fun withConnectionPool(connectionPool: ConnectionPool): Interceptor.Chain { + TODO() + } } } From e86a85833250eb24bb30ac9a432c256812ba53d9 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Thu, 11 Dec 2025 07:57:25 +0000 Subject: [PATCH 16/21] simplify test --- .../kotlin/mockwebserver3/MockWebServer.kt | 10 -- .../kotlin/mockwebserver3/SocketEffect.kt | 5 - .../okhttp3/InterceptorOverridesTest.kt | 145 ++++++++++++++---- 3 files changed, 113 insertions(+), 47 deletions(-) diff --git a/mockwebserver/src/main/kotlin/mockwebserver3/MockWebServer.kt b/mockwebserver/src/main/kotlin/mockwebserver3/MockWebServer.kt index f3a82141e2cc..71b5d952a0c3 100644 --- a/mockwebserver/src/main/kotlin/mockwebserver3/MockWebServer.kt +++ b/mockwebserver/src/main/kotlin/mockwebserver3/MockWebServer.kt @@ -392,10 +392,6 @@ public class MockWebServer : Closeable { ) socket.close() } else { - if (peek.onRequestStart is SocketEffect.Delay) { - Thread.sleep(peek.onRequestStart.delayMs) - } - openClientSockets.add(socket) serveConnection(nextConnectionIndex++, socket, peek) } @@ -976,12 +972,6 @@ public class MockWebServer : Closeable { socket.sleepWhileOpen(TimeUnit.MINUTES.toNanos(60)) error("expected timeout") } - - is SocketEffect.Delay -> { - // Sleep for delay period. - socket.sleepWhileOpen(TimeUnit.MILLISECONDS.toNanos(effect.delayMs)) - return false - } } return true diff --git a/mockwebserver/src/main/kotlin/mockwebserver3/SocketEffect.kt b/mockwebserver/src/main/kotlin/mockwebserver3/SocketEffect.kt index 75cf61a68bf0..c04cf3a0ce00 100644 --- a/mockwebserver/src/main/kotlin/mockwebserver3/SocketEffect.kt +++ b/mockwebserver/src/main/kotlin/mockwebserver3/SocketEffect.kt @@ -49,9 +49,4 @@ public sealed interface SocketEffect { /** Stop processing this. */ public object Stall : SocketEffect - - /** Delay before processing. */ - public class Delay( - public val delayMs: Long, - ) : SocketEffect } diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt index b3eae141d4f6..a0a2b314b664 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt @@ -11,6 +11,8 @@ import assertk.assertions.isNotSameInstanceAs import assertk.assertions.isSuccess import assertk.assertions.isTrue import assertk.fail +import java.io.InputStream +import java.io.OutputStream import java.net.InetSocketAddress import java.net.Proxy import java.net.ProxySelector @@ -36,10 +38,16 @@ import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.internal.connection.ConnectionListener import okhttp3.internal.platform.Platform import okhttp3.testing.PlatformRule +import okio.Buffer import okio.ForwardingFileSystem +import okio.ForwardingSink +import okio.IOException import okio.Path import okio.Path.Companion.toPath +import okio.buffer import okio.fakefilesystem.FakeFileSystem +import okio.sink +import okio.source import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension @@ -176,6 +184,7 @@ class InterceptorOverridesTest { * Test that if we set a bad implementation on the OkHttpClient directly, that we can avoid the failure * by setting a good override. */ + // TODO consider replacing with individual methods @Test fun testOverrideBadImplementation( override: OverrideParam = @@ -225,9 +234,22 @@ class InterceptorOverridesTest { } OverrideParam.RetryOnConnectionFailure -> { - server.enqueue( - MockResponse.Builder().failHandshake().build(), - ) + var first = true + client = client.newBuilder() + .dns { host -> + val hosts = Dns.SYSTEM.lookup(host) + hosts + hosts + } + .socketFactory(DelayingSocketFactory(onConnect = { + if (first) { + println("fail") + first = false + throw IOException() + } + + println("work") + })) + .build() overrideBadImplementation( override = Override.RetryOnConnectionFailureOverride, @@ -242,7 +264,7 @@ class InterceptorOverridesTest { overrideBadImplementation( override = Override.SslSocketFactoryOverride, testItFails = testItFails, - goodValue = client.sslSocketFactory, + goodValue = handshakeCertificates.sslSocketFactory(), ) } @@ -251,7 +273,7 @@ class InterceptorOverridesTest { overrideBadImplementation( override = Override.X509TrustManagerOverride, testItFails = testItFails, - goodValue = client.x509TrustManager, + goodValue = handshakeCertificates.trustManager, ) } @@ -261,17 +283,19 @@ class InterceptorOverridesTest { } OverrideParam.WriteTimeout -> { - server.enqueue( - MockResponse.Builder().onRequestStart(SocketEffect.Delay(1000)).build(), - ) + client = client.newBuilder().socketFactory(DelayingSocketFactory(onWrite = { Thread.sleep(100L) })).build() - overrideBadImplementation(override = override.override, testItFails = testItFails, bodySize = 1024 * 256) + overrideBadImplementation(override = override.override, testItFails = testItFails, bodySize = 100) } OverrideParam.ReadTimeout -> { - server.enqueue( - MockResponse.Builder().bodyDelay(1, TimeUnit.SECONDS).body("Hello").build(), - ) + client = client.newBuilder().socketFactory(DelayingSocketFactory(onRead = { Thread.sleep(100L) })).build() + + overrideBadImplementation(override = override.override, testItFails = testItFails) + } + + OverrideParam.ConnectTimeout -> { + client = client.newBuilder().socketFactory(DelayingSocketFactory(onConnect = { Thread.sleep(100L) })).build() overrideBadImplementation(override = override.override, testItFails = testItFails) } @@ -285,7 +309,11 @@ class InterceptorOverridesTest { .add(server.hostName, pin(handshakeCertificates.trustManager.acceptedIssuers.first())) .build() - overrideBadImplementation(override = Override.CertificatePinnerOverride, testItFails = testItFails, goodValue = pinner) + overrideBadImplementation( + override = Override.CertificatePinnerOverride, + testItFails = testItFails, + goodValue = pinner + ) } else -> { @@ -375,6 +403,41 @@ class InterceptorOverridesTest { get() = override.paramName ?: name.replaceFirstChar { it.lowercase(getDefault()) } } + class DelayingSocketFactory( + val onConnect: () -> Unit = {}, + val onRead: () -> Unit = {}, + val onWrite: () -> Unit = {}, + ) : DelegatingSocketFactory(getDefault()) { + override fun createSocket(): Socket { + return object : Socket() { + override fun connect(endpoint: SocketAddress?, timeout: Int) { + onConnect() + super.connect(endpoint, timeout) + } + + override fun getInputStream(): InputStream { + return object : ForwardingSource(super.getInputStream().source()) { + override fun read(sink: Buffer, byteCount: Long): Long { + onRead() + + return super.read(sink, byteCount) + } + }.buffer().inputStream() + } + + override fun getOutputStream(): OutputStream { + return object : ForwardingSink(super.getOutputStream().sink()) { + override fun write(source: Buffer, byteCount: Long) { + onWrite() + + return super.write(source, byteCount) + } + }.buffer().outputStream() + } + } + } + } + sealed interface Override { fun Interceptor.Chain.value(): T @@ -545,9 +608,11 @@ class InterceptorOverridesTest { object ProxyAuthenticatorOverride : Override { override fun Interceptor.Chain.value(): Authenticator = proxyAuthenticator - override fun Interceptor.Chain.withOverride(value: Authenticator): Interceptor.Chain = withProxyAuthenticator(value) + override fun Interceptor.Chain.withOverride(value: Authenticator): Interceptor.Chain = + withProxyAuthenticator(value) - override fun OkHttpClient.Builder.withOverride(value: Authenticator): OkHttpClient.Builder = proxyAuthenticator(value) + override fun OkHttpClient.Builder.withOverride(value: Authenticator): OkHttpClient.Builder = + proxyAuthenticator(value) override val nonDefaultValue: Authenticator = Authenticator { route, response -> response.request } @@ -634,9 +699,11 @@ class InterceptorOverridesTest { object HostnameVerifierOverride : Override { override fun Interceptor.Chain.value(): HostnameVerifier = hostnameVerifier - override fun Interceptor.Chain.withOverride(value: HostnameVerifier): Interceptor.Chain = withHostnameVerifier(value) + override fun Interceptor.Chain.withOverride(value: HostnameVerifier): Interceptor.Chain = + withHostnameVerifier(value) - override fun OkHttpClient.Builder.withOverride(value: HostnameVerifier): OkHttpClient.Builder = hostnameVerifier(value) + override fun OkHttpClient.Builder.withOverride(value: HostnameVerifier): OkHttpClient.Builder = + hostnameVerifier(value) override val nonDefaultValue: HostnameVerifier = HostnameVerifier { _, _ -> true } @@ -648,9 +715,11 @@ class InterceptorOverridesTest { object CertificatePinnerOverride : Override { override fun Interceptor.Chain.value(): CertificatePinner = certificatePinner - override fun Interceptor.Chain.withOverride(value: CertificatePinner): Interceptor.Chain = withCertificatePinner(value) + override fun Interceptor.Chain.withOverride(value: CertificatePinner): Interceptor.Chain = + withCertificatePinner(value) - override fun OkHttpClient.Builder.withOverride(value: CertificatePinner): OkHttpClient.Builder = certificatePinner(value) + override fun OkHttpClient.Builder.withOverride(value: CertificatePinner): OkHttpClient.Builder = + certificatePinner(value) override val nonDefaultValue: CertificatePinner = CertificatePinner @@ -669,21 +738,27 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.withOverride(value: ConnectionPool): Interceptor.Chain = withConnectionPool(value) - override fun OkHttpClient.Builder.withOverride(value: ConnectionPool): OkHttpClient.Builder = connectionPool(value) + override fun OkHttpClient.Builder.withOverride(value: ConnectionPool): OkHttpClient.Builder = + connectionPool(value) override val nonDefaultValue: ConnectionPool = ConnectionPool(keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES) - override val badValue: ConnectionPool = ConnectionPool(keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES, connectionListener = object : ConnectionListener() { - override fun connectStart(route: Route, call: Call): Unit = TODO() - }) + override val badValue: ConnectionPool = ConnectionPool( + keepAliveDuration = 1, + timeUnit = TimeUnit.MINUTES, + connectionListener = object : ConnectionListener() { + override fun connectStart(route: Route, call: Call): Unit = TODO() + }) - override fun isDefaultValue(value: ConnectionPool): Boolean = value.delegate.keepAliveDurationNs == 5.minutes.inWholeNanoseconds + override fun isDefaultValue(value: ConnectionPool): Boolean = + value.delegate.keepAliveDurationNs == 5.minutes.inWholeNanoseconds } object ConnectTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = connectTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withConnectTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = + withConnectTimeout(value.toLong(), TimeUnit.MILLISECONDS) override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = connectTimeout(value.toLong(), TimeUnit.MILLISECONDS) @@ -699,9 +774,11 @@ class InterceptorOverridesTest { object ReadTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = readTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withReadTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = + withReadTimeout(value.toLong(), TimeUnit.MILLISECONDS) - override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = readTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = + readTimeout(value.toLong(), TimeUnit.MILLISECONDS) override val nonDefaultValue: Int = 5000 @@ -714,9 +791,11 @@ class InterceptorOverridesTest { object WriteTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = writeTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withWriteTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = + withWriteTimeout(value.toLong(), TimeUnit.MILLISECONDS) - override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = writeTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = + writeTimeout(value.toLong(), TimeUnit.MILLISECONDS) override val nonDefaultValue: Int = 5000 @@ -729,9 +808,11 @@ class InterceptorOverridesTest { object RetryOnConnectionFailureOverride : Override { override fun Interceptor.Chain.value(): Boolean = retryOnConnectionFailure - override fun Interceptor.Chain.withOverride(value: Boolean): Interceptor.Chain = withRetryOnConnectionFailure(value) + override fun Interceptor.Chain.withOverride(value: Boolean): Interceptor.Chain = + withRetryOnConnectionFailure(value) - override fun OkHttpClient.Builder.withOverride(value: Boolean): OkHttpClient.Builder = retryOnConnectionFailure(value) + override fun OkHttpClient.Builder.withOverride(value: Boolean): OkHttpClient.Builder = + retryOnConnectionFailure(value) override val nonDefaultValue: Boolean = false @@ -749,7 +830,7 @@ class InterceptorOverridesTest { .sslSocketFactory( handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager, - ).hostnameVerifier(RecordingHostnameVerifier()) + ) .build() server.useHttps(handshakeCertificates.sslSocketFactory()) } From 101e588a2d5ce0a0026c400d41397babd92b51ca Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sat, 13 Dec 2025 10:56:18 +0000 Subject: [PATCH 17/21] Fix test --- .../internal/http/RealInterceptorChain.kt | 14 +- .../okhttp3/InterceptorOverridesTest.kt | 504 ++++++++---------- 2 files changed, 232 insertions(+), 286 deletions(-) diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt index 2f0e495c8d6e..9d62383a464b 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt @@ -258,7 +258,7 @@ class RealInterceptorChain( sslSocketFactory = sslSocketFactory, x509TrustManager = x509TrustManager, certificateChainCleaner = newCertificateChainCleaner, - certificatePinner = certificatePinner.withCertificateChainCleaner(newCertificateChainCleaner) + certificatePinner = certificatePinner.withCertificateChainCleaner(newCertificateChainCleaner), ) } else { return copy( @@ -267,7 +267,6 @@ class RealInterceptorChain( certificateChainCleaner = null, ) } - } override fun withHostnameVerifier(hostnameVerifier: HostnameVerifier): Interceptor.Chain { @@ -279,11 +278,12 @@ class RealInterceptorChain( override fun withCertificatePinner(certificatePinner: CertificatePinner): Interceptor.Chain { check(exchange == null) { "certificatePinner can't be adjusted in a network interceptor" } - val newCertificatePinner = if (certificateChainCleaner != null) { - certificatePinner.withCertificateChainCleaner(certificateChainCleaner) - } else { - certificatePinner - } + val newCertificatePinner = + if (certificateChainCleaner != null) { + certificatePinner.withCertificateChainCleaner(certificateChainCleaner) + } else { + certificatePinner + } return copy(certificatePinner = newCertificatePinner) } diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt index a0a2b314b664..2c396fe1bd1f 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt @@ -8,9 +8,9 @@ import assertk.assertions.hasMessage import assertk.assertions.isFailure import assertk.assertions.isFalse import assertk.assertions.isNotSameInstanceAs -import assertk.assertions.isSuccess import assertk.assertions.isTrue -import assertk.fail +import java.io.FilterInputStream +import java.io.FilterOutputStream import java.io.InputStream import java.io.OutputStream import java.net.InetSocketAddress @@ -24,30 +24,26 @@ import java.util.Locale.getDefault import java.util.concurrent.TimeUnit import javax.net.SocketFactory import javax.net.ssl.HostnameVerifier +import javax.net.ssl.SSLException import javax.net.ssl.SSLSocket import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509TrustManager import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.nanoseconds import mockwebserver3.MockResponse import mockwebserver3.MockWebServer -import mockwebserver3.SocketEffect import mockwebserver3.junit5.StartStop import okhttp3.CertificatePinner.Companion.pin import okhttp3.Headers.Companion.headersOf -import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.internal.connection.ConnectionListener import okhttp3.internal.platform.Platform import okhttp3.testing.PlatformRule -import okio.Buffer +import okio.BufferedSink import okio.ForwardingFileSystem -import okio.ForwardingSink import okio.IOException import okio.Path import okio.Path.Companion.toPath -import okio.buffer import okio.fakefilesystem.FakeFileSystem -import okio.sink -import okio.source import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension @@ -60,10 +56,7 @@ class InterceptorOverridesTest { private val server = MockWebServer() // Can't use test instance with overrides - private var client = - OkHttpClient - .Builder() - .build() + private var client = OkHttpClient.Builder().build() private val handshakeCertificates = platform.localhostHandshakeCertificates() @@ -73,26 +66,25 @@ class InterceptorOverridesTest { */ @Test fun testOverrideInApplicationInterceptor( - override: OverrideParam = - burstValues( - OverrideParam.Authenticator, - OverrideParam.Cache, - OverrideParam.CertificatePinner, - OverrideParam.ConnectTimeout, - OverrideParam.ConnectionPool, - OverrideParam.CookieJar, - OverrideParam.Dns, - OverrideParam.HostnameVerifier, - OverrideParam.Proxy, - OverrideParam.ProxyAuthenticator, - OverrideParam.ProxySelector, - OverrideParam.ReadTimeout, - OverrideParam.RetryOnConnectionFailure, - OverrideParam.SocketFactory, - OverrideParam.SslSocketFactory, - OverrideParam.WriteTimeout, - OverrideParam.X509TrustManager, - ), + override: OverrideParam = burstValues( + OverrideParam.Authenticator, + OverrideParam.Cache, + OverrideParam.CertificatePinner, + OverrideParam.ConnectTimeout, + OverrideParam.ConnectionPool, + OverrideParam.CookieJar, + OverrideParam.Dns, + OverrideParam.HostnameVerifier, + OverrideParam.Proxy, + OverrideParam.ProxyAuthenticator, + OverrideParam.ProxySelector, + OverrideParam.ReadTimeout, + OverrideParam.RetryOnConnectionFailure, + OverrideParam.SocketFactory, + OverrideParam.SslSocketFactory, + OverrideParam.WriteTimeout, + OverrideParam.X509TrustManager, + ), isDefault: Boolean, ) { fun Override.testApplicationInterceptor(chain: Interceptor.Chain): Response { @@ -112,15 +104,12 @@ class InterceptorOverridesTest { } with(override.override) { - client = - client - .newBuilder() - .addInterceptor { chain -> - testApplicationInterceptor(chain) - }.addNetworkInterceptor { chain -> - assertThat(isDefaultValue(chain.value())).isFalse() - chain.proceed(chain.request()) - }.build() + client = client.newBuilder().addInterceptor { chain -> + testApplicationInterceptor(chain) + }.addNetworkInterceptor { chain -> + assertThat(isDefaultValue(chain.value())).isFalse() + chain.proceed(chain.request()) + }.build() server.enqueue( MockResponse(), @@ -135,42 +124,38 @@ class InterceptorOverridesTest { */ @Test fun testOverrideInNetworkInterceptor( - override: OverrideParam = - burstValues( - OverrideParam.Authenticator, - OverrideParam.Cache, - OverrideParam.CertificatePinner, - OverrideParam.ConnectTimeout, - OverrideParam.ConnectionPool, - OverrideParam.CookieJar, - OverrideParam.Dns, - OverrideParam.HostnameVerifier, - OverrideParam.Proxy, - OverrideParam.ProxyAuthenticator, - OverrideParam.ProxySelector, - OverrideParam.ReadTimeout, - OverrideParam.RetryOnConnectionFailure, - OverrideParam.SocketFactory, - OverrideParam.SslSocketFactory, - OverrideParam.WriteTimeout, - OverrideParam.X509TrustManager, - ), + override: OverrideParam = burstValues( + OverrideParam.Authenticator, + OverrideParam.Cache, + OverrideParam.CertificatePinner, + OverrideParam.ConnectTimeout, + OverrideParam.ConnectionPool, + OverrideParam.CookieJar, + OverrideParam.Dns, + OverrideParam.HostnameVerifier, + OverrideParam.Proxy, + OverrideParam.ProxyAuthenticator, + OverrideParam.ProxySelector, + OverrideParam.ReadTimeout, + OverrideParam.RetryOnConnectionFailure, + OverrideParam.SocketFactory, + OverrideParam.SslSocketFactory, + OverrideParam.WriteTimeout, + OverrideParam.X509TrustManager, + ), ) { with(override.override) { - client = - client - .newBuilder() - .addNetworkInterceptor { chain -> - assertThat(isDefaultValue(chain.value())).isTrue() + client = client.newBuilder().addNetworkInterceptor { chain -> + assertThat(isDefaultValue(chain.value())).isTrue() - assertFailure { - chain.withOverride( - nonDefaultValue, - ) - }.hasMessage("${override.paramName} can't be adjusted in a network interceptor") + assertFailure { + chain.withOverride( + nonDefaultValue, + ) + }.hasMessage("${override.paramName} can't be adjusted in a network interceptor") - chain.proceed(chain.request()) - }.build() + chain.proceed(chain.request()) + }.build() server.enqueue( MockResponse(), @@ -184,42 +169,36 @@ class InterceptorOverridesTest { * Test that if we set a bad implementation on the OkHttpClient directly, that we can avoid the failure * by setting a good override. */ - // TODO consider replacing with individual methods @Test fun testOverrideBadImplementation( - override: OverrideParam = - burstValues( - OverrideParam.Authenticator, - OverrideParam.Cache, - OverrideParam.CertificatePinner, - OverrideParam.ConnectTimeout, - OverrideParam.ConnectionPool, - OverrideParam.CookieJar, - OverrideParam.Dns, - OverrideParam.HostnameVerifier, - OverrideParam.Proxy, - OverrideParam.ProxyAuthenticator, - OverrideParam.ProxySelector, - OverrideParam.ReadTimeout, - OverrideParam.RetryOnConnectionFailure, - OverrideParam.SocketFactory, - OverrideParam.SslSocketFactory, - OverrideParam.WriteTimeout, - OverrideParam.X509TrustManager, - ), - testItFails: Boolean = true, + override: OverrideParam = burstValues( + OverrideParam.Authenticator, + OverrideParam.Cache, + OverrideParam.CertificatePinner, + OverrideParam.ConnectTimeout, + OverrideParam.ConnectionPool, + OverrideParam.CookieJar, + OverrideParam.Dns, + OverrideParam.HostnameVerifier, + OverrideParam.Proxy, + OverrideParam.ProxyAuthenticator, + OverrideParam.ProxySelector, + OverrideParam.ReadTimeout, + OverrideParam.RetryOnConnectionFailure, + OverrideParam.SocketFactory, + OverrideParam.SslSocketFactory, + OverrideParam.WriteTimeout, + OverrideParam.X509TrustManager, + ), + testItFails: Boolean = false, ) { when (override) { OverrideParam.ProxyAuthenticator -> { client = client.newBuilder().proxy(server.proxyAddress).build() server.enqueue( - MockResponse - .Builder() - .code(407) - .headers(headersOf("Proxy-Authenticate", "Basic realm=\"localhost\"")) - .inTunnel() - .build(), + MockResponse.Builder().code(407).headers(headersOf("Proxy-Authenticate", "Basic realm=\"localhost\"")) + .inTunnel().build(), ) overrideBadImplementation(override = override.override, testItFails = testItFails) @@ -234,21 +213,18 @@ class InterceptorOverridesTest { } OverrideParam.RetryOnConnectionFailure -> { + enableTls() var first = true client = client.newBuilder() - .dns { host -> - val hosts = Dns.SYSTEM.lookup(host) - hosts + hosts - } - .socketFactory(DelayingSocketFactory(onConnect = { - if (first) { - println("fail") - first = false - throw IOException() + .connectionSpecs(listOf(ConnectionSpec.RESTRICTED_TLS, ConnectionSpec.MODERN_TLS)) + .eventListener(object : EventListener() { + override fun secureConnectEnd(call: Call, handshake: Handshake?) { + if (first) { + first = false + throw SSLException("") + } } - - println("work") - })) + }) .build() overrideBadImplementation( @@ -283,19 +259,32 @@ class InterceptorOverridesTest { } OverrideParam.WriteTimeout -> { - client = client.newBuilder().socketFactory(DelayingSocketFactory(onWrite = { Thread.sleep(100L) })).build() + val body = object : RequestBody() { + override fun contentType(): MediaType? = null - overrideBadImplementation(override = override.override, testItFails = testItFails, bodySize = 100) + override fun writeTo(sink: BufferedSink) { + if (sink.timeout().timeoutNanos().nanoseconds.inWholeMilliseconds == 10L) { + throw IOException() + } + } + } + overrideBadImplementation(override = override.override, testItFails = testItFails, body = body) } OverrideParam.ReadTimeout -> { - client = client.newBuilder().socketFactory(DelayingSocketFactory(onRead = { Thread.sleep(100L) })).build() + client = client.newBuilder().socketFactory(DelayingSocketFactory(onRead = { + Thread.sleep(100L) + })).build() overrideBadImplementation(override = override.override, testItFails = testItFails) } OverrideParam.ConnectTimeout -> { - client = client.newBuilder().socketFactory(DelayingSocketFactory(onConnect = { Thread.sleep(100L) })).build() + client = client.newBuilder().socketFactory(DelayingSocketFactory(onConnect = { timeout -> + if (timeout == 10) { + throw IOException() + } + })).build() overrideBadImplementation(override = override.override, testItFails = testItFails) } @@ -303,16 +292,11 @@ class InterceptorOverridesTest { OverrideParam.CertificatePinner -> { enableTls() - val pinner = - CertificatePinner - .Builder() - .add(server.hostName, pin(handshakeCertificates.trustManager.acceptedIssuers.first())) - .build() + val pinner = CertificatePinner.Builder() + .add(server.hostName, pin(handshakeCertificates.trustManager.acceptedIssuers.first())).build() overrideBadImplementation( - override = Override.CertificatePinnerOverride, - testItFails = testItFails, - goodValue = pinner + override = Override.CertificatePinnerOverride, testItFails = testItFails, goodValue = pinner ) } @@ -327,30 +311,26 @@ class InterceptorOverridesTest { testItFails: Boolean, badValue: T = override.badValue, goodValue: T = override.nonDefaultValue, - bodySize: Int? = null + body: RequestBody? = null ) { with(override) { - client = - client - .newBuilder() - // Set the bad override directly on the client - .withOverride(badValue) - .addInterceptor { chain -> - // the only way to stop a bad override of a client is with a good override of an interceptor - chain - .run { - if (testItFails) { - this - } else { - withOverride(goodValue) - } - }.proceed(chain.request()) - }.build() + client = client.newBuilder() + // Set the bad override directly on the client + .withOverride(badValue).addInterceptor { chain -> + // the only way to stop a bad override of a client is with a good override of an interceptor + chain.run { + if (testItFails) { + this + } else { + withOverride(goodValue) + } + }.proceed(chain.request()) + }.build() server.enqueue( MockResponse(), ) - val call = client.newCall(Request(server.url("/"), body = bodySize?.let { ByteArray(bodySize).toRequestBody() })) + val call = client.newCall(Request(server.url("/"), body = body)) val result = runCatching { call.execute().body.bytes() } if (testItFails) { @@ -364,33 +344,22 @@ class InterceptorOverridesTest { enum class OverrideParam( val override: Override<*>, ) { - Authenticator(Override.AuthenticatorOverride), - Cache(Override.CacheOverride), - CertificatePinner(Override.CertificatePinnerOverride), - ConnectTimeout( + Authenticator(Override.AuthenticatorOverride), Cache(Override.CacheOverride), CertificatePinner(Override.CertificatePinnerOverride), ConnectTimeout( Override.ConnectTimeoutOverride, ) { override val paramName: String get() = "Timeouts" }, - ConnectionPool(Override.ConnectionPoolOverride), - CookieJar(Override.CookieJarOverride), - Dns(Override.DnsOverride), - HostnameVerifier( + ConnectionPool(Override.ConnectionPoolOverride), CookieJar(Override.CookieJarOverride), Dns(Override.DnsOverride), HostnameVerifier( Override.HostnameVerifierOverride, ), - Proxy(Override.ProxyOverride), - ProxyAuthenticator(Override.ProxyAuthenticatorOverride), - ProxySelector(Override.ProxySelectorOverride), - ReadTimeout( + Proxy(Override.ProxyOverride), ProxyAuthenticator(Override.ProxyAuthenticatorOverride), ProxySelector(Override.ProxySelectorOverride), ReadTimeout( Override.ReadTimeoutOverride, ) { override val paramName: String get() = "Timeouts" }, - RetryOnConnectionFailure(Override.RetryOnConnectionFailureOverride), - SocketFactory(Override.SocketFactoryOverride), - SslSocketFactory( + RetryOnConnectionFailure(Override.RetryOnConnectionFailureOverride), SocketFactory(Override.SocketFactoryOverride), SslSocketFactory( Override.SslSocketFactoryOverride, ), WriteTimeout(Override.WriteTimeoutOverride) { @@ -404,35 +373,33 @@ class InterceptorOverridesTest { } class DelayingSocketFactory( - val onConnect: () -> Unit = {}, - val onRead: () -> Unit = {}, - val onWrite: () -> Unit = {}, + val onConnect: Socket.(timeout: Int) -> Unit = {}, + val onRead: Socket.() -> Unit = {}, + val onWrite: Socket.() -> Unit = {}, ) : DelegatingSocketFactory(getDefault()) { override fun createSocket(): Socket { return object : Socket() { override fun connect(endpoint: SocketAddress?, timeout: Int) { - onConnect() + onConnect(timeout) super.connect(endpoint, timeout) } override fun getInputStream(): InputStream { - return object : ForwardingSource(super.getInputStream().source()) { - override fun read(sink: Buffer, byteCount: Long): Long { + return object : FilterInputStream(super.inputStream) { + override fun read(b: ByteArray?, off: Int, len: Int): Int { onRead() - - return super.read(sink, byteCount) + return super.read(b, off, len) } - }.buffer().inputStream() + } } override fun getOutputStream(): OutputStream { - return object : ForwardingSink(super.getOutputStream().sink()) { - override fun write(source: Buffer, byteCount: Long) { + return object : FilterOutputStream(super.outputStream) { + override fun write(b: ByteArray?, off: Int, len: Int) { onWrite() - - return super.write(source, byteCount) + super.write(b, off, len) } - }.buffer().outputStream() + } } } } @@ -477,10 +444,9 @@ class InterceptorOverridesTest { override val nonDefaultValue: SocketFactory = object : DelegatingSocketFactory(getDefault()) {} - override val badValue: SocketFactory = - object : DelegatingSocketFactory(getDefault()) { - override fun configureSocket(socket: Socket): Socket = TODO() - } + override val badValue: SocketFactory = object : DelegatingSocketFactory(getDefault()) { + override fun configureSocket(socket: Socket): Socket = TODO() + } override fun isDefaultValue(value: SocketFactory): Boolean = value === SocketFactory.getDefault() } @@ -506,28 +472,26 @@ class InterceptorOverridesTest { override fun OkHttpClient.Builder.withOverride(value: CookieJar): OkHttpClient.Builder = cookieJar(value) - override val nonDefaultValue: CookieJar = - object : CookieJar { - override fun saveFromResponse( - url: HttpUrl, - cookies: List, - ) { - } - - override fun loadForRequest(url: HttpUrl): List = emptyList() + override val nonDefaultValue: CookieJar = object : CookieJar { + override fun saveFromResponse( + url: HttpUrl, + cookies: List, + ) { } - override val badValue: CookieJar = - object : CookieJar { - override fun saveFromResponse( - url: HttpUrl, - cookies: List, - ) { - } + override fun loadForRequest(url: HttpUrl): List = emptyList() + } - override fun loadForRequest(url: HttpUrl): List = TODO() + override val badValue: CookieJar = object : CookieJar { + override fun saveFromResponse( + url: HttpUrl, + cookies: List, + ) { } + override fun loadForRequest(url: HttpUrl): List = TODO() + } + override fun isDefaultValue(value: CookieJar): Boolean = value === CookieJar.NO_COOKIES } @@ -540,18 +504,17 @@ class InterceptorOverridesTest { override val nonDefaultValue: Cache = Cache(FakeFileSystem(), "/cash".toPath(), 1) - override val badValue: Cache = - Cache( - object : ForwardingFileSystem(FakeFileSystem()) { - override fun onPathParameter( - path: Path, - functionName: String, - parameterName: String, - ): Path = TODO() - }, - "/cash".toPath(), - 1, - ) + override val badValue: Cache = Cache( + object : ForwardingFileSystem(FakeFileSystem()) { + override fun onPathParameter( + path: Path, + functionName: String, + parameterName: String, + ): Path = TODO() + }, + "/cash".toPath(), + 1, + ) override fun isDefaultValue(value: Cache?): Boolean = value == null } @@ -578,29 +541,27 @@ class InterceptorOverridesTest { override fun OkHttpClient.Builder.withOverride(value: ProxySelector): OkHttpClient.Builder = proxySelector(value) - override val nonDefaultValue: ProxySelector = - object : ProxySelector() { - override fun select(uri: URI?): MutableList = mutableListOf(java.net.Proxy.NO_PROXY) + override val nonDefaultValue: ProxySelector = object : ProxySelector() { + override fun select(uri: URI?): MutableList = mutableListOf(java.net.Proxy.NO_PROXY) - override fun connectFailed( - uri: URI?, - sa: SocketAddress?, - ioe: java.io.IOException?, - ) { - } + override fun connectFailed( + uri: URI?, + sa: SocketAddress?, + ioe: java.io.IOException?, + ) { } + } - override val badValue: ProxySelector = - object : ProxySelector() { - override fun select(uri: URI?): MutableList = TODO() + override val badValue: ProxySelector = object : ProxySelector() { + override fun select(uri: URI?): MutableList = TODO() - override fun connectFailed( - uri: URI?, - sa: SocketAddress?, - ioe: java.io.IOException?, - ) { - } + override fun connectFailed( + uri: URI?, + sa: SocketAddress?, + ioe: java.io.IOException?, + ) { } + } override fun isDefaultValue(value: ProxySelector): Boolean = value === ProxySelector.getDefault() } @@ -630,9 +591,8 @@ class InterceptorOverridesTest { override fun OkHttpClient.Builder.withOverride(value: SSLSocketFactory?): OkHttpClient.Builder = sslSocketFactory(value!!, x509TrustManagerOrNull!!) - override val nonDefaultValue: SSLSocketFactory = - object : - DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) {} + override val nonDefaultValue: SSLSocketFactory = object : + DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) {} override val badValue: SSLSocketFactory = object : DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) { @@ -648,52 +608,46 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.value(): X509TrustManager? = x509TrustManagerOrNull override fun Interceptor.Chain.withOverride(value: X509TrustManager?): Interceptor.Chain = - withSslSocketFactory(sslSocketFactoryOrNull, value) + withSslSocketFactory(Platform.get().newSslSocketFactory(value!!), value) override fun OkHttpClient.Builder.withOverride(value: X509TrustManager?): OkHttpClient.Builder = - sslSocketFactory(sslSocketFactoryOrNull!!, value!!) - - override val nonDefaultValue: X509TrustManager = - object : X509TrustManager { - override fun checkClientTrusted( - x509Certificates: Array, - s: String, - ) { - } + sslSocketFactory(Platform.get().newSslSocketFactory(value!!), value) - override fun checkServerTrusted( - x509Certificates: Array, - s: String, - ) { - } + override val nonDefaultValue: X509TrustManager = object : X509TrustManager { + override fun checkClientTrusted( + x509Certificates: Array, + s: String, + ) { + } - override fun getAcceptedIssuers(): Array = arrayOf() + override fun checkServerTrusted( + x509Certificates: Array, + s: String, + ) { } - override val badValue: X509TrustManager = - object : X509TrustManager { - override fun checkClientTrusted( - x509Certificates: Array, - s: String, - ) { - } + override fun getAcceptedIssuers(): Array = arrayOf() + } - override fun checkServerTrusted( - x509Certificates: Array, - s: String, - ) { - TODO() - } + override val badValue: X509TrustManager = object : X509TrustManager { + override fun checkClientTrusted( + x509Certificates: Array, + s: String, + ) { + } - override fun getAcceptedIssuers(): Array = arrayOf() + override fun checkServerTrusted( + x509Certificates: Array, + s: String, + ) { + TODO() } + override fun getAcceptedIssuers(): Array = arrayOf() + } + override fun isDefaultValue(value: X509TrustManager?): Boolean = - !value - ?.javaClass - ?.name - .orEmpty() - .startsWith("okhttp") + !value?.javaClass?.name.orEmpty().startsWith("okhttp") } object HostnameVerifierOverride : Override { @@ -722,9 +676,7 @@ class InterceptorOverridesTest { certificatePinner(value) override val nonDefaultValue: CertificatePinner = - CertificatePinner - .Builder() - .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") + CertificatePinner.Builder().add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") .build() override val badValue: CertificatePinner = @@ -744,9 +696,7 @@ class InterceptorOverridesTest { override val nonDefaultValue: ConnectionPool = ConnectionPool(keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES) override val badValue: ConnectionPool = ConnectionPool( - keepAliveDuration = 1, - timeUnit = TimeUnit.MINUTES, - connectionListener = object : ConnectionListener() { + keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES, connectionListener = object : ConnectionListener() { override fun connectStart(route: Route, call: Call): Unit = TODO() }) @@ -766,7 +716,7 @@ class InterceptorOverridesTest { override val nonDefaultValue: Int = 5000 override val badValue: Int - get() = 1 + get() = 10 override fun isDefaultValue(value: Int): Boolean = value == 10000 } @@ -783,7 +733,7 @@ class InterceptorOverridesTest { override val nonDefaultValue: Int = 5000 override val badValue: Int - get() = 1 + get() = 10 override fun isDefaultValue(value: Int): Boolean = value == 10000 } @@ -800,7 +750,7 @@ class InterceptorOverridesTest { override val nonDefaultValue: Int = 5000 override val badValue: Int - get() = 1 + get() = 10 override fun isDefaultValue(value: Int): Boolean = value == 10000 } @@ -824,14 +774,10 @@ class InterceptorOverridesTest { } private fun enableTls() { - client = - client - .newBuilder() - .sslSocketFactory( - handshakeCertificates.sslSocketFactory(), - handshakeCertificates.trustManager, - ) - .build() + client = client.newBuilder().sslSocketFactory( + handshakeCertificates.sslSocketFactory(), + handshakeCertificates.trustManager, + ).build() server.useHttps(handshakeCertificates.sslSocketFactory()) } } From 40ee3dbe264ffc832197df7a06e8c1244b08ac67 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sat, 13 Dec 2025 11:10:53 +0000 Subject: [PATCH 18/21] cleanup --- .../okhttp3/InterceptorOverridesTest.kt | 553 ++++++++++-------- 1 file changed, 314 insertions(+), 239 deletions(-) diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt index 2c396fe1bd1f..8c2a6af5dfc2 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt @@ -66,25 +66,26 @@ class InterceptorOverridesTest { */ @Test fun testOverrideInApplicationInterceptor( - override: OverrideParam = burstValues( - OverrideParam.Authenticator, - OverrideParam.Cache, - OverrideParam.CertificatePinner, - OverrideParam.ConnectTimeout, - OverrideParam.ConnectionPool, - OverrideParam.CookieJar, - OverrideParam.Dns, - OverrideParam.HostnameVerifier, - OverrideParam.Proxy, - OverrideParam.ProxyAuthenticator, - OverrideParam.ProxySelector, - OverrideParam.ReadTimeout, - OverrideParam.RetryOnConnectionFailure, - OverrideParam.SocketFactory, - OverrideParam.SslSocketFactory, - OverrideParam.WriteTimeout, - OverrideParam.X509TrustManager, - ), + override: OverrideParam = + burstValues( + OverrideParam.Authenticator, + OverrideParam.Cache, + OverrideParam.CertificatePinner, + OverrideParam.ConnectTimeout, + OverrideParam.ConnectionPool, + OverrideParam.CookieJar, + OverrideParam.Dns, + OverrideParam.HostnameVerifier, + OverrideParam.Proxy, + OverrideParam.ProxyAuthenticator, + OverrideParam.ProxySelector, + OverrideParam.ReadTimeout, + OverrideParam.RetryOnConnectionFailure, + OverrideParam.SocketFactory, + OverrideParam.SslSocketFactory, + OverrideParam.WriteTimeout, + OverrideParam.X509TrustManager, + ), isDefault: Boolean, ) { fun Override.testApplicationInterceptor(chain: Interceptor.Chain): Response { @@ -104,12 +105,15 @@ class InterceptorOverridesTest { } with(override.override) { - client = client.newBuilder().addInterceptor { chain -> - testApplicationInterceptor(chain) - }.addNetworkInterceptor { chain -> - assertThat(isDefaultValue(chain.value())).isFalse() - chain.proceed(chain.request()) - }.build() + client = + client + .newBuilder() + .addInterceptor { chain -> + testApplicationInterceptor(chain) + }.addNetworkInterceptor { chain -> + assertThat(isDefaultValue(chain.value())).isFalse() + chain.proceed(chain.request()) + }.build() server.enqueue( MockResponse(), @@ -124,38 +128,42 @@ class InterceptorOverridesTest { */ @Test fun testOverrideInNetworkInterceptor( - override: OverrideParam = burstValues( - OverrideParam.Authenticator, - OverrideParam.Cache, - OverrideParam.CertificatePinner, - OverrideParam.ConnectTimeout, - OverrideParam.ConnectionPool, - OverrideParam.CookieJar, - OverrideParam.Dns, - OverrideParam.HostnameVerifier, - OverrideParam.Proxy, - OverrideParam.ProxyAuthenticator, - OverrideParam.ProxySelector, - OverrideParam.ReadTimeout, - OverrideParam.RetryOnConnectionFailure, - OverrideParam.SocketFactory, - OverrideParam.SslSocketFactory, - OverrideParam.WriteTimeout, - OverrideParam.X509TrustManager, - ), + override: OverrideParam = + burstValues( + OverrideParam.Authenticator, + OverrideParam.Cache, + OverrideParam.CertificatePinner, + OverrideParam.ConnectTimeout, + OverrideParam.ConnectionPool, + OverrideParam.CookieJar, + OverrideParam.Dns, + OverrideParam.HostnameVerifier, + OverrideParam.Proxy, + OverrideParam.ProxyAuthenticator, + OverrideParam.ProxySelector, + OverrideParam.ReadTimeout, + OverrideParam.RetryOnConnectionFailure, + OverrideParam.SocketFactory, + OverrideParam.SslSocketFactory, + OverrideParam.WriteTimeout, + OverrideParam.X509TrustManager, + ), ) { with(override.override) { - client = client.newBuilder().addNetworkInterceptor { chain -> - assertThat(isDefaultValue(chain.value())).isTrue() + client = + client + .newBuilder() + .addNetworkInterceptor { chain -> + assertThat(isDefaultValue(chain.value())).isTrue() - assertFailure { - chain.withOverride( - nonDefaultValue, - ) - }.hasMessage("${override.paramName} can't be adjusted in a network interceptor") + assertFailure { + chain.withOverride( + nonDefaultValue, + ) + }.hasMessage("${override.paramName} can't be adjusted in a network interceptor") - chain.proceed(chain.request()) - }.build() + chain.proceed(chain.request()) + }.build() server.enqueue( MockResponse(), @@ -171,25 +179,26 @@ class InterceptorOverridesTest { */ @Test fun testOverrideBadImplementation( - override: OverrideParam = burstValues( - OverrideParam.Authenticator, - OverrideParam.Cache, - OverrideParam.CertificatePinner, - OverrideParam.ConnectTimeout, - OverrideParam.ConnectionPool, - OverrideParam.CookieJar, - OverrideParam.Dns, - OverrideParam.HostnameVerifier, - OverrideParam.Proxy, - OverrideParam.ProxyAuthenticator, - OverrideParam.ProxySelector, - OverrideParam.ReadTimeout, - OverrideParam.RetryOnConnectionFailure, - OverrideParam.SocketFactory, - OverrideParam.SslSocketFactory, - OverrideParam.WriteTimeout, - OverrideParam.X509TrustManager, - ), + override: OverrideParam = + burstValues( + OverrideParam.Authenticator, + OverrideParam.Cache, + OverrideParam.CertificatePinner, + OverrideParam.ConnectTimeout, + OverrideParam.ConnectionPool, + OverrideParam.CookieJar, + OverrideParam.Dns, + OverrideParam.HostnameVerifier, + OverrideParam.Proxy, + OverrideParam.ProxyAuthenticator, + OverrideParam.ProxySelector, + OverrideParam.ReadTimeout, + OverrideParam.RetryOnConnectionFailure, + OverrideParam.SocketFactory, + OverrideParam.SslSocketFactory, + OverrideParam.WriteTimeout, + OverrideParam.X509TrustManager, + ), testItFails: Boolean = false, ) { when (override) { @@ -197,8 +206,12 @@ class InterceptorOverridesTest { client = client.newBuilder().proxy(server.proxyAddress).build() server.enqueue( - MockResponse.Builder().code(407).headers(headersOf("Proxy-Authenticate", "Basic realm=\"localhost\"")) - .inTunnel().build(), + MockResponse + .Builder() + .code(407) + .headers(headersOf("Proxy-Authenticate", "Basic realm=\"localhost\"")) + .inTunnel() + .build(), ) overrideBadImplementation(override = override.override, testItFails = testItFails) @@ -215,17 +228,23 @@ class InterceptorOverridesTest { OverrideParam.RetryOnConnectionFailure -> { enableTls() var first = true - client = client.newBuilder() - .connectionSpecs(listOf(ConnectionSpec.RESTRICTED_TLS, ConnectionSpec.MODERN_TLS)) - .eventListener(object : EventListener() { - override fun secureConnectEnd(call: Call, handshake: Handshake?) { - if (first) { - first = false - throw SSLException("") - } - } - }) - .build() + client = + client + .newBuilder() + .connectionSpecs(listOf(ConnectionSpec.RESTRICTED_TLS, ConnectionSpec.MODERN_TLS)) + .eventListener( + object : EventListener() { + override fun secureConnectEnd( + call: Call, + handshake: Handshake?, + ) { + if (first) { + first = false + throw SSLException("") + } + } + }, + ).build() overrideBadImplementation( override = Override.RetryOnConnectionFailureOverride, @@ -259,32 +278,47 @@ class InterceptorOverridesTest { } OverrideParam.WriteTimeout -> { - val body = object : RequestBody() { - override fun contentType(): MediaType? = null - - override fun writeTo(sink: BufferedSink) { - if (sink.timeout().timeoutNanos().nanoseconds.inWholeMilliseconds == 10L) { - throw IOException() + val body = + object : RequestBody() { + override fun contentType(): MediaType? = null + + override fun writeTo(sink: BufferedSink) { + if (sink + .timeout() + .timeoutNanos() + .nanoseconds.inWholeMilliseconds == 10L + ) { + throw IOException() + } } } - } overrideBadImplementation(override = override.override, testItFails = testItFails, body = body) } OverrideParam.ReadTimeout -> { - client = client.newBuilder().socketFactory(DelayingSocketFactory(onRead = { - Thread.sleep(100L) - })).build() + client = + client + .newBuilder() + .socketFactory( + DelayingSocketFactory(onRead = { + Thread.sleep(100L) + }), + ).build() overrideBadImplementation(override = override.override, testItFails = testItFails) } OverrideParam.ConnectTimeout -> { - client = client.newBuilder().socketFactory(DelayingSocketFactory(onConnect = { timeout -> - if (timeout == 10) { - throw IOException() - } - })).build() + client = + client + .newBuilder() + .socketFactory( + DelayingSocketFactory(onConnect = { timeout -> + if (timeout == 10) { + throw IOException() + } + }), + ).build() overrideBadImplementation(override = override.override, testItFails = testItFails) } @@ -292,11 +326,16 @@ class InterceptorOverridesTest { OverrideParam.CertificatePinner -> { enableTls() - val pinner = CertificatePinner.Builder() - .add(server.hostName, pin(handshakeCertificates.trustManager.acceptedIssuers.first())).build() + val pinner = + CertificatePinner + .Builder() + .add(server.hostName, pin(handshakeCertificates.trustManager.acceptedIssuers.first())) + .build() overrideBadImplementation( - override = Override.CertificatePinnerOverride, testItFails = testItFails, goodValue = pinner + override = Override.CertificatePinnerOverride, + testItFails = testItFails, + goodValue = pinner, ) } @@ -311,21 +350,25 @@ class InterceptorOverridesTest { testItFails: Boolean, badValue: T = override.badValue, goodValue: T = override.nonDefaultValue, - body: RequestBody? = null + body: RequestBody? = null, ) { with(override) { - client = client.newBuilder() - // Set the bad override directly on the client - .withOverride(badValue).addInterceptor { chain -> - // the only way to stop a bad override of a client is with a good override of an interceptor - chain.run { - if (testItFails) { - this - } else { - withOverride(goodValue) - } - }.proceed(chain.request()) - }.build() + client = + client + .newBuilder() + // Set the bad override directly on the client + .withOverride(badValue) + .addInterceptor { chain -> + // the only way to stop a bad override of a client is with a good override of an interceptor + chain + .run { + if (testItFails) { + this + } else { + withOverride(goodValue) + } + }.proceed(chain.request()) + }.build() server.enqueue( MockResponse(), @@ -344,22 +387,33 @@ class InterceptorOverridesTest { enum class OverrideParam( val override: Override<*>, ) { - Authenticator(Override.AuthenticatorOverride), Cache(Override.CacheOverride), CertificatePinner(Override.CertificatePinnerOverride), ConnectTimeout( + Authenticator(Override.AuthenticatorOverride), + Cache(Override.CacheOverride), + CertificatePinner(Override.CertificatePinnerOverride), + ConnectTimeout( Override.ConnectTimeoutOverride, ) { override val paramName: String get() = "Timeouts" }, - ConnectionPool(Override.ConnectionPoolOverride), CookieJar(Override.CookieJarOverride), Dns(Override.DnsOverride), HostnameVerifier( + ConnectionPool(Override.ConnectionPoolOverride), + CookieJar(Override.CookieJarOverride), + Dns(Override.DnsOverride), + HostnameVerifier( Override.HostnameVerifierOverride, ), - Proxy(Override.ProxyOverride), ProxyAuthenticator(Override.ProxyAuthenticatorOverride), ProxySelector(Override.ProxySelectorOverride), ReadTimeout( + Proxy(Override.ProxyOverride), + ProxyAuthenticator(Override.ProxyAuthenticatorOverride), + ProxySelector(Override.ProxySelectorOverride), + ReadTimeout( Override.ReadTimeoutOverride, ) { override val paramName: String get() = "Timeouts" }, - RetryOnConnectionFailure(Override.RetryOnConnectionFailureOverride), SocketFactory(Override.SocketFactoryOverride), SslSocketFactory( + RetryOnConnectionFailure(Override.RetryOnConnectionFailureOverride), + SocketFactory(Override.SocketFactoryOverride), + SslSocketFactory( Override.SslSocketFactoryOverride, ), WriteTimeout(Override.WriteTimeoutOverride) { @@ -379,28 +433,38 @@ class InterceptorOverridesTest { ) : DelegatingSocketFactory(getDefault()) { override fun createSocket(): Socket { return object : Socket() { - override fun connect(endpoint: SocketAddress?, timeout: Int) { + override fun connect( + endpoint: SocketAddress?, + timeout: Int, + ) { onConnect(timeout) super.connect(endpoint, timeout) } override fun getInputStream(): InputStream { return object : FilterInputStream(super.inputStream) { - override fun read(b: ByteArray?, off: Int, len: Int): Int { + override fun read( + b: ByteArray?, + off: Int, + len: Int, + ): Int { onRead() return super.read(b, off, len) } } } - override fun getOutputStream(): OutputStream { - return object : FilterOutputStream(super.outputStream) { - override fun write(b: ByteArray?, off: Int, len: Int) { + override fun getOutputStream(): OutputStream = + object : FilterOutputStream(super.outputStream) { + override fun write( + b: ByteArray?, + off: Int, + len: Int, + ) { onWrite() super.write(b, off, len) } } - } } } } @@ -444,9 +508,10 @@ class InterceptorOverridesTest { override val nonDefaultValue: SocketFactory = object : DelegatingSocketFactory(getDefault()) {} - override val badValue: SocketFactory = object : DelegatingSocketFactory(getDefault()) { - override fun configureSocket(socket: Socket): Socket = TODO() - } + override val badValue: SocketFactory = + object : DelegatingSocketFactory(getDefault()) { + override fun configureSocket(socket: Socket): Socket = TODO() + } override fun isDefaultValue(value: SocketFactory): Boolean = value === SocketFactory.getDefault() } @@ -472,26 +537,28 @@ class InterceptorOverridesTest { override fun OkHttpClient.Builder.withOverride(value: CookieJar): OkHttpClient.Builder = cookieJar(value) - override val nonDefaultValue: CookieJar = object : CookieJar { - override fun saveFromResponse( - url: HttpUrl, - cookies: List, - ) { + override val nonDefaultValue: CookieJar = + object : CookieJar { + override fun saveFromResponse( + url: HttpUrl, + cookies: List, + ) { + } + + override fun loadForRequest(url: HttpUrl): List = emptyList() } - override fun loadForRequest(url: HttpUrl): List = emptyList() - } + override val badValue: CookieJar = + object : CookieJar { + override fun saveFromResponse( + url: HttpUrl, + cookies: List, + ) { + } - override val badValue: CookieJar = object : CookieJar { - override fun saveFromResponse( - url: HttpUrl, - cookies: List, - ) { + override fun loadForRequest(url: HttpUrl): List = TODO() } - override fun loadForRequest(url: HttpUrl): List = TODO() - } - override fun isDefaultValue(value: CookieJar): Boolean = value === CookieJar.NO_COOKIES } @@ -504,17 +571,18 @@ class InterceptorOverridesTest { override val nonDefaultValue: Cache = Cache(FakeFileSystem(), "/cash".toPath(), 1) - override val badValue: Cache = Cache( - object : ForwardingFileSystem(FakeFileSystem()) { - override fun onPathParameter( - path: Path, - functionName: String, - parameterName: String, - ): Path = TODO() - }, - "/cash".toPath(), - 1, - ) + override val badValue: Cache = + Cache( + object : ForwardingFileSystem(FakeFileSystem()) { + override fun onPathParameter( + path: Path, + functionName: String, + parameterName: String, + ): Path = TODO() + }, + "/cash".toPath(), + 1, + ) override fun isDefaultValue(value: Cache?): Boolean = value == null } @@ -541,27 +609,29 @@ class InterceptorOverridesTest { override fun OkHttpClient.Builder.withOverride(value: ProxySelector): OkHttpClient.Builder = proxySelector(value) - override val nonDefaultValue: ProxySelector = object : ProxySelector() { - override fun select(uri: URI?): MutableList = mutableListOf(java.net.Proxy.NO_PROXY) + override val nonDefaultValue: ProxySelector = + object : ProxySelector() { + override fun select(uri: URI?): MutableList = mutableListOf(java.net.Proxy.NO_PROXY) - override fun connectFailed( - uri: URI?, - sa: SocketAddress?, - ioe: java.io.IOException?, - ) { + override fun connectFailed( + uri: URI?, + sa: SocketAddress?, + ioe: java.io.IOException?, + ) { + } } - } - override val badValue: ProxySelector = object : ProxySelector() { - override fun select(uri: URI?): MutableList = TODO() + override val badValue: ProxySelector = + object : ProxySelector() { + override fun select(uri: URI?): MutableList = TODO() - override fun connectFailed( - uri: URI?, - sa: SocketAddress?, - ioe: java.io.IOException?, - ) { + override fun connectFailed( + uri: URI?, + sa: SocketAddress?, + ioe: java.io.IOException?, + ) { + } } - } override fun isDefaultValue(value: ProxySelector): Boolean = value === ProxySelector.getDefault() } @@ -569,11 +639,9 @@ class InterceptorOverridesTest { object ProxyAuthenticatorOverride : Override { override fun Interceptor.Chain.value(): Authenticator = proxyAuthenticator - override fun Interceptor.Chain.withOverride(value: Authenticator): Interceptor.Chain = - withProxyAuthenticator(value) + override fun Interceptor.Chain.withOverride(value: Authenticator): Interceptor.Chain = withProxyAuthenticator(value) - override fun OkHttpClient.Builder.withOverride(value: Authenticator): OkHttpClient.Builder = - proxyAuthenticator(value) + override fun OkHttpClient.Builder.withOverride(value: Authenticator): OkHttpClient.Builder = proxyAuthenticator(value) override val nonDefaultValue: Authenticator = Authenticator { route, response -> response.request } @@ -591,8 +659,9 @@ class InterceptorOverridesTest { override fun OkHttpClient.Builder.withOverride(value: SSLSocketFactory?): OkHttpClient.Builder = sslSocketFactory(value!!, x509TrustManagerOrNull!!) - override val nonDefaultValue: SSLSocketFactory = object : - DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) {} + override val nonDefaultValue: SSLSocketFactory = + object : + DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) {} override val badValue: SSLSocketFactory = object : DelegatingSSLSocketFactory(Platform.get().newSslSocketFactory(Platform.get().platformTrustManager())) { @@ -613,51 +682,55 @@ class InterceptorOverridesTest { override fun OkHttpClient.Builder.withOverride(value: X509TrustManager?): OkHttpClient.Builder = sslSocketFactory(Platform.get().newSslSocketFactory(value!!), value) - override val nonDefaultValue: X509TrustManager = object : X509TrustManager { - override fun checkClientTrusted( - x509Certificates: Array, - s: String, - ) { - } + override val nonDefaultValue: X509TrustManager = + object : X509TrustManager { + override fun checkClientTrusted( + x509Certificates: Array, + s: String, + ) { + } - override fun checkServerTrusted( - x509Certificates: Array, - s: String, - ) { + override fun checkServerTrusted( + x509Certificates: Array, + s: String, + ) { + } + + override fun getAcceptedIssuers(): Array = arrayOf() } - override fun getAcceptedIssuers(): Array = arrayOf() - } + override val badValue: X509TrustManager = + object : X509TrustManager { + override fun checkClientTrusted( + x509Certificates: Array, + s: String, + ) { + } - override val badValue: X509TrustManager = object : X509TrustManager { - override fun checkClientTrusted( - x509Certificates: Array, - s: String, - ) { - } + override fun checkServerTrusted( + x509Certificates: Array, + s: String, + ) { + TODO() + } - override fun checkServerTrusted( - x509Certificates: Array, - s: String, - ) { - TODO() + override fun getAcceptedIssuers(): Array = arrayOf() } - override fun getAcceptedIssuers(): Array = arrayOf() - } - override fun isDefaultValue(value: X509TrustManager?): Boolean = - !value?.javaClass?.name.orEmpty().startsWith("okhttp") + !value + ?.javaClass + ?.name + .orEmpty() + .startsWith("okhttp") } object HostnameVerifierOverride : Override { override fun Interceptor.Chain.value(): HostnameVerifier = hostnameVerifier - override fun Interceptor.Chain.withOverride(value: HostnameVerifier): Interceptor.Chain = - withHostnameVerifier(value) + override fun Interceptor.Chain.withOverride(value: HostnameVerifier): Interceptor.Chain = withHostnameVerifier(value) - override fun OkHttpClient.Builder.withOverride(value: HostnameVerifier): OkHttpClient.Builder = - hostnameVerifier(value) + override fun OkHttpClient.Builder.withOverride(value: HostnameVerifier): OkHttpClient.Builder = hostnameVerifier(value) override val nonDefaultValue: HostnameVerifier = HostnameVerifier { _, _ -> true } @@ -669,14 +742,14 @@ class InterceptorOverridesTest { object CertificatePinnerOverride : Override { override fun Interceptor.Chain.value(): CertificatePinner = certificatePinner - override fun Interceptor.Chain.withOverride(value: CertificatePinner): Interceptor.Chain = - withCertificatePinner(value) + override fun Interceptor.Chain.withOverride(value: CertificatePinner): Interceptor.Chain = withCertificatePinner(value) - override fun OkHttpClient.Builder.withOverride(value: CertificatePinner): OkHttpClient.Builder = - certificatePinner(value) + override fun OkHttpClient.Builder.withOverride(value: CertificatePinner): OkHttpClient.Builder = certificatePinner(value) override val nonDefaultValue: CertificatePinner = - CertificatePinner.Builder().add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") + CertificatePinner + .Builder() + .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") .build() override val badValue: CertificatePinner = @@ -690,25 +763,30 @@ class InterceptorOverridesTest { override fun Interceptor.Chain.withOverride(value: ConnectionPool): Interceptor.Chain = withConnectionPool(value) - override fun OkHttpClient.Builder.withOverride(value: ConnectionPool): OkHttpClient.Builder = - connectionPool(value) + override fun OkHttpClient.Builder.withOverride(value: ConnectionPool): OkHttpClient.Builder = connectionPool(value) override val nonDefaultValue: ConnectionPool = ConnectionPool(keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES) - override val badValue: ConnectionPool = ConnectionPool( - keepAliveDuration = 1, timeUnit = TimeUnit.MINUTES, connectionListener = object : ConnectionListener() { - override fun connectStart(route: Route, call: Call): Unit = TODO() - }) + override val badValue: ConnectionPool = + ConnectionPool( + keepAliveDuration = 1, + timeUnit = TimeUnit.MINUTES, + connectionListener = + object : ConnectionListener() { + override fun connectStart( + route: Route, + call: Call, + ): Unit = TODO() + }, + ) - override fun isDefaultValue(value: ConnectionPool): Boolean = - value.delegate.keepAliveDurationNs == 5.minutes.inWholeNanoseconds + override fun isDefaultValue(value: ConnectionPool): Boolean = value.delegate.keepAliveDurationNs == 5.minutes.inWholeNanoseconds } object ConnectTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = connectTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = - withConnectTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withConnectTimeout(value.toLong(), TimeUnit.MILLISECONDS) override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = connectTimeout(value.toLong(), TimeUnit.MILLISECONDS) @@ -724,11 +802,9 @@ class InterceptorOverridesTest { object ReadTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = readTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = - withReadTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withReadTimeout(value.toLong(), TimeUnit.MILLISECONDS) - override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = - readTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = readTimeout(value.toLong(), TimeUnit.MILLISECONDS) override val nonDefaultValue: Int = 5000 @@ -741,11 +817,9 @@ class InterceptorOverridesTest { object WriteTimeoutOverride : Override { override fun Interceptor.Chain.value(): Int = writeTimeoutMillis() - override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = - withWriteTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun Interceptor.Chain.withOverride(value: Int): Interceptor.Chain = withWriteTimeout(value.toLong(), TimeUnit.MILLISECONDS) - override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = - writeTimeout(value.toLong(), TimeUnit.MILLISECONDS) + override fun OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = writeTimeout(value.toLong(), TimeUnit.MILLISECONDS) override val nonDefaultValue: Int = 5000 @@ -758,11 +832,9 @@ class InterceptorOverridesTest { object RetryOnConnectionFailureOverride : Override { override fun Interceptor.Chain.value(): Boolean = retryOnConnectionFailure - override fun Interceptor.Chain.withOverride(value: Boolean): Interceptor.Chain = - withRetryOnConnectionFailure(value) + override fun Interceptor.Chain.withOverride(value: Boolean): Interceptor.Chain = withRetryOnConnectionFailure(value) - override fun OkHttpClient.Builder.withOverride(value: Boolean): OkHttpClient.Builder = - retryOnConnectionFailure(value) + override fun OkHttpClient.Builder.withOverride(value: Boolean): OkHttpClient.Builder = retryOnConnectionFailure(value) override val nonDefaultValue: Boolean = false @@ -774,10 +846,13 @@ class InterceptorOverridesTest { } private fun enableTls() { - client = client.newBuilder().sslSocketFactory( - handshakeCertificates.sslSocketFactory(), - handshakeCertificates.trustManager, - ).build() + client = + client + .newBuilder() + .sslSocketFactory( + handshakeCertificates.sslSocketFactory(), + handshakeCertificates.trustManager, + ).build() server.useHttps(handshakeCertificates.sslSocketFactory()) } } From efd106cca40ced68e0ac8ba5a0958fb034a16944 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sat, 13 Dec 2025 11:22:49 +0000 Subject: [PATCH 19/21] Use interceptor for redirect flags --- .../main/kotlin/okhttp3/TestValueFactory.kt | 1 - .../kotlin/okhttp3/Interceptor.kt | 6 ++++++ .../okhttp3/internal/cache/CacheInterceptor.kt | 18 +++++++++--------- .../okhttp3/internal/connection/RealCall.kt | 5 ++--- .../internal/http/RealInterceptorChain.kt | 12 +++++++++++- .../http/RetryAndFollowUpInterceptor.kt | 16 ++++++++-------- 6 files changed, 36 insertions(+), 22 deletions(-) diff --git a/okhttp-testing-support/src/main/kotlin/okhttp3/TestValueFactory.kt b/okhttp-testing-support/src/main/kotlin/okhttp3/TestValueFactory.kt index b3a2d5a0b80a..0c06968c1e30 100644 --- a/okhttp-testing-support/src/main/kotlin/okhttp3/TestValueFactory.kt +++ b/okhttp-testing-support/src/main/kotlin/okhttp3/TestValueFactory.kt @@ -170,7 +170,6 @@ class TestValueFactory : Closeable { index = 0, exchange = null, request = call.request(), - client = call.client, ) fun newRoutePlanner( diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt index e0a0aa0e74bd..662bc51d3042 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt @@ -137,6 +137,10 @@ fun interface Interceptor { unit: TimeUnit, ): Chain + val followSslRedirects: Boolean + + val followRedirects: Boolean + /** * Get the [DNS] instance for the OkHttpClient, or an override from the Call.Chain. */ @@ -288,5 +292,7 @@ fun interface Interceptor { * Returns a new chain with the specified [ConnectionPool]. */ fun withConnectionPool(connectionPool: ConnectionPool): Chain + + val eventListener: EventListener } } diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/CacheInterceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/CacheInterceptor.kt index 9ec3ad774dc8..ca354fa223c8 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/CacheInterceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/CacheInterceptor.kt @@ -40,11 +40,11 @@ import okio.Timeout import okio.buffer /** Serves requests from the cache and writes responses to the cache. */ -class CacheInterceptor( - internal val call: RealCall, -) : Interceptor { +class CacheInterceptor : Interceptor { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { + val call = chain.call() + val eventListener = chain.eventListener val cache = chain.cache val cacheCandidate = cache?.get(chain.request().requestForCache()) @@ -73,7 +73,7 @@ class CacheInterceptor( .receivedResponseAtMillis(System.currentTimeMillis()) .build() .also { - call.eventListener.satisfactionFailure(call, it) + eventListener.satisfactionFailure(call, it) } } @@ -84,14 +84,14 @@ class CacheInterceptor( .cacheResponse(cacheResponse.stripBody()) .build() .also { - call.eventListener.cacheHit(call, it) + eventListener.cacheHit(call, it) } } if (cacheResponse != null) { - call.eventListener.cacheConditionalHit(call, cacheResponse) + eventListener.cacheConditionalHit(call, cacheResponse) } else if (cache != null) { - call.eventListener.cacheMiss(call) + eventListener.cacheMiss(call) } var networkResponse: Response? = null @@ -124,7 +124,7 @@ class CacheInterceptor( cache!!.trackConditionalCacheHit() cache.update(cacheResponse, response) return response.also { - call.eventListener.cacheHit(call, it) + eventListener.cacheHit(call, it) } } else { cacheResponse.body.closeQuietly() @@ -147,7 +147,7 @@ class CacheInterceptor( return cacheWritingResponse(cacheRequest, response).also { if (cacheResponse != null) { // This will log a conditional cache miss only. - call.eventListener.cacheMiss(call) + eventListener.cacheMiss(call) } } } diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt index 588f1d5cb3bb..9c7721fc5978 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealCall.kt @@ -209,9 +209,9 @@ class RealCall( // Build a full stack of interceptors. val interceptors = mutableListOf() interceptors += client.interceptors - interceptors += RetryAndFollowUpInterceptor(client) + interceptors += RetryAndFollowUpInterceptor() interceptors += BridgeInterceptor() - interceptors += CacheInterceptor(this) + interceptors += CacheInterceptor() interceptors += ConnectInterceptor if (!forWebSocket) { interceptors += client.networkInterceptors @@ -225,7 +225,6 @@ class RealCall( index = 0, exchange = null, request = originalRequest, - client = client, ) var calledNoMoreExchanges = false diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt index 9d62383a464b..7cf3ff7819ee 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt @@ -32,6 +32,7 @@ import okhttp3.Connection import okhttp3.ConnectionPool import okhttp3.CookieJar import okhttp3.Dns +import okhttp3.EventListener import okhttp3.HttpUrl import okhttp3.Interceptor import okhttp3.OkHttpClient @@ -80,7 +81,7 @@ class RealInterceptorChain( index: Int, exchange: Nothing?, request: Request, - client: OkHttpClient, + client: OkHttpClient = call.client, ) : this( call, interceptors, @@ -157,6 +158,15 @@ class RealInterceptorChain( certificateChainCleaner, ) + override val eventListener: EventListener + get() = call.eventListener + + override val followSslRedirects: Boolean + get() = call.client.followSslRedirects + + override val followRedirects: Boolean + get() = call.client.followRedirects + override fun connection(): Connection? = exchange?.connection override fun connectTimeoutMillis(): Int = connectTimeoutMillis diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RetryAndFollowUpInterceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RetryAndFollowUpInterceptor.kt index 73c7823f48da..a28ea5bf6fed 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RetryAndFollowUpInterceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RetryAndFollowUpInterceptor.kt @@ -48,9 +48,7 @@ import okhttp3.internal.withSuppressed * This interceptor recovers from failures and follows redirects as necessary. It may throw an * [IOException] if the call was canceled. */ -class RetryAndFollowUpInterceptor( - private val client: OkHttpClient, -) : Interceptor { +class RetryAndFollowUpInterceptor : Interceptor { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val realChain = chain as RealInterceptorChain @@ -75,7 +73,7 @@ class RetryAndFollowUpInterceptor( newRoutePlanner = true } catch (e: IOException) { // An attempt to communicate with a server failed. The request may have been sent. - val isRecoverable = recover(e, call, request) + val isRecoverable = recover(e, call, chain, request) call.eventListener.retryDecision(call, e, isRecoverable) if (!isRecoverable) throw e.withSuppressed(recoveredFailures) recoveredFailures += e @@ -135,12 +133,13 @@ class RetryAndFollowUpInterceptor( private fun recover( e: IOException, call: RealCall, + chain: Interceptor.Chain, userRequest: Request, ): Boolean { val requestSendStarted = e !is ConnectionShutdownException // The application layer has forbidden retries. - if (!client.retryOnConnectionFailure) return false + if (!chain.retryOnConnectionFailure) return false // We can't send the request body again. if (requestSendStarted && requestIsOneShot(e, userRequest)) return false @@ -225,7 +224,7 @@ class RetryAndFollowUpInterceptor( HTTP_UNAUTHORIZED -> return chain.authenticator.authenticate(route, userResponse) HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> { - return buildRedirectRequest(userResponse, method) + return buildRedirectRequest(userResponse, method, chain) } HTTP_CLIENT_TIMEOUT -> { @@ -293,9 +292,10 @@ class RetryAndFollowUpInterceptor( private fun buildRedirectRequest( userResponse: Response, method: String, + chain: Interceptor.Chain, ): Request? { // Does the client allow redirects? - if (!client.followRedirects) return null + if (!chain.followRedirects) return null val location = userResponse.header("Location") ?: return null // Don't follow redirects to unsupported protocols. @@ -303,7 +303,7 @@ class RetryAndFollowUpInterceptor( // If configured, don't follow redirects between SSL and non-SSL. val sameScheme = url.scheme == userResponse.request.url.scheme - if (!sameScheme && !client.followSslRedirects) return null + if (!sameScheme && !chain.followSslRedirects) return null // Most redirects don't include a request body. val requestBuilder = userResponse.request.newBuilder() From 769cf1994626dfb5681a75d62ff81a979c72aaa4 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sat, 13 Dec 2025 11:59:26 +0000 Subject: [PATCH 20/21] Cleanup --- okhttp/api/android/okhttp.api | 18 +++++++-------- okhttp/api/jvm/okhttp.api | 18 +++++++-------- .../kotlin/okhttp3/KotlinSourceModernTest.kt | 23 +++++++++++++++---- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/okhttp/api/android/okhttp.api b/okhttp/api/android/okhttp.api index 5b84b1d347c8..8aa4a4a193eb 100644 --- a/okhttp/api/android/okhttp.api +++ b/okhttp/api/android/okhttp.api @@ -751,27 +751,28 @@ public abstract interface class okhttp3/Interceptor$Chain { public abstract fun connection ()Lokhttp3/Connection; public abstract fun getAuthenticator ()Lokhttp3/Authenticator; public abstract fun getCache ()Lokhttp3/Cache; - public abstract fun getCertificateChainCleaner ()Lokhttp3/internal/tls/CertificateChainCleaner; public abstract fun getCertificatePinner ()Lokhttp3/CertificatePinner; public abstract fun getConnectionPool ()Lokhttp3/ConnectionPool; public abstract fun getCookieJar ()Lokhttp3/CookieJar; public abstract fun getDns ()Lokhttp3/Dns; + public abstract fun getEventListener ()Lokhttp3/EventListener; + public abstract fun getFollowRedirects ()Z + public abstract fun getFollowSslRedirects ()Z public abstract fun getHostnameVerifier ()Ljavax/net/ssl/HostnameVerifier; public abstract fun getProxy ()Ljava/net/Proxy; public abstract fun getProxyAuthenticator ()Lokhttp3/Authenticator; public abstract fun getProxySelector ()Ljava/net/ProxySelector; public abstract fun getRetryOnConnectionFailure ()Z public abstract fun getSocketFactory ()Ljavax/net/SocketFactory; - public abstract fun getSslSocketFactory ()Ljavax/net/ssl/SSLSocketFactory; - public abstract fun getX509TrustManager ()Ljavax/net/ssl/X509TrustManager; + public abstract fun getSslSocketFactoryOrNull ()Ljavax/net/ssl/SSLSocketFactory; + public abstract fun getX509TrustManagerOrNull ()Ljavax/net/ssl/X509TrustManager; public abstract fun proceed (Lokhttp3/Request;)Lokhttp3/Response; public abstract fun readTimeoutMillis ()I public abstract fun request ()Lokhttp3/Request; public abstract fun withAuthenticator (Lokhttp3/Authenticator;)Lokhttp3/Interceptor$Chain; public abstract fun withCache (Lokhttp3/Cache;)Lokhttp3/Interceptor$Chain; - public abstract fun withCertificateChainCleaner (Lokhttp3/internal/tls/CertificateChainCleaner;)Lokhttp3/Interceptor$Chain; public abstract fun withCertificatePinner (Lokhttp3/CertificatePinner;)Lokhttp3/Interceptor$Chain; - public abstract fun withConnectTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; + public abstract fun withConnectTimeout (JLjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; public abstract fun withConnectionPool (Lokhttp3/ConnectionPool;)Lokhttp3/Interceptor$Chain; public abstract fun withCookieJar (Lokhttp3/CookieJar;)Lokhttp3/Interceptor$Chain; public abstract fun withDns (Lokhttp3/Dns;)Lokhttp3/Interceptor$Chain; @@ -779,12 +780,11 @@ public abstract interface class okhttp3/Interceptor$Chain { public abstract fun withProxy (Ljava/net/Proxy;)Lokhttp3/Interceptor$Chain; public abstract fun withProxyAuthenticator (Lokhttp3/Authenticator;)Lokhttp3/Interceptor$Chain; public abstract fun withProxySelector (Ljava/net/ProxySelector;)Lokhttp3/Interceptor$Chain; - public abstract fun withReadTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; + public abstract fun withReadTimeout (JLjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; public abstract fun withRetryOnConnectionFailure (Z)Lokhttp3/Interceptor$Chain; public abstract fun withSocketFactory (Ljavax/net/SocketFactory;)Lokhttp3/Interceptor$Chain; - public abstract fun withSslSocketFactory (Ljavax/net/ssl/SSLSocketFactory;)Lokhttp3/Interceptor$Chain; - public abstract fun withWriteTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; - public abstract fun withX509TrustManager (Ljavax/net/ssl/X509TrustManager;)Lokhttp3/Interceptor$Chain; + public abstract fun withSslSocketFactory (Ljavax/net/ssl/SSLSocketFactory;Ljavax/net/ssl/X509TrustManager;)Lokhttp3/Interceptor$Chain; + public abstract fun withWriteTimeout (JLjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; public abstract fun writeTimeoutMillis ()I } diff --git a/okhttp/api/jvm/okhttp.api b/okhttp/api/jvm/okhttp.api index a9375a66b806..d26a8fc26b40 100644 --- a/okhttp/api/jvm/okhttp.api +++ b/okhttp/api/jvm/okhttp.api @@ -751,27 +751,28 @@ public abstract interface class okhttp3/Interceptor$Chain { public abstract fun connection ()Lokhttp3/Connection; public abstract fun getAuthenticator ()Lokhttp3/Authenticator; public abstract fun getCache ()Lokhttp3/Cache; - public abstract fun getCertificateChainCleaner ()Lokhttp3/internal/tls/CertificateChainCleaner; public abstract fun getCertificatePinner ()Lokhttp3/CertificatePinner; public abstract fun getConnectionPool ()Lokhttp3/ConnectionPool; public abstract fun getCookieJar ()Lokhttp3/CookieJar; public abstract fun getDns ()Lokhttp3/Dns; + public abstract fun getEventListener ()Lokhttp3/EventListener; + public abstract fun getFollowRedirects ()Z + public abstract fun getFollowSslRedirects ()Z public abstract fun getHostnameVerifier ()Ljavax/net/ssl/HostnameVerifier; public abstract fun getProxy ()Ljava/net/Proxy; public abstract fun getProxyAuthenticator ()Lokhttp3/Authenticator; public abstract fun getProxySelector ()Ljava/net/ProxySelector; public abstract fun getRetryOnConnectionFailure ()Z public abstract fun getSocketFactory ()Ljavax/net/SocketFactory; - public abstract fun getSslSocketFactory ()Ljavax/net/ssl/SSLSocketFactory; - public abstract fun getX509TrustManager ()Ljavax/net/ssl/X509TrustManager; + public abstract fun getSslSocketFactoryOrNull ()Ljavax/net/ssl/SSLSocketFactory; + public abstract fun getX509TrustManagerOrNull ()Ljavax/net/ssl/X509TrustManager; public abstract fun proceed (Lokhttp3/Request;)Lokhttp3/Response; public abstract fun readTimeoutMillis ()I public abstract fun request ()Lokhttp3/Request; public abstract fun withAuthenticator (Lokhttp3/Authenticator;)Lokhttp3/Interceptor$Chain; public abstract fun withCache (Lokhttp3/Cache;)Lokhttp3/Interceptor$Chain; - public abstract fun withCertificateChainCleaner (Lokhttp3/internal/tls/CertificateChainCleaner;)Lokhttp3/Interceptor$Chain; public abstract fun withCertificatePinner (Lokhttp3/CertificatePinner;)Lokhttp3/Interceptor$Chain; - public abstract fun withConnectTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; + public abstract fun withConnectTimeout (JLjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; public abstract fun withConnectionPool (Lokhttp3/ConnectionPool;)Lokhttp3/Interceptor$Chain; public abstract fun withCookieJar (Lokhttp3/CookieJar;)Lokhttp3/Interceptor$Chain; public abstract fun withDns (Lokhttp3/Dns;)Lokhttp3/Interceptor$Chain; @@ -779,12 +780,11 @@ public abstract interface class okhttp3/Interceptor$Chain { public abstract fun withProxy (Ljava/net/Proxy;)Lokhttp3/Interceptor$Chain; public abstract fun withProxyAuthenticator (Lokhttp3/Authenticator;)Lokhttp3/Interceptor$Chain; public abstract fun withProxySelector (Ljava/net/ProxySelector;)Lokhttp3/Interceptor$Chain; - public abstract fun withReadTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; + public abstract fun withReadTimeout (JLjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; public abstract fun withRetryOnConnectionFailure (Z)Lokhttp3/Interceptor$Chain; public abstract fun withSocketFactory (Ljavax/net/SocketFactory;)Lokhttp3/Interceptor$Chain; - public abstract fun withSslSocketFactory (Ljavax/net/ssl/SSLSocketFactory;)Lokhttp3/Interceptor$Chain; - public abstract fun withWriteTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; - public abstract fun withX509TrustManager (Ljavax/net/ssl/X509TrustManager;)Lokhttp3/Interceptor$Chain; + public abstract fun withSslSocketFactory (Ljavax/net/ssl/SSLSocketFactory;Ljavax/net/ssl/X509TrustManager;)Lokhttp3/Interceptor$Chain; + public abstract fun withWriteTimeout (JLjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; public abstract fun writeTimeoutMillis ()I } diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt index 95d5c706ddfb..0565ce66afe8 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package okhttp3 import java.io.File @@ -61,7 +62,6 @@ import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.internal.authenticator.JavaNetAuthenticator import okhttp3.internal.http2.Settings import okhttp3.internal.proxy.NullProxySelector -import okhttp3.internal.tls.CertificateChainCleaner import okhttp3.internal.tls.OkHostnameVerifier import okhttp3.java.net.cookiejar.JavaNetCookieJar import okhttp3.logging.HttpLoggingInterceptor @@ -92,13 +92,19 @@ import org.junit.jupiter.api.Test */ @Suppress( "ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE", + "AssignedValueIsNeverRead", + "CanBeVal", + "DEPRECATION", + "IMPLICIT_NOTHING_AS_TYPE_PARAMETER", + "RedundantExplicitType", + "RedundantNullableReturnType", "UNUSED_ANONYMOUS_PARAMETER", "UNUSED_VALUE", "UNUSED_VARIABLE", "VARIABLE_WITH_REDUNDANT_INITIALIZER", - "RedundantLambdaArrow", - "RedundantExplicitType", - "IMPLICIT_NOTHING_AS_TYPE_PARAMETER", + "VariableInitializerIsRedundant", + "VariableNeverRead", + "unused", ) @Disabled class KotlinSourceModernTest { @@ -384,7 +390,7 @@ class KotlinSourceModernTest { val maxRequestsPerHost: Int = dispatcher.maxRequestsPerHost dispatcher.maxRequestsPerHost = 0 val executorService: ExecutorService = dispatcher.executorService - dispatcher.idleCallback = Runnable { ({ TODO() })() } + dispatcher.idleCallback = Runnable { TODO() } val queuedCalls: List = dispatcher.queuedCalls() val runningCalls: List = dispatcher.runningCalls() val queuedCallsCount: Int = dispatcher.queuedCallsCount() @@ -1448,5 +1454,12 @@ class KotlinSourceModernTest { override fun withConnectionPool(connectionPool: ConnectionPool): Interceptor.Chain { TODO() } + + override val followSslRedirects: Boolean + get() = TODO() + override val followRedirects: Boolean + get() = TODO() + override val eventListener: EventListener + get() = TODO() } } From 5894c956aae10c43dfb8d27e6c5a6dc2e82dffd5 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 14 Dec 2025 06:09:54 +0000 Subject: [PATCH 21/21] Fix test --- .../okhttp3/internal/cache/CacheInterceptor.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/CacheInterceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/CacheInterceptor.kt index ca354fa223c8..4482f905b3bd 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/CacheInterceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/CacheInterceptor.kt @@ -44,7 +44,6 @@ class CacheInterceptor : Interceptor { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val call = chain.call() - val eventListener = chain.eventListener val cache = chain.cache val cacheCandidate = cache?.get(chain.request().requestForCache()) @@ -73,7 +72,7 @@ class CacheInterceptor : Interceptor { .receivedResponseAtMillis(System.currentTimeMillis()) .build() .also { - eventListener.satisfactionFailure(call, it) + chain.eventListener.satisfactionFailure(call, it) } } @@ -84,14 +83,14 @@ class CacheInterceptor : Interceptor { .cacheResponse(cacheResponse.stripBody()) .build() .also { - eventListener.cacheHit(call, it) + chain.eventListener.cacheHit(call, it) } } if (cacheResponse != null) { - eventListener.cacheConditionalHit(call, cacheResponse) + chain.eventListener.cacheConditionalHit(call, cacheResponse) } else if (cache != null) { - eventListener.cacheMiss(call) + chain.eventListener.cacheMiss(call) } var networkResponse: Response? = null @@ -124,7 +123,7 @@ class CacheInterceptor : Interceptor { cache!!.trackConditionalCacheHit() cache.update(cacheResponse, response) return response.also { - eventListener.cacheHit(call, it) + chain.eventListener.cacheHit(call, it) } } else { cacheResponse.body.closeQuietly() @@ -147,7 +146,7 @@ class CacheInterceptor : Interceptor { return cacheWritingResponse(cacheRequest, response).also { if (cacheResponse != null) { // This will log a conditional cache miss only. - eventListener.cacheMiss(call) + chain.eventListener.cacheMiss(call) } } }