OkHttp深度解析(二) : OkHttpClient 你没见过的那些属性

88 阅读6分钟

这是 OkHttp 深度解析系列文章

OkHttp深度解析(一) : 从一次完整请求看 OkHttp整体架构

OkHttp深度解析(二) : OkHttpClient 你没见过的那些属性

OkHttp深度解析(三) : 拦截器?连接复用&合并?Http2?

OkHttpClient 属性详解

OkHttpClient 中管理的是Okhttp 网络请求中各种参数配置,因此了解它里面的每个参数含义及作用非常重要

class Builder constructor() {
	// 基于线程池的管理和分配请求任务的调度器
	internal var dispatcher: Dispatcher = Dispatcher()
	//连接池,负责批量管理连接对象,功能:查找、创建、复用、存储连接,实际工作对象是 RealConnectionPool
	//核心是通过 ConcurrentLinkedQueue 实现的
	internal var connectionPool: ConnectionPool = ConnectionPool()
	//自定义拦截器,用于添加请求头,修改 URL 打印日志等
	internal val interceptors: MutableList<Interceptor> = mutableListOf()
	//网络拦截器,主要用于调试服务返回的原始数据
	internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
	//网络请求过程中各种事件的监听器
	internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
	//连接失败重试,重试指的是在有其它可行的路线下才会重试,比如多IP主机一个IP失败会重试其它的IP,
	internal var retryOnConnectionFailure = true
	//认证器,当服务器返回认证失败 401 or 407 时,会回调这个认证器,如果需要刷新Token则需要重写并在回调中处理
	internal var authenticator: Authenticator = Authenticator.NONE
	//是否重定向
	internal var followRedirects = true
	//是否允许协议切换的重定向(Http <=> Https)
	internal var followSslRedirects = true
	//cookieJar表示cookie存储器,用于在客户端保存一些请求状态信息,默认实现是空,移动端一般不使用
	internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
	//服务端数据的缓存
	internal var cache: Cache? = null
	//域名解析
	internal var dns: Dns = Dns.SYSTEM
	//正向代理配置:DIRECT,HTTP,SOCKS
	internal var proxy: Proxy? = null
	//代理选择器,它的默认配置是:当 proxy 为空时,ProxySelector会返回NullProxySelector,也就是直连
	//当 proxy不为空时,NullProxySelector也会返回NullProxySelector,因为起作用的是proxy,也就是ProxySelector属性失效
	internal var proxySelector: ProxySelector? = null
	//与authenticator相同,当配置的代理服务器要求认证时也会回调authenticate函数
	internal var proxyAuthenticator: Authenticator = Authenticator.NONE
	//用于创建 Socket 连接的工厂类
	internal var socketFactory: SocketFactory = SocketFactory.getDefault()
	//用于创建 TLS连接的工厂类
	internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
	//证书管理器,验证证书有效性,x509 表示证书格式
	internal var x509TrustManagerOrNull: X509TrustManager? = null
	//连接规范,用于记录支持的TLS协议版本和加密套件(cipherSuites)
	//默认连接规范:Https使用MODERN_TLS(最合适的TLS配置),Http 使用明文传输
	internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS
	//支持的协议 Http 1.0/1.1/2;SPDY(废弃,Http2 的前身);H2_PRIOR_KNOWLEDGE(HTTP2的明文版);QUIC(HTTP3 没有实现只提供拦截器入口)
	internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS
	//主机名验证器,用于验证证书主机名与请求中的主机名是否一致,无特殊要求不可自定义这个属性
	internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier
	//证书固定器,通过硬编码方式将证书固定,也就是除了验证证书合法性之外,还需要确定是指定的那个证书
	internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
	//证书链清理员,它的作用是:1.操作X509TrustManager 返回证书链列表 2. 清理与TLS握手无关的证书
	internal var certificateChainCleaner: CertificateChainCleaner? = null
	//整个调用周期的超时时间,默认不限制,但内部的连接,读写有超时限制
	internal var callTimeout = 0
	internal var connectTimeout = 10_000//连接超时
	internal var readTimeout = 10_000//读超时
	internal var writeTimeout = 10_000//写超时
	internal var pingInterval = 0//http2 和 websocket 的心跳间隔
	//websocket 中被压缩的最小的消息大小,默认 1024,即当消息>=1024时会被压缩
	internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
	//路由数据库,用于记录有故障的路由,便于寻找备用路由
	internal var routeDatabase: RouteDatabase? = null
	
}

