前言
现在,我们就来看看 OkHttpClient
中可配置的参数,也就是下面的这些属性。
kotlin
open class OkHttpClient internal constructor(
builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
@get:JvmName("dispatcher") val dispatcher: Dispatcher = builder.dispatcher
@get:JvmName("connectionPool") val connectionPool: ConnectionPool = builder.connectionPool
@get:JvmName("interceptors") val interceptors: List<Interceptor> =
builder.interceptors.toImmutableList()
@get:JvmName("networkInterceptors") val networkInterceptors: List<Interceptor> =
builder.networkInterceptors.toImmutableList()
@get:JvmName("eventListenerFactory") val eventListenerFactory: EventListener.Factory =
builder.eventListenerFactory
@get:JvmName("retryOnConnectionFailure") val retryOnConnectionFailure: Boolean =
builder.retryOnConnectionFailure
@get:JvmName("authenticator") val authenticator: Authenticator = builder.authenticator
@get:JvmName("followRedirects") val followRedirects: Boolean = builder.followRedirects
@get:JvmName("followSslRedirects") val followSslRedirects: Boolean = builder.followSslRedirects
@get:JvmName("cookieJar") val cookieJar: CookieJar = builder.cookieJar
@get:JvmName("cache") val cache: Cache? = builder.cache
@get:JvmName("dns") val dns: Dns = builder.dns
@get:JvmName("proxy") val proxy: Proxy? = builder.proxy
@get:JvmName("proxySelector") val proxySelector: ProxySelector =
when {
builder.proxy != null -> NullProxySelector
else -> builder.proxySelector ?: ProxySelector.getDefault() ?: NullProxySelector
}
@get:JvmName("proxyAuthenticator") val proxyAuthenticator: Authenticator =
builder.proxyAuthenticator
@get:JvmName("socketFactory") val socketFactory: SocketFactory = builder.socketFactory
@get:JvmName("sslSocketFactory") val sslSocketFactory: SSLSocketFactory
get() = sslSocketFactoryOrNull ?: throw IllegalStateException("CLEARTEXT-only client")
@get:JvmName("x509TrustManager") val x509TrustManager: X509TrustManager?
@get:JvmName("connectionSpecs") val connectionSpecs: List<ConnectionSpec> =
builder.connectionSpecs
@get:JvmName("protocols") val protocols: List<Protocol> = builder.protocols
@get:JvmName("hostnameVerifier") val hostnameVerifier: HostnameVerifier = builder.hostnameVerifier
@get:JvmName("certificatePinner") val certificatePinner: CertificatePinner
@get:JvmName("certificateChainCleaner") val certificateChainCleaner: CertificateChainCleaner?
@get:JvmName("callTimeoutMillis") val callTimeoutMillis: Int = builder.callTimeout
@get:JvmName("connectTimeoutMillis") val connectTimeoutMillis: Int = builder.connectTimeout
@get:JvmName("readTimeoutMillis") val readTimeoutMillis: Int = builder.readTimeout
@get:JvmName("writeTimeoutMillis") val writeTimeoutMillis: Int = builder.writeTimeout
@get:JvmName("pingIntervalMillis") val pingIntervalMillis: Int = builder.pingInterval
}
dispatcher: Dispatcher
它是请求的调度中心 ,其内部维护了一个线程池,负责执行异步请求,它的默认值是 Dispatcher()
。
kotlin
class Builder constructor() {
internal var dispatcher: Dispatcher = Dispatcher()
...
}
我们可以通过自定义 Dispatcher
对象来控制最大并发请求数和每个主机的最大并发请求数,以降低资源消耗。
kotlin
class Dispatcher constructor() {
@get:Synchronized var maxRequests = 64
set(maxRequests) {
require(maxRequests >= 1) { "max < 1: $maxRequests" }
synchronized(this) {
field = maxRequests
}
promoteAndExecute()
}
@get:Synchronized var maxRequestsPerHost = 5
set(maxRequestsPerHost) {
require(maxRequestsPerHost >= 1) { "max < 1: $maxRequestsPerHost" }
synchronized(this) {
field = maxRequestsPerHost
}
promoteAndExecute()
}
...
}
connectionPool: ConnectionPool
连接池,它的概念和线程池非常类似,内部存储了一批网络连接。
连接池的工作模式:
当需要一个连接时,会先从连接池中拿取现成、可用的连接。如果有,就拿走复用;如果没有,就创建新的连接。当一个连接使用完后,并不会立马销毁,而是会在连接池中等待后续使用。如果一个闲置的连接在连接池中等待过长时,可能会被销毁。具体是否销毁、等待多长后销毁,由连接池决定(连接池的配置)。
复用连接可以避免在内存中频繁创建和销毁对象,还可以省去 TCP 和 TLS 握手的过程,加快请求。
连接复用的两种形式:
-
在 HTTP/1.1 的连接中,请求结束后,连接变为空闲状态,后续对该主机的请求可以复用这个已经建立的连接。
-
在 HTTP2 的连接中(多路复用 Multiplexing),在同一个 TCP 连接上,可以同时发送多个请求。
interceptors
与 networkInterceptors
这两个的类型都是 List<Interceptor>
,也就是拦截器列表,默认值都是空列表。
kotlin
class Builder constructor() {
internal val interceptors: MutableList<Interceptor> = mutableListOf()
internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
...
}
其中 interceptors
是应用拦截器,是最外层的拦截器。在请求的整个生命周期中只会被调用一次,你可以看到最原始的请求。
networkInterceptors
是靠近网络层的拦截器,如果请求发生了重定向或是重试时,该拦截器就会被调用多次。
eventListenerFactory: EventListener.Factory
这是用于生产 eventListener
的工厂,而 EventListener
是监听器,监听网络请求中的各个过程,比如 TCP 连接建立、请求发起、接收到响应等。
retryOnConnectionFailure: Boolean
它表示连接失败时是否重试,默认值是 true
。不过它不只是连接失败,某些请求失败的情况也会处理。
如果值为 true
,那么当连接失败(如 TCP 连接建立失败)或某些请求失败(如连接建立成功,但对方未响应)时,OkHttp 会自动进行重试。
authenticator: Authenticator
它是自动进行认证修正的工具。
当 Token 失效(过期)时,服务器返回的状态码会是 401
,表示未授权。而我们可以使用 Authenticator
在收到 401
响应时被自动回调,来无感刷新 Token 并使用新的 Token 发起请求。
像这样来使用:
kotlin
val client = OkHttpClient.Builder()
.authenticator(object : Authenticator {
override fun authenticate(route: Route?, response: Response): Request {
// 同步获取新的 Token
val newToken = "newToken"
// 获取响应对应的原始请求
// 然后创建新的请求,设置新的 Token,并返回
return response.request.newBuilder()
.header("Authorization", "Bearer $newToken")
.build()
}
}).build()
followRedirects: Boolean
跟随重定向,表示当服务器返回的状态码为 301、302 时,OkHttp 是否自动发送新的请求到重定向网址。
值为 true 时,OkHttp 会自动请求新的地址,并将最终的、非重定向的响应结果返回。而为 false 时,会直接将重定向状态的响应结果返回。
它的默认值为 true
,我们一般也是开着的,因为我们开发者通常只关心最终的内容。
followSslRedirects: Boolean
表示在 followRedirects
为 true
的前提下,重定向发生协议切换时,是否继续跟随。
协议切换指的是从 http://
重定向到 https://
,当然从 https://
重定向到 http://
也是。
cookieJar: CookieJar
CookieJar
的字面意思是饼干罐子,它其实就是用来存储和管理 Cookie 的工具。
其默认实现 CookieJar.NO_COOKIES
是空的。也就是存储时,什么也不干;当取用时,只返回一个空列表。
kotlin
interface CookieJar {
fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>)
fun loadForRequest(url: HttpUrl): List<Cookie>
companion object {
@JvmField
val NO_COOKIES: CookieJar = NoCookies()
private class NoCookies : CookieJar {
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
}
override fun loadForRequest(url: HttpUrl): List<Cookie> {
return emptyList()
}
}
}
}
当然客户端也用不上,因为 Cookie 主要是浏览器的功能,客户端会更多使用 Authorization
Header 进行认证。
cache: Cache?
它用于配置 HTTP 缓存。配置后,OkHttp 会根据 HTTP 响应头的缓存规则,来存储响应内容。
dns: Dns
域名解析系统,用于将网址(域名)解析成服务器的 IP 地址。
其默认值是 Dns.SYSTEM
,也就是下面的伴生对象。
kotlin
interface Dns {
@Throws(UnknownHostException::class)
fun lookup(hostname: String): List<InetAddress>
companion object {
@JvmField
val SYSTEM: Dns = DnsSystem()
private class DnsSystem : Dns {
override fun lookup(hostname: String): List<InetAddress> {
try {
return InetAddress.getAllByName(hostname).toList()
} catch (e: NullPointerException) {
throw UnknownHostException("Broken system behaviour for dns lookup of $hostname").apply {
initCause(e)
}
}
}
}
}
}
默认使用 Java 原生的 InetAddress.getAllByName(hostname)
方法,来进行 DNS 查询,返回的是一个 InetAddress[]
数组。
proxy: Proxy?
与 proxySelector: ProxySelector
Proxy
是代理的意思,用于配置一个代理服务器。
客户端不会直接向目标服务器发送请求,而是会发给代理服务器,让它帮助我们转发请求和响应。
使用场景:当你被限制访问某个资源时,你可以让不被限制的代理服务器帮助你访问该资源。
代理有三种类型,分别是 DIRECT
、HTTP
以及 SOCKS
。不过 proxy
的默认值是 null
,那么代理是什么?
如果通过 .proxy()
方法设置了代理,就会始终使用该代理。在没有设置代理的情况下 ,才会通过 proxySelector
代理选择器来决定的,其选择逻辑为:
kotlin
// OkHttpClient.kt
@get:JvmName("proxySelector") val proxySelector: ProxySelector =
when {
// Defer calls to ProxySelector.getDefault() because it can throw a SecurityException.
builder.proxy != null -> NullProxySelector // proxy 不为空时,proxySelector 不起作用
else -> builder.proxySelector ?: ProxySelector.getDefault() ?: NullProxySelector
}
- 如果用户已经设置了代理,就赋值为
NullProxySelector
(无意义)。 - 如果代理没被设置,会依次尝试使用自定义的
proxySelector
和 Java 系统的默认代理选择器。如果两者都没有,会使用NullProxySelector
。
而 NullProxySelector
最终会返回 Proxy.NO_PROXY
,即直连。
所以,在大多数情况下,OkHttp 的默认代理行为就是直连。
kotlin
// NullProxySelector.kt
object NullProxySelector : ProxySelector() {
// 用于选择代理
override fun select(uri: URI?): List<Proxy> {
requireNotNull(uri) { "uri must not be null" }
return listOf(Proxy.NO_PROXY)
}
override fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: IOException?) {
}
}
java
// Proxy.java
public static final Proxy NO_PROXY = new Proxy();
private Proxy() {
type = Type.DIRECT; // 直连
sa = null;
}
proxyAuthenticator: Authenticator
和 authenticator
类似,当代理服务器的认证失效时,就使用它来处理。
如果代理服务器返回 407 Proxy Authentication Required
状态码时,它的回调会触发。
SSL/TLS 相关配置
-
socketFactory: SocketFactory
创建 TCP 连接(Socket)的工厂
-
sslSocketFactory: SSLSocketFactory
创建在 TCP 连接之上的加密连接(TSL、SSL 连接)的工厂
-
x509TrustManager: X509TrustManager
证书验证器,验证建立 HTTPS 连接时,服务器证书链的合法性。
X509
是当前证书的标准格式, -
connectionSpecs: List<ConnectionSpec>
连接规范列表。也就是 HTTPS 连接时,允许使用的 TLS 版本和加密套件(包含非对称加密、对称加密、哈希算法)。
OkHttp 给我们提供了几个配置,分别是:
kotlinclass ConnectionSpec internal constructor(...) { ... @Suppress("DEPRECATION") companion object { ... @JvmField val RESTRICTED_TLS = Builder(true) .cipherSuites(*RESTRICTED_CIPHER_SUITES) .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2) .supportsTlsExtensions(true) .build() @JvmField val MODERN_TLS = Builder(true) .cipherSuites(*APPROVED_CIPHER_SUITES) .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2) .supportsTlsExtensions(true) .build() @JvmField val COMPATIBLE_TLS = Builder(true) .cipherSuites(*APPROVED_CIPHER_SUITES) .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0) .supportsTlsExtensions(true) .build() /** Unencrypted, unauthenticated connections for `http:` URLs. */ @JvmField val CLEARTEXT = Builder(false).build() } }
RESTRICTED_TLS
是最严格的规范,它的安全性最高,但兼容性最弱。MODERN_TLS
(默认值),是目前最流行的规范。而COMPATIBLE_TLS
会比MODERN_TLS
宽松一些,支持更多 TLS 的版本,有最强的兼容性。CLEARTEXT
是明文的意思,也就是 HTTP(明文传输)。 -
protocols: List<Protocol>
表示客户端支持的 HTTP 协议列表:
kotlinenum class Protocol(private val protocol: String) { HTTP_1_0("http/1.0"), HTTP_1_1("http/1.1"), @Deprecated("OkHttp has dropped support for SPDY. Prefer {@link #HTTP_2}.") SPDY_3("spdy/3.1"), HTTP_2("h2"), H2_PRIOR_KNOWLEDGE("h2_prior_knowledge"), QUIC("quic"); }
其中
SPDY_3
是HTTP_2
的前身,现在已经被废弃了。H2_PRIOR_KNOWLEDGE
也是HTTP_2
,只不过是非加密版本。 -
hostnameVerifier: HostnameVerifier
、certificatePinner: CertificatePinner
和certificateChainCleaner: CertificateChainCleaner?
这三个是和证书验证相关的。
其中
certificateChainCleaner
是实际操作X509TrustManager
进行证书链验证的操作员。hostnameVerifier
是主机名验证器,在证书被验证合法后,会使用它来验证证书的归属主机名是否为我们当前请求的域名。certificatePinner
是证书锁定,就是在代码中硬编码服务器证书的公钥哈希值,只信任指定的证书,来加强安全性。例如下面代码中,如果服务器证书链中的证书公钥哈希值不包含
sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
,证书链就会被拒绝。kotlinval url = "https://www.baidu.com" val hostname = "www.baidu.com" val certificatePinner = CertificatePinner.Builder() .add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") .build() val client = OkHttpClient.Builder() .certificatePinner(certificatePinner) .build() val request = Request.Builder() .url(url) .build() client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { e.printStackTrace() } override fun onResponse(call: Call, response: Response) { println("Status Code: ${response.code}") } })
如何获取服务器的证书公钥哈希值呢?也是使用上述代码,可以在日志中查看:
lessW/System.err com.example.okhttptest javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure! W/System.err com.example.okhttptest Peer certificate chain: W/System.err com.example.okhttptest sha256/aqQ4L+Pac7Qy3Or7l6f9IypN8w1H64i48B4weiXJ2v4=: CN=baidu.com,O=Beijing Baidu Netcom Science Technology Co.\, Ltd,L=beijing,ST=beijing,C=CN W/System.err com.example.okhttptest sha256/hETpgVvaLC0bvcGG3t0cuqiHvr4XyP2MTwCiqhgRWwU=: CN=GlobalSign RSA OV SSL CA 2018,O=GlobalSign nv-sa,C=BE W/System.err com.example.okhttptest sha256/cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A=: CN=GlobalSign,O=GlobalSign,OU=GlobalSign Root CA - R3 W/System.err com.example.okhttptest Pinned certificates for www.baidu.com: W/System.err com.example.okhttptest sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
超时与心跳
-
callTimeoutMillis
,connectTimeout
,readTimeout
,writeTimeout
分别是整个调用、连接、读取和写入的超时设置。其中整个调用的超时是指,从
client.newCall(request)
开始,到Response
完全被读取结束的总时间。默认值是 0,表示不限制总时长。 -
pingIntervalMillis: Int
是
WebSocket
和HTTP/2
这类长连接的心跳间隔。