diff --git a/okhttp-testing-support/src/main/kotlin/okhttp3/TestValueFactory.kt b/okhttp-testing-support/src/main/kotlin/okhttp3/TestValueFactory.kt index 948482c2ca35..0c06968c1e30 100644 --- a/okhttp-testing-support/src/main/kotlin/okhttp3/TestValueFactory.kt +++ b/okhttp-testing-support/src/main/kotlin/okhttp3/TestValueFactory.kt @@ -170,9 +170,6 @@ class TestValueFactory : Closeable { index = 0, exchange = null, request = call.request(), - connectTimeoutMillis = 10_000, - readTimeoutMillis = 10_000, - writeTimeoutMillis = 10_000, ) fun newRoutePlanner( diff --git a/okhttp/api/android/okhttp.api b/okhttp/api/android/okhttp.api index ac992d1ad6d4..5c2fb7b67142 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 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 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 withConnectTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; - public abstract fun withReadTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; - public abstract fun withWriteTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; + public abstract fun withAuthenticator (Lokhttp3/Authenticator;)Lokhttp3/Interceptor$Chain; + public abstract fun withCache (Lokhttp3/Cache;)Lokhttp3/Interceptor$Chain; + public abstract fun withCertificatePinner (Lokhttp3/CertificatePinner;)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; + 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 (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;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 b6ef6340d152..62eead9baf56 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 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 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 withConnectTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; - public abstract fun withReadTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; - public abstract fun withWriteTimeout (ILjava/util/concurrent/TimeUnit;)Lokhttp3/Interceptor$Chain; + public abstract fun withAuthenticator (Lokhttp3/Authenticator;)Lokhttp3/Interceptor$Chain; + public abstract fun withCache (Lokhttp3/Cache;)Lokhttp3/Interceptor$Chain; + public abstract fun withCertificatePinner (Lokhttp3/CertificatePinner;)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; + 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 (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;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/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt index bfa1f582326c..662bc51d3042 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Interceptor.kt @@ -16,7 +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 @@ -82,31 +89,210 @@ 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, + timeout: Long, 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, + timeout: Long, 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, + timeout: Long, unit: TimeUnit, ): Chain + + val followSslRedirects: Boolean + + val followRedirects: Boolean + + /** + * 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? + + /** + * 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? + + /** + * 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 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?, + x509TrustManager: X509TrustManager?, + ): Chain + + /** + * Returns the [X509TrustManager] for the OkHttpClient, or an override from the Call.Chain. + */ + val x509TrustManagerOrNull: X509TrustManager? + + /** + * 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 [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 + + val eventListener: EventListener } } 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/cache/CacheInterceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/CacheInterceptor.kt index e76c977c2eb8..4482f905b3bd 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/CacheInterceptor.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/CacheInterceptor.kt @@ -40,12 +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, - internal val cache: Cache?, -) : Interceptor { +class CacheInterceptor : Interceptor { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { + val call = chain.call() + val cache = chain.cache val cacheCandidate = cache?.get(chain.request().requestForCache()) val now = System.currentTimeMillis() @@ -73,7 +72,7 @@ class CacheInterceptor( .receivedResponseAtMillis(System.currentTimeMillis()) .build() .also { - call.eventListener.satisfactionFailure(call, it) + chain.eventListener.satisfactionFailure(call, it) } } @@ -84,14 +83,14 @@ class CacheInterceptor( .cacheResponse(cacheResponse.stripBody()) .build() .also { - call.eventListener.cacheHit(call, it) + chain.eventListener.cacheHit(call, it) } } if (cacheResponse != null) { - call.eventListener.cacheConditionalHit(call, cacheResponse) + chain.eventListener.cacheConditionalHit(call, cacheResponse) } else if (cache != null) { - call.eventListener.cacheMiss(call) + chain.eventListener.cacheMiss(call) } var networkResponse: Response? = null @@ -124,7 +123,7 @@ class CacheInterceptor( cache!!.trackConditionalCacheHit() cache.update(cacheResponse, response) return response.also { - call.eventListener.cacheHit(call, it) + chain.eventListener.cacheHit(call, it) } } else { cacheResponse.body.closeQuietly() @@ -147,7 +146,7 @@ class CacheInterceptor( return cacheWritingResponse(cacheRequest, response).also { if (cacheResponse != null) { // This will log a conditional cache miss only. - call.eventListener.cacheMiss(call) + chain.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 e2a4f7381024..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 += BridgeInterceptor(client.cookieJar) - interceptors += CacheInterceptor(this, client.cache) + interceptors += RetryAndFollowUpInterceptor() + interceptors += BridgeInterceptor() + interceptors += CacheInterceptor() interceptors += ConnectInterceptor if (!forWebSocket) { interceptors += client.networkInterceptors @@ -225,9 +225,6 @@ class RealCall( index = 0, exchange = null, request = originalRequest, - connectTimeoutMillis = client.connectTimeoutMillis, - readTimeoutMillis = client.readTimeoutMillis, - writeTimeoutMillis = client.writeTimeoutMillis, ) var calledNoMoreExchanges = false @@ -275,15 +272,15 @@ class RealCall( val routePlanner = RealRoutePlanner( taskRunner = client.taskRunner, - connectionPool = connectionPool, - readTimeoutMillis = client.readTimeoutMillis, - writeTimeoutMillis = client.writeTimeoutMillis, + connectionPool = chain.connectionPool.delegate, + readTimeoutMillis = chain.readTimeoutMillis, + writeTimeoutMillis = chain.writeTimeoutMillis, socketConnectTimeoutMillis = chain.connectTimeoutMillis, socketReadTimeoutMillis = chain.readTimeoutMillis, pingIntervalMillis = client.pingIntervalMillis, - retryOnConnectionFailure = client.retryOnConnectionFailure, + retryOnConnectionFailure = chain.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/BridgeInterceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/BridgeInterceptor.kt index e5ff80a89552..3d0bfba56758 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 e0a294271ddb..7cf3ff7819ee 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RealInterceptorChain.kt @@ -16,15 +16,32 @@ 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.EventListener +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 @@ -42,7 +59,55 @@ class RealInterceptorChain( internal val connectTimeoutMillis: Int, internal val readTimeoutMillis: Int, internal val writeTimeoutMillis: Int, + override val authenticator: Authenticator, + override val cache: Cache?, + 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 sslSocketFactoryOrNull: SSLSocketFactory?, + override val x509TrustManagerOrNull: X509TrustManager?, + val certificateChainCleaner: CertificateChainCleaner?, ) : Interceptor.Chain { + internal constructor( + call: RealCall, + interceptors: List, + index: Int, + exchange: Nothing?, + request: Request, + client: OkHttpClient = call.client, + ) : this( + call, + interceptors, + index, + exchange, + request, + client.connectTimeoutMillis, + client.readTimeoutMillis, + client.writeTimeoutMillis, + client.authenticator, + client.cache, + client.certificatePinner, + client.connectionPool, + client.cookieJar, + client.dns, + client.hostnameVerifier, + client.proxy, + client.proxyAuthenticator, + client.proxySelector, + client.retryOnConnectionFailure, + client.socketFactory, + client.sslSocketFactoryOrNull, + client.x509TrustManager, + client.certificateChainCleaner, + ) + private var calls: Int = 0 internal fun copy( @@ -52,6 +117,21 @@ class RealInterceptorChain( connectTimeoutMillis: Int = this.connectTimeoutMillis, readTimeoutMillis: Int = this.readTimeoutMillis, writeTimeoutMillis: Int = this.writeTimeoutMillis, + authenticator: Authenticator = this.authenticator, + cache: Cache? = this.cache, + 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.sslSocketFactoryOrNull, + x509TrustManager: X509TrustManager? = this.x509TrustManagerOrNull, + certificateChainCleaner: CertificateChainCleaner? = this.certificateChainCleaner, ) = RealInterceptorChain( call, interceptors, @@ -61,41 +141,167 @@ class RealInterceptorChain( connectTimeoutMillis, readTimeoutMillis, writeTimeoutMillis, + authenticator, + cache, + certificatePinner, + connectionPool, + cookieJar, + dns, + hostnameVerifier, + proxy, + proxyAuthenticator, + proxySelector, + retryOnConnectionFailure, + socketFactory, + sslSocketFactory, + x509TrustManager, + 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 override fun withConnectTimeout( - timeout: Int, + timeout: Long, unit: TimeUnit, ): 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 override fun withReadTimeout( - timeout: Int, + timeout: Long, unit: TimeUnit, ): 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 override fun withWriteTimeout( - timeout: Int, + timeout: Long, unit: TimeUnit, ): 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 { + check(exchange == null) { "dns can't be adjusted in a network interceptor" } + + 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) + } + + override fun withRetryOnConnectionFailure(retryOnConnectionFailure: Boolean): Interceptor.Chain { + check(exchange == null) { "retryOnConnectionFailure can't be adjusted in a network interceptor" } + + 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?, + x509TrustManager: X509TrustManager?, + ): Interceptor.Chain { + check(exchange == null) { "sslSocketFactory can't be adjusted in a network interceptor" } + + 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 { + 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" } + + 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 @@ -135,4 +341,34 @@ 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 = this.sslSocketFactoryOrNull + useHostnameVerifier = this.hostnameVerifier + useCertificatePinner = this.certificatePinner + } + + return Address( + uriHost = url.host, + uriPort = url.port, + dns = dns, + socketFactory = socketFactory, + sslSocketFactory = useSslSocketFactory, + hostnameVerifier = useHostnameVerifier, + certificatePinner = useCertificatePinner, + proxyAuthenticator = proxyAuthenticator, + proxy = proxy, + protocols = call.client.protocols, + connectionSpecs = call.client.connectionSpecs, + proxySelector = proxySelector, + ) + } } diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RetryAndFollowUpInterceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http/RetryAndFollowUpInterceptor.kt index d4ab34b059ed..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 @@ -92,7 +90,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) { @@ -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 @@ -207,6 +206,7 @@ class RetryAndFollowUpInterceptor( private fun followUpRequest( userResponse: Response, exchange: Exchange?, + chain: Interceptor.Chain, ): Request? { val route = exchange?.connection?.route() val responseCode = userResponse.code @@ -218,20 +218,20 @@ 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) + return buildRedirectRequest(userResponse, method, chain) } HTTP_CLIENT_TIMEOUT -> { // 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 } @@ -292,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. @@ -302,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() diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt new file mode 100644 index 000000000000..8c2a6af5dfc2 --- /dev/null +++ b/okhttp/src/jvmTest/kotlin/okhttp3/InterceptorOverridesTest.kt @@ -0,0 +1,858 @@ +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.isFailure +import assertk.assertions.isFalse +import assertk.assertions.isNotSameInstanceAs +import assertk.assertions.isTrue +import java.io.FilterInputStream +import java.io.FilterOutputStream +import java.io.InputStream +import java.io.OutputStream +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 +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.junit5.StartStop +import okhttp3.CertificatePinner.Companion.pin +import okhttp3.Headers.Companion.headersOf +import okhttp3.internal.connection.ConnectionListener +import okhttp3.internal.platform.Platform +import okhttp3.testing.PlatformRule +import okio.BufferedSink +import okio.ForwardingFileSystem +import okio.IOException +import okio.Path +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.Builder().build() + + 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( + 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 { + 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() + response.close() + } + } + + /** + * Test that we can't override in a Network Interceptor, which will throw an exception. + */ + @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, + ), + ) { + 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() + response.close() + } + } + + /** + * 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( + 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(), + ) + + overrideBadImplementation(override = override.override, testItFails = testItFails) + } + + OverrideParam.Authenticator -> { + server.enqueue( + MockResponse.Builder().code(401).build(), + ) + + overrideBadImplementation(override = override.override, testItFails = testItFails) + } + + 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() + + overrideBadImplementation( + override = Override.RetryOnConnectionFailureOverride, + testItFails = testItFails, + badValue = false, + goodValue = true, + ) + } + + OverrideParam.SslSocketFactory -> { + enableTls() + overrideBadImplementation( + override = Override.SslSocketFactoryOverride, + testItFails = testItFails, + goodValue = handshakeCertificates.sslSocketFactory(), + ) + } + + OverrideParam.X509TrustManager -> { + enableTls() + overrideBadImplementation( + override = Override.X509TrustManagerOverride, + testItFails = testItFails, + goodValue = handshakeCertificates.trustManager, + ) + } + + OverrideParam.HostnameVerifier -> { + enableTls() + overrideBadImplementation(override = override.override, testItFails = testItFails) + } + + 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() + } + } + } + overrideBadImplementation(override = override.override, testItFails = testItFails, body = body) + } + + OverrideParam.ReadTimeout -> { + 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() + + 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, + 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() + + server.enqueue( + MockResponse(), + ) + val call = client.newCall(Request(server.url("/"), body = body)) + val result = runCatching { call.execute().body.bytes() } + + 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, + ) { + 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() = override.paramName ?: name.replaceFirstChar { it.lowercase(getDefault()) } + } + + class DelayingSocketFactory( + 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(timeout) + super.connect(endpoint, timeout) + } + + override fun getInputStream(): InputStream { + return object : FilterInputStream(super.inputStream) { + override fun read( + b: ByteArray?, + off: Int, + len: Int, + ): Int { + onRead() + return super.read(b, off, len) + } + } + } + + override fun getOutputStream(): OutputStream = + object : FilterOutputStream(super.outputStream) { + override fun write( + b: ByteArray?, + off: Int, + len: Int, + ) { + onWrite() + super.write(b, off, len) + } + } + } + } + } + + sealed interface Override { + fun Interceptor.Chain.value(): T + + 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 { + override fun Interceptor.Chain.value(): Dns = dns + + 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 + } + + object SocketFactoryOverride : Override { + override fun Interceptor.Chain.value(): SocketFactory = socketFactory + + 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() + } + + object AuthenticatorOverride : Override { + override fun Interceptor.Chain.value(): Authenticator = authenticator + + 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 -> response.request } + + override val badValue: Authenticator = Authenticator { route, response -> TODO() } + + 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 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 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 + } + + object CacheOverride : Override { + override fun Interceptor.Chain.value(): Cache? = cache + + 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 + } + + 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 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 + } + + object ProxySelectorOverride : Override { + override fun Interceptor.Chain.value(): ProxySelector = proxySelector + + 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 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?, + 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 OkHttpClient.Builder.withOverride(value: Authenticator): OkHttpClient.Builder = proxyAuthenticator(value) + + override val nonDefaultValue: Authenticator = Authenticator { route, response -> response.request } + + 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? = sslSocketFactoryOrNull + + 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 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 val paramName: String = "sslSocketFactory" + + override fun Interceptor.Chain.value(): X509TrustManager? = x509TrustManagerOrNull + + override fun Interceptor.Chain.withOverride(value: X509TrustManager?): Interceptor.Chain = + withSslSocketFactory(Platform.get().newSslSocketFactory(value!!), value) + + 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 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 fun checkServerTrusted( + x509Certificates: Array, + s: String, + ) { + TODO() + } + + 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 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 + } + + object CertificatePinnerOverride : Override { + override fun Interceptor.Chain.value(): CertificatePinner = certificatePinner + + 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 val badValue: CertificatePinner = + CertificatePinner.Builder().add("localhost", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=").build() + + 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 OkHttpClient.Builder.withOverride(value: Int): OkHttpClient.Builder = + connectTimeout(value.toLong(), TimeUnit.MILLISECONDS) + + override val nonDefaultValue: Int = 5000 + + override val badValue: Int + get() = 10 + + 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.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() = 10 + + 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.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() = 10 + + 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 OkHttpClient.Builder.withOverride(value: Boolean): OkHttpClient.Builder = retryOnConnectionFailure(value) + + override val nonDefaultValue: Boolean = false + + override val badValue: Boolean + get() = false + + override fun isDefaultValue(value: Boolean): Boolean = value + } + } + + private fun enableTls() { + client = + client + .newBuilder() + .sslSocketFactory( + handshakeCertificates.sslSocketFactory(), + handshakeCertificates.trustManager, + ).build() + server.useHttps(handshakeCertificates.sslSocketFactory()) + } +} diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt index 6de0f552082b..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 @@ -91,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 { @@ -383,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() @@ -1344,22 +1351,115 @@ 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() + + 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 sslSocketFactoryOrNull: SSLSocketFactory + get() = TODO() + override val x509TrustManagerOrNull: X509TrustManager + get() = TODO() + override val hostnameVerifier: HostnameVerifier + get() = TODO() + override val certificatePinner: CertificatePinner + 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?, + x509TrustManager: X509TrustManager?, + ): Interceptor.Chain { + TODO() + } + + override fun withHostnameVerifier(hostnameVerifier: HostnameVerifier): Interceptor.Chain { + TODO() + } + + override fun withCertificatePinner(certificatePinner: CertificatePinner): Interceptor.Chain { + TODO() + } + + 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() } }