1
0
mirror of https://github.com/square/okhttp.git synced 2026-01-12 10:23:16 +03:00

Allow OkHttpClient overrides in Interceptor (#9108)

Allow OkHttpClient overrides in Interceptor, generally anything settable on OkHttpClient.Builder can be overridden in application interceptor.

Tests that the effect of overrides takes effect and can be reversed.
This commit is contained in:
Yuri Schimke
2026-01-11 12:06:21 +00:00
committed by GitHub
parent a587ecddc6
commit 7c4b35ba27
12 changed files with 1497 additions and 65 deletions

View File

@@ -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(

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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)
}
}
}

View File

@@ -209,9 +209,9 @@ class RealCall(
// Build a full stack of interceptors.
val interceptors = mutableListOf<Interceptor>()
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,

View File

@@ -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

View File

@@ -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<Interceptor>,
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 clients 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,
)
}
}

View File

@@ -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()

View File

@@ -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 <T> Override<T>.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 <T> overrideBadImplementation(
override: Override<T>,
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<T> {
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<Dns> {
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<SocketFactory> {
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<Authenticator> {
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<CookieJar> {
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<Cookie>,
) {
}
override fun loadForRequest(url: HttpUrl): List<Cookie> = emptyList()
}
override val badValue: CookieJar =
object : CookieJar {
override fun saveFromResponse(
url: HttpUrl,
cookies: List<Cookie>,
) {
}
override fun loadForRequest(url: HttpUrl): List<Cookie> = TODO()
}
override fun isDefaultValue(value: CookieJar): Boolean = value === CookieJar.NO_COOKIES
}
object CacheOverride : Override<Cache?> {
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<java.net.Proxy?> {
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<ProxySelector> {
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<Proxy> = 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<Proxy> = TODO()
override fun connectFailed(
uri: URI?,
sa: SocketAddress?,
ioe: java.io.IOException?,
) {
}
}
override fun isDefaultValue(value: ProxySelector): Boolean = value === ProxySelector.getDefault()
}
object ProxyAuthenticatorOverride : Override<Authenticator> {
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<SSLSocketFactory?> {
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<X509TrustManager?> {
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<X509Certificate>,
s: String,
) {
}
override fun checkServerTrusted(
x509Certificates: Array<X509Certificate>,
s: String,
) {
}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
}
override val badValue: X509TrustManager =
object : X509TrustManager {
override fun checkClientTrusted(
x509Certificates: Array<X509Certificate>,
s: String,
) {
}
override fun checkServerTrusted(
x509Certificates: Array<X509Certificate>,
s: String,
) {
TODO()
}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
}
override fun isDefaultValue(value: X509TrustManager?): Boolean =
!value
?.javaClass
?.name
.orEmpty()
.startsWith("okhttp")
}
object HostnameVerifierOverride : Override<HostnameVerifier> {
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<CertificatePinner> {
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<ConnectionPool> {
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<Int> {
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<Int> {
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<Int> {
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<Boolean> {
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())
}
}

View File

@@ -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<Call> = dispatcher.queuedCalls()
val runningCalls: List<Call> = 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()
}
}