OkHttp 源码阅读笔记(四)
第一篇文章中介绍了 OkHttp
的同步调用和异步调用,Dispatcher
的任务调度器工作方式和 RealInterceptorChain
拦截器链的工作方式:OkHttp 源码阅读笔记(一)。
第二篇文章中介绍了 OkHttp
如何从缓存中获取链接,如何创建链接以及 ConnectionPool
的工作原理:OkHttp 源码阅读笔记(二)。
第三篇文章中介绍了 OkHttp
中的系统拦截器 RetryAndFollowUpInterceptor
和 BridgeInterceptor
:OkHttp 源码阅读笔记(三)。
本篇文章是系列文章的第四篇,主要介绍剩下没有介绍的系统拦截器。
CacheInterceptor
CacheInterceptor
是用来处理 Http
协议的缓存的,默认情况下 OkHttp
是没有支持缓存的,可以在构建 OkhttpClient
的时候设置一个 Cache
对象就可以支持缓存了,Cache
对象中可以指定缓存文件的目录和缓存文件的最大值。CacheIntercptor
只会缓存 GET
请求同时满足缓存条件控制的 Response
。
OK,现在来看看 CacheInterceptor
是如何处理缓存的,同样的我们以它的 intercept()
方法作为入口函数:
Kotlin
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val call = chain.call()
// 获取缓存的 Response
val cacheCandidate = cache?.get(chain.request())
val now = System.currentTimeMillis()
// 通过 Request 和缓存的 Response 计算出新的请求的 Request 和 缓存的 Response
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
// 新的请求 Request,如果它为空就表示不需要请求网络
val networkRequest = strategy.networkRequest
// 处理后的缓存的 Response,如果它为空就表示不能使用缓存
val cacheResponse = strategy.cacheResponse
cache?.trackResponse(strategy)
val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE
// 表示无法使用缓存中的 Response,把其中的 ResponseBody 关闭。
if (cacheCandidate != null && cacheResponse == null) {
// The cache candidate wasn't applicable. Close it.
cacheCandidate.body?.closeQuietly()
}
// If we're forbidden from using the network and the cache is insufficient, fail.
// 这种情况表示 Request 中希望只要缓存的数据,但是缓存的数据并没有找到,这里就会构建一个描述这个异常的 Response 直接返回。
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 {
// 通知 eventListener 获取缓存失败
listener.satisfactionFailure(call, it)
}
}
// If we don't need the network, we're done.
// 这种情况下表示缓存可用而且不需要重新请求网络验证,直接把缓存的 Response 返回
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build().also {
// 通知 eventListener 命中缓存
listener.cacheHit(call, it)
}
}
if (cacheResponse != null) {
// 通知 eventListener 命中缓存,但是需要再次请求网络验证缓存是否可用
listener.cacheConditionalHit(call, cacheResponse)
} else if (cache != null) {
// 通知 eventListener 未命中缓存
listener.cacheMiss(call)
}
var networkResponse: Response? = null
try {
// 执行网络请求
networkResponse = chain.proceed(networkRequest)
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
cacheCandidate.body?.closeQuietly()
}
}
// If we have a cache response too, then we're doing a conditional get.
// 这里表示有等待验证的缓存 Response
if (cacheResponse != null) {
// ResponseCode 返回 304 就表示原来的缓存是可以继续使用的,反之不可以使用
if (networkResponse?.code == HTTP_NOT_MODIFIED) {
// 通过缓存 Response 构建一个新的 Response
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 {
// 通知 eventListener 命中缓存
listener.cacheHit(call, it)
}
} else {
// 缓存的 Response 不可用,关闭 ResponseBody。
cacheResponse.body?.closeQuietly()
}
}
// 后续的逻辑就是没有缓存和缓存不可用的处理逻辑了。
val response = networkResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
if (cache != null) {
// 判断当前 Response 是否可以缓存。
if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
// 写入到 Cache 中
val cacheRequest = cache.put(response)
return cacheWritingResponse(cacheRequest, response).also {
if (cacheResponse != null) {
// This will log a conditional cache miss only.
// 通知 eventListener 未命中缓存
listener.cacheMiss(call)
}
}
}
// 验证是否可以缓存
if (HttpMethod.invalidatesCache(networkRequest.method)) {
try {
// 移除缓存
cache.remove(networkRequest)
} catch (_: IOException) {
// The cache cannot be written.
}
}
}
return response
}
上面的代码主要逻辑可以分为以下几步:
-
从
Cache
中获取缓存的Response
。 -
通过
Request
和缓存的Response
计算CacheStrategy
,CacheStrategy
非常重要,它来控制后续是否需要网络请求,缓存是否能用等等,我们后面要重点分析它的关键代码。 -
如果请求只允许缓存的数据,同时又需要网络验证,就构建一个描述该错误的
Response
返回。 -
如果不需要请求网络,同时缓存可用,直接返回缓存的
Response
。 -
请求网络。
-
如果有等待验证的缓存,同时服务端返回
304
表示缓存可用,就把缓存的Response
返回,同时更新Cache
。 -
后续就是普通没有缓存或者缓存过期的处理逻辑了,判断
Response
是否可以缓存,如果可以缓存到Cache
中,然后把网络的Response
返回。
基于上面的代码我们再分析分析 CacheStrategy
关键逻辑和 Cache
的关键逻辑。
CacheStrategy
CacheStrategy
是通过 CacheStrategy.Factory#compute()
方法计算的,我们也以它为入口函数分析:
Kotlin
fun compute(): CacheStrategy {
val candidate = computeCandidate()
// We're forbidden from using the network and the cache is insufficient.
// 没有找到缓存,同时请求也要缓存,就把 networkRequest 和 cacheResponse 都设置为空
if (candidate.networkRequest != null && request.cacheControl.onlyIfCached) {
return CacheStrategy(null, null)
}
return candidate
}
通过 computeCandidate()
方法计算 CacheStrategy
,如果获取到的缓存需要网络验证同时又只使用缓存,那么就会把 networkRequest
和 cacheResponse
都设置为空(也就是我们分析 intercept()
方法中的第二点)。
我们继续看 computeCandidate()
方法(以下的方法有许多的 Http
协议缓存控制相关的内容,不熟悉的同学建议网络上找找相关资料):
Kotlin
private fun computeCandidate(): CacheStrategy {
// No cached response.
// 没有缓存,直接请求网络
if (cacheResponse == null) {
return CacheStrategy(request, null)
}
// Drop the cached response if it's missing a required handshake.
// 有缓存,但是 https 协议的 handshake 缺失,直接请求网络
if (request.isHttps && cacheResponse.handshake == null) {
return CacheStrategy(request, null)
}
// If this response shouldn't have been stored, it should never be used as a response source.
// This check should be redundant as long as the persistence store is well-behaved and the
// rules are constant.
// 通过 Response 和 Reqeust 判断,不能够使用缓存,直接请求网络。
if (!isCacheable(cacheResponse, request)) {
return CacheStrategy(request, null)
}
val requestCaching = request.cacheControl
// Request 中的缓存控制如果有 no-cache 或者请求头有缓存控制的相关的条件,直接请求网络。
if (requestCaching.noCache || hasConditions(request)) {
return CacheStrategy(request, null)
}
val responseCaching = cacheResponse.cacheControl
// 缓存年纪
val ageMillis = cacheResponseAge()
// 缓存刷新时间
var freshMillis = computeFreshnessLifetime()
if (requestCaching.maxAgeSeconds != -1) {
freshMillis = minOf(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds.toLong()))
}
// 最小刷新间隔
var minFreshMillis: Long = 0
if (requestCaching.minFreshSeconds != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds.toLong())
}
// 如果不是一定需要服务器验证,缓存最大的可用时间
var maxStaleMillis: Long = 0
if (!responseCaching.mustRevalidate && requestCaching.maxStaleSeconds != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds.toLong())
}
// 如果 Response 中缓存控制没有 no-cache,表示服务器可以接受不验证缓存。同时缓存的年纪没有达到最大的限制。这样就直接使用缓存且不验证服务器。
if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
val builder = cacheResponse.newBuilder()
// 如果时间超过 freshMillis,但是没有超过 maxStaleMillis 限制,添加警告 header
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"")
}
val oneDayMillis = 24 * 60 * 60 * 1000L
// 如果缓存的时间超过一天,添加警告 header
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"")
}
// 不向服务器验证缓存
return CacheStrategy(null, builder.build())
}
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
val conditionName: String
val conditionValue: String?
// 计算缓存控制 header,也就是 If-None-Match 和 If-Modified-Since
when {
etag != null -> {
conditionName = "If-None-Match"
conditionValue = etag
}
lastModified != null -> {
conditionName = "If-Modified-Since"
conditionValue = lastModifiedString
}
servedDate != null -> {
conditionName = "If-Modified-Since"
conditionValue = servedDateString
}
// 没有缓存控制,直接请求网络
else -> return CacheStrategy(request, null) // No condition! Make a regular request.
}
val conditionalRequestHeaders = request.headers.newBuilder()
conditionalRequestHeaders.addLenient(conditionName, conditionValue!!)
// 添加缓存控制相关的 Request Header
val conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build()
// 缓存需要向服务端确认是否可用
return CacheStrategy(conditionalRequest, cacheResponse)
}
这里先简单描述下 CacheStrategy
中 networkRequest
和 cacheResponse
的空状态分别表示什么意思:
-
networkRequest
和cacheResponse
都为空。表示没有获取到缓存,同时也无法请求网络,也就是前面分析到的
Request Header
中有only-if-cache
的,表示只要缓存的数据,但是缓存数据不可用。 -
networkRequest
不为空,cacheResponse
为空。表示缓存不可用,需要直接请求服务器。
-
networkRequest
为空,cacheResponse
不为空。表示缓存可以直接使用,不需要向服务器验证。
-
networkRequest
和cacheResponse
都不为空。 表示虽然有缓存,但是需要向服务器验证是否可用。
在了解了 CacheStrategy
的各种状态后,我们再看看源码的处理流程:
-
没有缓存,直接请求网络。
-
有缓存,但是
https
协议的handshake
缺失,直接请求网络。 -
通过
Response
和Reqeust
调用isCacheable()
方法(后面分析)判断是否能够使用缓存,不能够使用缓存,直接请求网络。 -
Request
中的缓存控制如果有no-cache
或者请求头有缓存控制的相关的条件(也就是有If-None-Match
或者If-Modified-Since
),直接请求网络。 -
如果
Response
中缓存控制没有no-cache
(这里注意在Response
中no-cache
表示可以使用缓存但是使用前必须要向服务器再验证一次,no-store
表示不允许使用缓存。这个命名让人有点懵逼。),表示服务器可以接受不验证缓存。同时缓存的年纪没有达到最大的限制。这样就直接使用缓存且不验证服务器。 -
剩下的情况就是有缓存且需要向服务器验证,在请求的
Request
中添加验证缓存相关的Header
(If-None-Match
或者If-Modified-Since
)。
我们再来看看 isCacheable()
方法的实现:
Kotlin
fun isCacheable(response: Response, request: Request): Boolean {
// Always go to network for uncacheable response codes (RFC 7231 section 6.1), This
// implementation doesn't support caching partial content.
when (response.code) {
HTTP_OK,
HTTP_NOT_AUTHORITATIVE,
HTTP_NO_CONTENT,
HTTP_MULT_CHOICE,
HTTP_MOVED_PERM,
HTTP_NOT_FOUND,
HTTP_BAD_METHOD,
HTTP_GONE,
HTTP_REQ_TOO_LONG,
HTTP_NOT_IMPLEMENTED,
StatusLine.HTTP_PERM_REDIRECT -> {
// These codes can be cached unless headers forbid it.
}
HTTP_MOVED_TEMP,
StatusLine.HTTP_TEMP_REDIRECT -> {
// These codes can only be cached with the right response headers.
// http://tools.ietf.org/html/rfc7234#section-3
// s-maxage is not checked because OkHttp is a private cache that should ignore s-maxage.
if (response.header("Expires") == null &&
response.cacheControl.maxAgeSeconds == -1 &&
!response.cacheControl.isPublic &&
!response.cacheControl.isPrivate) {
return false
}
}
else -> {
// All other codes cannot be cached.
return false
}
}
// A 'no-store' directive on request or response prevents the response from being cached.
return !response.cacheControl.noStore && !request.cacheControl.noStore
}
}
上面代码比较简单分为以下情况
-
200,203,204,300,301,404,405,410,414,501,308
需要
Request Header
和Response Header
中没有no-store
才可以缓存。 -
302,307
如果
Response Header
中没有Expires
,max-age
,public
和private
,禁止缓存;其他情况需要Request Header
和Response Header
中没有no-store
才可以缓存。 -
其他情况禁止缓存
Cache
更加具体的缓存原理我就没有贴代码了,这里只是简单的看看 get()
和 put()
方法。
get()
Kotlin
internal fun get(request: Request): Response? {
// 通过 url 的 md5 作为 key.
val key = key(request.url)
val snapshot: DiskLruCache.Snapshot = try {
cache[key] ?: return null
} catch (_: IOException) {
return null // Give up because the cache cannot be read.
}
val entry: Entry = try {
Entry(snapshot.getSource(ENTRY_METADATA))
} catch (_: IOException) {
snapshot.closeQuietly()
return null
}
val response = entry.response(snapshot)
if (!entry.matches(request, response)) {
response.body?.closeQuietly()
return null
}
return response
}
这里有一点比较有意思,Cache
存储 Response
的方式是分两部分存储:除 Response Body
外的部分存储为 ENTRY_METADATA
; Response Body
存储为 ENTRY_BODY
。在读取的时候 ENTRY_METADATA
是立即读取到 Response
中,而 ENTRY_BODY
就不是,需要等外部调用的地方真正读取的时候才去缓存文件中加载。获取到 ENTRY_METADATA
后,会去检查 Vary
Response Header
,如果 Vary
值为 *
,禁止使用缓存,这个表示该 Response
的结果受 Request
外的参数影响。
put()
Kotlin
internal fun put(response: Response): CacheRequest? {
val requestMethod = response.request.method
if (HttpMethod.invalidatesCache(response.request.method)) {
try {
remove(response.request)
} catch (_: IOException) {
// The cache cannot be written.
}
return null
}
// 只缓存 GET 请求
if (requestMethod != "GET") {
// Don't cache non-GET responses. We're technically allowed to cache HEAD requests and some
// POST requests, but the complexity of doing so is high and the benefit is low.
return null
}
// 检查 Vary 参数
if (response.hasVaryAll()) {
return null
}
val entry = Entry(response)
var editor: DiskLruCache.Editor? = null
try {
editor = cache.edit(key(response.request.url)) ?: return null
entry.writeTo(editor)
return RealCacheRequest(editor)
} catch (_: IOException) {
abortQuietly(editor)
return null
}
}
只是缓存 GET
请求,和 get()
方法一样,ENTRY_METADATA
的数据会被立马写入到本地缓存文件中,而 ENTRY_BODY
是只有在外部读取 Response Body
的时候才会被写入到本地缓存文件中。
最后
我还是低估了 CacheIntercptor
的复杂程度,本篇文章也是够长了,下一篇文章继续介绍系统的最后一个拦截器 CallServerInterceptor
。