OkHttp源码剖析(四) 报文读写工具ExchangeCodec
文章目录
OkHttp的源码中存在着很多常见的设计模式,比如工厂模式(Call.Factory、WebSocket.Factory、CacheInterceptor中的CacheStrategy.Factory等)、外观者模式(OkHttpClient)等,但OkHttp最明显最重要的是三种模式:建造者模式、责任链模式和享元模式。
建造者模式
定义:将复杂对象的构建与表示分离。建造者模式将一个复杂对象的创建过程封装起来,允许对象通过多个步骤来创建,在对象特别复杂,内部参数很多时,建造者模式就能发挥出它的优势。
若源码中有Builder这个词,大概率使用了建造者模式。
OkHttpClient
OkHttpClient我们常用的就只有默认模式,但其内部包含很多复杂对象:超时时间(Timeout),代理(proxy),缓存(cache),分发器(dispatcher),拦截器(interceptors)等等。
public class OkHttpClient implements Cloneable, Call.Factory, WebSocketCall.Factory {
// 构造函数1
public OkHttpClient() {
this(new Builder());
}
// 构造函数2
private OkHttpClient(Builder builder) {
......
}
public Builder newBuilder() {
return new Builder(this);
}
// Builder类
public static final class Builder {
public Builder() { }
Builder(OkHttpClient okHttpClient) { }
//Builder类的OkHttpClient
public OkHttpClient build() {
return new OkHttpClient(this);
}
}
}
newBuilder函数作用:
利用建造者模式实例化OkHttpClient对象:
// 实例化一个默认的HTTP客户端
OkHttpClient client = new OkHttpClient();
// 使用自定义设置创建HTTP客户端实例
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor()) //增加拦截器
.cache(new Cache(cacheDir, cacheSize)) //设置用于读取和写入缓存响应的响应缓存。
.build();
// 实例化一个500毫秒则超时的HTTP客户端实例
OkHttpClient eagerClient = client.newBuilder()
.readTimeout(500, TimeUnit.MILLISECONDS)
.build();
Request
Request类通过建造者模式通过Request.Builder生成实例,其中url代表这个Http请求的url,method指的是GET/POST等请求,header和body自然就是http请求的首部和请求体;cacheControl是缓存控制。
kotlin代码:
class Request internal constructor(
@get:JvmName("url") val url: HttpUrl,
@get:JvmName("method") val method: String,
@get:JvmName("headers") val headers: Headers,
@get:JvmName("body") val body: RequestBody?,
internal val tags: Map<Class<*>, Any>
) {
private var lazyCacheControl: CacheControl? = null
val isHttps: Boolean
fun header(name: String): String? = headers[name]
fun headers(name: String): List<String> = headers.values(name)
fun newBuilder(): Builder = Builder(this)
@get:JvmName("cacheControl") val cacheControl: CacheControl
open class Builder {
...
open fun url(url: String): Builder {}
open fun url(url: URL) = url(url.toString().toHttpUrl())
open fun header(name: String, value: String)
open fun addHeader(name: String, value: String)
open fun removeHeader(name: String)
open fun headers(headers: Headers)
open fun cacheControl(cacheControl: CacheControl): Builder {}
open fun get() = method("GET", null)
open fun head() = method("HEAD", null)
open fun post(body: RequestBody) = method("POST", body)
@JvmOverloads
open fun delete(body: RequestBody? = EMPTY_REQUEST) = method("DELETE", body)
open fun put(body: RequestBody) = method("PUT", body)
open fun patch(body: RequestBody) = method("PATCH", body)
open fun method(method: String, body: RequestBody?): Builder = apply {}
...
open fun build(): Request {}
}
}
java代码:
public final class Request {
private Request(Builder builder) {
......
}
public Builder newBuilder() {
return new Builder(this);
}
public static class Builder {
private HttpUrl url;
public Builder() {
......
}
private Builder(Request request) {
this.url = request.url;
......
}
public Builder url(HttpUrl url) {}
public Builder header(String name, String value) {}
......
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
}
}
Response类
…类似Request
责任链模式
若源代码中有Chain这个词,大概率使用了责任链模式。
使用举例
一个记录传出请求和传入响应简单的拦截器:
class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
//前置工作
long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
//中置工作
Response response = chain.proceed(request);
//后置工作
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
源码解析
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)
val chain = RealInterceptorChain(
...
)
var calledNoMoreExchanges = false
try {
val response = chain.proceed(originalRequest)
...
return response
} catch (e: IOException) {
...
}
}
各层Interceptor
通过上面的责任链时序图可以看到,所有的拦截器直接最主要的操作为intercceptor(),该步骤使得责任链可以不断往下传递执行,我们将在各拦截器在intercceptor()方法之前的操作称为前置操作,在intercceptor()方法之后的操作称为前后置操作,以此来进行具体分析。
开发者自定的Interceptor
开发者使用 addInterceptor(Interceptor) 所设置的拦截器会在所有其他 Interceptor 处理之前运行,它也会收到 Response 之后,做最后的善后工作。如果有统一的 header 要添加,可以在这里设置;
RetryAndFollowUpInterceptor
负责出错重试、重定向。这层拦截器使得出错重试和重定向对开发者无感。
while (true) {
call.enterNetworkInterceptorExchange(request, newExchangeFinder)
...
try {
response = realChain.proceed(request)
newExchangeFinder = true
} catch (e: RouteException) {
...
}
...
}
前置工作:
通过call.enterNetworkInterceptorExchange()方法,创建 ExchangeFinder,供在后续拦截器使用。
中置工作:
response = realChain.proceed()
后置工作:
错误重试、重定向 -> 返回response
BridgeInterceptor
桥接(从应用程序代码到网络代码的桥梁)
前置工作:
填充Http请求协议中的head头信息
中置工作:
response = realChain.proceed()
后置工作:
通过GzipSource 解开 body
if (transparentGzip &&
"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
networkResponse.promisesBody()) {
val responseBody = networkResponse.body
if (responseBody != null) {
val gzipSource = GzipSource(responseBody.source())
val strippedHeaders = networkResponse.headers.newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build()
responseBuilder.headers(strippedHeaders)
val contentType = networkResponse.header("Content-Type")
responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
}
}
return responseBuilder.build()
}
CacheInterceptor
前置工作:
判断是否有缓存,有缓存就用并提前返回;除此之外如果被禁止使用网络或者缓存不足等条件,也会提前返回。
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
return Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(HTTP_GATEWAY_TIMEOUT)
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build().also {
listener.satisfactionFailure(call, it)
}
}
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build().also {
listener.cacheHit(call, it)
}
}
中置工作:
response = realChain.proceed()
后置工作:
判断响应结果是否可以缓存,可以缓存就缓存/更新缓存下来
if (cache != null) {
if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
val cacheRequest = cache.put(response)
return cacheWritingResponse(cacheRequest, response).also {
if (cacheResponse != null) {
// This will log a conditional cache miss only.
listener.cacheMiss(call)
}
}
}
if (HttpMethod.invalidatesCache(networkRequest.method)) {
try {
cache.remove(networkRequest)
} catch (_: IOException) {
// The cache cannot be written.
}
}
}
该Interceptor后置中,还主要实现http协议中的If-Modified-Since(304)类型请求,该字段的功能:
判断客户端缓存的数据是否已经过期,如果过期则获取新的数据
它通常应用于服务器需要返回大文件的情况,可以加快http传输速度。
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
if (networkResponse?.code == HTTP_NOT_MODIFIED) {
val response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers, networkResponse.headers))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis)
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
networkResponse.body!!.close()
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache!!.trackConditionalCacheHit()
cache.update(cacheResponse, response)
return response.also {
listener.cacheHit(call, it)
}
} else {
cacheResponse.body?.closeQuietly()
}
}
ConnectInterceptor
这个拦截器是okhttp核心中的核心。
因为该拦截器只要负责连接,因而前置操作为找到一个连接,中置操作为connectedChain.proceed(),后置直接返回结果response,没有额外的后置操作。
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
val exchange = realChain.call.initExchange(chain)
val connectedChain = realChain.copy(exchange = exchange)
return connectedChain.proceed(realChain.request)
}
}
关键是initExchange()这一行,想尽办法拿一个连接且连接健康,根据连接建立codec对象,然后把codec对象塞进Exchange里面。
internal fun initExchange(chain: RealInterceptorChain): Exchange {
...
val exchangeFinder = this.exchangeFinder!!
val codec = exchangeFinder.find(client, chain)
val result = Exchange(this, eventListener, exchangeFinder, codec)
this.interceptorScopedExchange = result
this.exchange = result
...
return result
}
ExchangeFinder类中的find方法可以看到,在拿到一个可用且健康的连接后,通过resultConnection.newCodec(client, chain)方法,从一个可用且健康的连接创立了Codec对象。
class ExchangeFinder(
...
) {
...
fun find(
client: OkHttpClient,
chain: RealInterceptorChain
): ExchangeCodec {
try {
val resultConnection = findHealthyConnection(
...
)
return resultConnection.newCodec(client, chain)
} catch (e: RouteException) {
...
}
}
}
在findHealthyConnection()方法中会调用ExchangeFinder.findConnection()方法,该方法在享元模式这章中再深入探索下。
NetworkInterceptor
网络拦截器,一般使用不到,除非我们想拿到最原始的网络请求和返回,因为该拦截器的下一层就是CallServerInterceptor了,下一层直接进行网络的收发,不再有任何前置和后置操作了。
具体应用有FaceBook的stetho。
CallServerInterceptor
链中的最后一个拦截器,由于它只负责和网络的收发,所以它没有前置操作和后置操作,只执行intercept中置方法。
它通过操作exchage,发请求,读响应(和io操作),对服务器进行网络调用发送内容,将字段输出到IO流当中。具体体现:
Http1.1:输入到字符串流
Http2:输入到二进制帧中,HEADER帧和DATA帧
exchange.writeRequestHeaders(request)
var invokeStartEvent = true
var responseBuilder: Response.Builder? = null
if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return
// what we did get (such as a 4xx response) without ever transmitting the request body.
if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
exchange.flushRequest()
responseBuilder = exchange.readResponseHeaders(expectContinue = true)
exchange.responseHeadersStart()
invokeStartEvent = false
}
if (responseBuilder == null) {
...
} else {
exchange.noRequestBody()
}
小结
平时使用框架时,只有应用拦截器和网络拦截器可以自定义。
各种拦截器都必须遵循Interceptor接口,拦截器责任链的实现类部分源码,随着index的递增,返回链条中的下一个拦截器。
RealInterceptorChain:
@Throws(IOException::class)
override fun proceed(request: Request): Response {
...
// Call the next interceptor in the chain.
val next = copy(index = index + 1, request = request)
val interceptor = interceptors[index]
@Suppress("USELESS_ELVIS")
val response = interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null")
...
return response
}
}
Android中常见的其他责任链模式:ViewGroup将事件分发到子View。
享元模式
享元设计模式核心:池中复用。
已有的对象们搁于享元池中,用到时存在即复用,否则再新建。
一般带池的概念大概率使用了享元模式:线程池,连接池,数据池,缓存池,消息池等。
ExchangeFinder.findConnection()
OkHttp3将客户端与服务器之间的连接定义为接口Connection,通过RealConnection实现,在ConnectionPool中,将连接Connection储存在一个双端队列中。
private val connections = ConcurrentLinkedQueue<RealConnection>()
在ConnectInterceptor中,需要调用realChain.call.initExchange(chain)找到exchange,而在initExchange中,调用了ExchangeFinder中的findConnection()方法去寻找一个可用的connection连接。连接存储在RealConnectionPool中的connections队列当中,OkHttp在寻找可用的Connection的过程当中,体现了享元模式的核心思想。
Route
route代表具体的路由,里面主要包含了连接的ip地址,端口,代理模式,还有直接取的HttpClient里的一些连接信息等。
class Route(
@get:JvmName("address") val address: Address,
@get:JvmName("proxy") val proxy: Proxy,
@get:JvmName("socketAddress") val socketAddress: InetSocketAddress
) {
...
}
在OkHtttp中,Route组成RouteSelection,RouteSelection实际上是Selection类,它是同样端口,同样代理类型下的不同ip的route集合。RouteSelection组成RouteSelector。这里不展开细说,只需要先关注到,在寻找连接时,需要拿到Route,然后根据Route中的ip、端口、代理等,判断连接是否可以被合并(Http2)。
连接寻找过程
ExchangeFinder.findConnection()返回一个连接来承载一个新的stream,它会优先选择已存在的stream,如果不存在,
池会最终创建一个新的连接。该方法这会在每次阻塞操作之前检查取消。
ExchangeFinder.findConnection()一共进行了五次寻找获取连接:
-
第一次:有连接,且可用:
call.connection 不为空且符合复用条件,直接使用并返回
-
第二次:没连接,去池里找,非多路复用连接:
调用 callAcquirePooledConnection 找非多路复用的链接
-
第三次:没连接,去池里找,多路复用/非多路复用连接:
调用 callAcquirePooledConnection 非多路/多路都拿
-
第四次:都拿不到,我自己去创建连接
-
第五次:在第四次基础上,尝试取只多路复用的链接,能拿到就用这个
再次调用 callAcquirePooledConnection 拿多路复用的链接,如果拿到将第四次创建的丢弃,将result 返回
@Throws(IOException::class)
private fun findConnection(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean
): RealConnection {
// 检查该call是否被取消,若取消,即不再进行后续操作
if (call.isCanceled()) throw IOException("Canceled")
// 尝试复用call中的已有的连接
val callConnection = call.connection
if (callConnection != null) {
var toClose: Socket? = null
synchronized(callConnection) {
if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
//该步骤首先检查call中已有的连接是否符合复用条件,不符合就要释放连接:releaseConnection
toClose = call.releaseConnectionNoEvents()
}
}
// call的连接不null没被释放说明通过了上面的可复用条件检查
// 既然连接存在,还是好的链接,直接返回连接
if (call.connection != null) {
check(toClose == null)
return callConnection
}
// call的连接释放掉了,关闭连接
toClose?.closeQuietly()
eventListener.connectionReleased(call, callConnection)
}
// 上述步骤没拿到连接,说明我们需要一个新连接,这边状态重赋值
refusedStreamCount = 0
connectionShutdownCount = 0
otherFailureCount = 0
// 第一次尝试从池中获取一个连接,
// 第一次取时,不寻找可多路复用的连接
// 取到连接即成功,直接返回即可
// 注意该步没有传route,requireMultiplexed 为 false,
// 表示只拿非多路复用的连接(Http1.1的连接可拿,Http2原本能用的连接,拿不到)
if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
val result = call.connection!!
eventListener.connectionAcquired(call, result)
return result
}
// 池中什么也没有,计算我们下一个需要的route
val routes: List<Route>?
val route: Route
if (nextRouteToTry != null) {
// Use a route from a preceding coalesced connection.
routes = null
route = nextRouteToTry!!
nextRouteToTry = null
} else if (routeSelection != null && routeSelection!!.hasNext()) {
// Use a route from an existing route selection.
routes = null
route = routeSelection!!.next()
} else {
// Compute a new route selection. This is a blocking operation!
var localRouteSelector = routeSelector
if (localRouteSelector == null) {
localRouteSelector = RouteSelector(address, call.client.routeDatabase, call, eventListener)
this.routeSelector = localRouteSelector
}
val localRouteSelection = localRouteSelector.next()
routeSelection = localRouteSelection
routes = localRouteSelection.routes
if (call.isCanceled()) throw IOException("Canceled")
// 在有了路由route后,可以根据route找能进行连接合并(Http2)的连接
// 第二次尝试调用 connectionPool.callAcquirePooledConnection
// 传了route,且requireMultiplexed 为 false,
// 表示拿多路复用或非多路复用我都拿
if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
val result = call.connection!!
eventListener.connectionAcquired(call, result)
return result
}
route = localRouteSelection.next()
}
// 第一次第二次都拿不到 我需要自己创立连接
val newConnection = RealConnection(connectionPool, route)
call.connectionToCancel = newConnection
try {
//创建完连接后要进行网络连接,所以是阻塞式的
newConnection.connect(
connectTimeout,
readTimeout,
writeTimeout,
pingIntervalMillis,
connectionRetryEnabled,
call,
eventListener
)
} finally {
call.connectionToCancel = null
}
call.client.routeDatabase.connected(newConnection.route())
// 防止创建了两个可被合并的连接,尝试取第一个先创建好的,后创建的回收掉。
// 传了route,且requireMultiplexed 为 true,
// 表示只能是Http2的,只拿能多路复用的连接
if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
val result = call.connection!!
nextRouteToTry = route
newConnection.socket().closeQuietly()
eventListener.connectionAcquired(call, result)
return result
}
synchronized(newConnection) {
// 自己创建完的连接,要塞进连接池,供共享使用
connectionPool.put(newConnection)
call.acquireConnectionNoEvents(newConnection)
}
eventListener.connectionAcquired(call, newConnection)
return newConnection
}
注意
1.Java里面static一般用来修饰成员变量或函数。但有一种特殊用法是用static修饰内部类,普通类是不允许声明为静态的,只有内部类才可以。被static修饰的内部类可以直接作为一个普通类来使用,而不需实例一个外部类。
2.Kotlin默认内部类是静态内部类,和Java相反。
参考
https://square.github.io/okhttp/
https://zhuanlan.zhihu.com/p/58093669
https://www.jianshu.com/p/8d69fd920166
https://juejin.cn/post/6844904037154816007