重点说明的几个属性:

  • connectionPool

    连接池的原理:

    RealConnectionPool是实际的连接池管理类,它内部使用 ConcurrentLinkedQueue<RealConnection> 的来存储连接对象,这表明对于连接池的单个操作是线程安全的,连接池负责 OkHttpClient 实例整个生命周期内TCP 连接的查找、复用、创建、存储、清理等工作

    PS : ConcurrentLinkedQueue 只能保证多线程操作中对它的单个操作是安全的,但如果是复合操作仍然需要加锁,这是多线程开发中要注意的点,OkHttp 源码注释中也强调了这一点:

    /**
    * Holding the lock of the connection being added or removed when mutating this, and check its
    * [RealConnection.noNewExchanges] property. This defends against races where a connection is
    * simultaneously adopted and removed.
    */
    private val connections = ConcurrentLinkedQueue<RealConnection>()
    

    其中 OkHttp 的连接复用机制是体现 OkHttp 性能的重点技术,它的复用逻辑主要有两种:

    1. 对于 Http1.1 ,由于 Http 1.1 开始支持 Keep-Alive ,因此一个当一个 Http 请求结束时,TCP 连接并不会立即释放,如果有新的请求发起,会经过一系列条件判断是否可以复用已有的连接,如果符合复用条件,这样就减少了一次 TCP 的握手动作,同时也减少了连接对象的创建,减少了内存开销。
    2. 对于 Http2,由于 Http2 支持Multiplexing(多路复用),也就是Http2支持在同一个 TCP 连接上同时可以发起多个 Http 请求,这样新请求也可以复用符合条件的TCP 连接,并且不需要等待连接空闲(不超负载)
  • followRedirectsfollowSslRedirects 的关系

    followRedirects 表示是否允许重定向,默认值是 true, followSslRedirects 表示在followRedirects=true 的前提下,是否允许进行协议切换(Http ↔ Https)的重定向,默认值也是 true,但是在一些高安全场景要求下,需要将 followSslRedirects 设置为 false,以防止协议攻击

  • certificatePinner

    证书固定器,通过硬编码方式将证书的哈希信息固定,也就是除了验证证书合法性之外,还需要确定是指定的那个证书,否则也不能通过验证

    使用方式:

    //1. 先使用错误的哈希值测试一下,把服务器证书正确的哈希值打印出来
    String hostname = "publicobject. com"; 
    CertificatePinner certificatePinner = new CertificatePinner.Builder()     
    	.add(hostname, "sha256/ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")     
    	.build(); 
    OkHttpClient client = OkHttpClient. Builder()     
    	.certificatePinner(certificatePinner)     
    	.build();
    Request request = new Request. Builder()     
    	.url("https://" + hostname)     
    	.build(); 
    client.newCall(request).execute();
    //2.打印出正确哈希值
    //3.再把正确的哈希值替换过去
    

    ⚠️注意:使用它比较危险,如果服务器证书更换了,可能导致客户端无法使用,因为由于是硬编码了证书哈希, 所以无法访问服务器了

    PS: CertificatePinner 也可以用于防止那些基于 VPN 的抓包软件(如 Drony),因为抓包软件抓取 Https 请求的原理是让用户主动信任证书,如果固定了服务器的证书签名,抓包软件也就无法通过它们直接抓包了

  • pingInterval

    Q:为什么H2 & ws 协议才需要心跳间隔,Http1.1 也是长连接,为什么不需要?

    A:因为 Http1.1 中的请求是单个执行的,同一个 TCP 连接上两个请求之间的间隔时间超过限制,连接允许被释放,但在H2 中由于多个请求是并行的,TCP 长连接维持时间需要更久