一、责任链模式概述
1.1 定义
责任链模式是面向对象程式设计的一种软件设计模式。在责任链模式里,把每一个对象及其对下家的引用连接起来形成一条链,请求在这个链上传递并返回结果。每个对象在收到请求时,如果可以处理则处理并返回结果,如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,直到某一个对象完成处理。
1.2 RealCall
在《Android网络基础(二、okhttp&retrofit)》中分析到,okhttp执行建立连接、发送Request、读取Response等工作都是在RealCall类拦截器责任链中的各Interceptor实现的,下面是RealCall调用拦截链去获取Response的代码:
/**
* okhttp业务层和网络层的桥接层
* 对建立连接、发送请求、读取响应、IO流进行了封装
*/
class RealCall(val client: OkHttpClient, val originalRequest: Request, val forWebSocket: Boolean) : Call {
/**
* 责任链模式,多个拦截器组成一个拦截链
*/
internal fun getResponseWithInterceptorChain(): Response {
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
}
// 数据发送&读取拦截器CallServerInterceptor
interceptors += CallServerInterceptor(forWebSocket)
// 拦截链InterceptorChain
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
// 拦截链InterceptorChain开始处理request对象
val response = chain.proceed(originalRequest)
return response
}
}
如代码中所示,okhttp拦截链有以下几种拦截器Interceptor,按如下顺序依次处理request对象:
1.3 拦截器责任链
拦截器责任链InterceptorChain调用proceed()方法来传递request对象,每个拦截器在处理request对象时如果可以处理则进行处理并返回结果,如果不能处理则继续传给下一个拦截器(例如如果缓存拦截器CacheInterceptor可以处理则返回缓存Response,否则就继续传递给ConnectInterceptor执行连接建立等工作来返回Response)。
class RealInterceptorChain(
internal val call: RealCall,
private val interceptors: List<Interceptor>,
private val index: Int,
internal val exchange: Exchange?,
internal val request: Request,
internal val connectTimeoutMillis: Int,
internal val readTimeoutMillis: Int,
internal val writeTimeoutMillis: Int
) : Interceptor.Chain {
override fun proceed(request: Request): Response {
check(index < interceptors.size)
// 把当前拦截链复制一份,并把其index+1
val next = copy(index = index + 1, request = request)
// 当前指向的拦截器
val interceptor = interceptors[index]
// 当前拦截器进行处理:在每个拦截器处理request对象时,如果自身不能处理,则调用next.proceed()由下一个拦截器处理
val response = interceptor.intercept(next)
return response
}
}
拦截器责任链的各拦截器的主要工作总结如下所示,下面从代码进行分析:
二、RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor是重试和重定向等拦截器,其内部有一个while循环用于重试或发送重定向请求等。
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
var request = chain.request
val call = realChain.call
// 重试和重定向等拦截器内部是while循环,在后续拦截器都未处理时会进行对应处理并循环
while (true) {
var response: Response
var closeActiveExchange = true
try {
try {
// 传给下一个拦截器执行后续处理;
response = realChain.proceed(request)
} catch (e: RouteException) {
// 路由异常时,进行相应处理代码并继续循环,省略部分代码...
continue
} catch (e: IOException) {
// 读写异常时,进行相应处理代码并继续循环,省略部分代码...
continue
}
val exchange = call.interceptorScopedExchange
// 构造重试或重定向等请求
val followUp = followUpRequest(response, exchange)
if (followUp == null) {
// 没有重试或重定向等请求则返回
return response
}
// 重试或重定向等请求,继续循环
request = followUp
} finally {
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
/**
* 构造后续的重试或重定向等请求
*/
private fun followUpRequest(userResponse: Response, exchange: Exchange?): Request? {
val route = exchange?.connection?.route()
val responseCode = userResponse.code
val method = userResponse.request.method
when (responseCode) {
HttpURLConnection.HTTP_PROXY_AUTH -> {
return client.proxyAuthenticator.authenticate(route, userResponse)
}
HttpURLConnection.HTTP_UNAUTHORIZED -> return client.authenticator.authenticate(route, userResponse)
StatusLine.HTTP_PERM_REDIRECT, StatusLine.HTTP_TEMP_REDIRECT, HttpURLConnection.HTTP_MULT_CHOICE,
HttpURLConnection.HTTP_MOVED_PERM, HttpURLConnection.HTTP_MOVED_TEMP, HttpURLConnection.HTTP_SEE_OTHER -> {
// 重定向
return buildRedirectRequest(userResponse, method)
}
HttpURLConnection.HTTP_CLIENT_TIMEOUT -> {
// 408错误重试;实践中比较少见
if (retryAfter(userResponse, 0) > 0) {
return null
}
return userResponse.request
}
HttpURLConnection.HTTP_UNAVAILABLE -> {
// 503错误重试
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
return userResponse.request
}
return null
}
StatusLine.HTTP_MISDIRECTED_REQUEST -> {
// 省略代码
exchange.connection.noCoalescedConnections()
return userResponse.request
}
else -> return null
}
}
}
三、BridgeInterceptor
BridgeInterceptor是桥接拦截器,是应用层request和网络层request的一个桥接器。BridgeInterceptor根据应用层的request对象复制出一个新的request对象,把业务层的request和后续处理的request隔离开,并在新的request对象中拼接Content-Type、Content-Length、Accept-Encoding等header。然后继续向下传递,在下级拦截器返回reponse对象后,BridgeInterceptor支持对response解压然后把response继续传递给上级拦截器。
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val userRequest = chain.request()
// 1.根据应用层的request对象复制出一个新的request对象,把业务层的request和后续处理的request隔离开
val requestBuilder = userRequest.newBuilder()
val body = userRequest.body
// 2.在新的request对象中拼接Content-Type、Content-Length、Accept-Encoding等header
if (body != null) {
val contentType = body.contentType()
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString())
}
val contentLength = body.contentLength()
if (contentLength != -1L) {
requestBuilder.header("Content-Length", contentLength.toString())
requestBuilder.removeHeader("Transfer-Encoding")
} else {
requestBuilder.header("Transfer-Encoding", "chunked")
requestBuilder.removeHeader("Content-Length")
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", userRequest.url.toHostHeader())
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive")
}
var transparentGzip = false
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true
requestBuilder.header("Accept-Encoding", "gzip")
}
val cookies = cookieJar.loadForRequest(userRequest.url)
if (cookies.isNotEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies))
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", userAgent)
}
// 3.由下一个拦截器继续处理,并返回response对象
val networkResponse = chain.proceed(requestBuilder.build())
cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
// 4.根据网络层的response对象复制出一个新的response对象
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
// 5.对返回的response支持gzip解压
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
在创建OkHttpClient的时候可以传一个缓存目录,缓存拦截器CacheInterceptor就是用来判断request是否命中了缓存。
val cacheFile = File("")
val okHttpClient = OkHttpClient.Builder()
.connectionPool(ConnectionPool(2, 35, TimeUnit.SECONDS))
.cache(Cache(cacheFile, 1024))
.build()
如果创建OkHttpClient时设置了缓存目录,则缓存拦截器CacheInterceptor会根据一定的缓存策略判断是否需要发送请求及是否命中缓存。
class CacheInterceptor(val cache: Cache?) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val call = chain.call()
// 1.根据request的url从cache中获取缓存
val cacheCandidate = cache?.get(chain.request())
val now = System.currentTimeMillis()
// 2.根据缓存策略判断缓存是否有效
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
val networkRequest = strategy.networkRequest
val cacheResponse = strategy.cacheResponse
cache?.trackResponse(strategy)
val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE
// 3.缓存策略判定不需要发送请求则命中缓存
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(CacheInterceptor.stripBody(cacheResponse))
.build().also {
listener.cacheHit(call, it)
}
}
// 4.继续传递request对象
var networkResponse = chain.proceed(networkRequest)
// 5.返回response
val response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
if (cache != null) {
// 6.省略部分代码,根据缓存策略添加到缓存
cache.put(response)
}
return response
}
}
五、ConnectInterceptor
连接拦截器ConnectInterceptor用于建立连接,内部会判断是否可以连接复用,否则执行TCP过程去建立一个网络连接(后面章节再分析详细过程)。
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
// 初始化Exchange,并判断是否可以连接复用,建立连接等操作
val exchange = realChain.call.initExchange(chain)
val connectedChain = realChain.copy(exchange = exchange)
// 建立连接后,继续向下传递
return connectedChain.proceed(realChain.request)
}
}
六、CallServerInterceptor
在建立连接后,CallServerInterceptor用来使用Okio的Sink和Source来读写数据,实现把request数据发送给后端,和从后端读取response。
class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
val exchange = realChain.exchange!!
val request = realChain.request
val requestBody = request.body
// 1.通过Okio的Sink把RequestBody发送给后端
val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
requestBody.writeTo(bufferedRequestBody)
bufferedRequestBody.close()
// 省略部分代码
var response = responseBuilder
.request(request)
.handshake(exchange.connection.handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
// 2.通过Okio的Source从后端读response
response.newBuilder()
.body(exchange.openResponseBody(response))
.build()
return response
}
}
七、总结
拦截器 | 前处理 | 后处理 |
---|---|---|
自定义拦截器 | 可自定义,如拼接公参等 | - |
重试和重定向等拦截器 | - | 重试和重定向等 |
桥接拦截器 | 添加了Content-Type、Host、Cookie等header | gzip解压 |
缓存拦截器 | 如果缓存可用,则直接返回缓存 | 如果服务器返回304则返回缓存。cache不为空时对response进行缓存 |
连接拦截器 | 建立连接 | |
自定义网络拦截器 | - | - |
数据发送&读取拦截器 | okio写数据 | okio读数据 |
The End
欢迎关注我,一起解锁更多技能:BC的掘金主页~💐 BC的CSDN主页~💐💐
okhttp:https://square.github.io/okhttp/
Android网络开发专栏:https://juejin.cn/column/7198740450198749240