Cache-Control字段
在正式介绍CacheInterceptor拦截器之前,我们有必要复习下Cache-Control字段。因为CacheInterceptor实际上是对Cache-Control字段的逻辑实现
常见的取值及含义是
no-store | 彻底禁用缓存,所有内容都不会被缓存到缓存或临时文件中 |
no-cache | 在浏览器使用缓存前,会往返对比ETag,如果ETag没变,返回304,则使用缓存 |
public | 所有的内容都将被缓存 |
private | 缓存仅客户端可以缓存,代理服务器不可缓存 |
max-age | 缓存的内容将在xxx秒后失效,这个选项只有在http1.1可用,并如果和Last-Modified一起使用时,优先级较高 |
代码逻辑
代码分三部分
1. 进行cache和request的预处理
//首先应该留意到这个构造方法, 需要初始化一个Cache对象, 而Cache类是final类型的, 所有这个地方不需要自己实现Cache的逻辑
class CacheInterceptor(internal val cache: Cache?) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val call = chain.call()
//从cache中获取当前的CacheResponse. 这个cacheCandidate实际上就是一个Response
val cacheCandidate = cache?.get(chain.request())
val now = System.currentTimeMillis()
//上面从cache, 即使上是从DiskLruCache中读取了CacheResponse,但是这个CacheResponse不一定可用, 可能会受到Cache Control中的
//过期等影响, 同样,原始的Request也不一定可用个, 需要利用CacheStrategy来综合判断处理过的request和cache response,也就是后面
//获取到的networkRequest和cacheResponse
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
val networkRequest = strategy.networkRequest
val cacheResponse = strategy.cacheResponse
2. 进行网络请求,拿到network response
// 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 {
listener.satisfactionFailure(call, it)
}
}
// If we don't need the network, we're done.
if (networkRequest == null) {
//如果没有network request, 则返回cacheResponse
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build().also {
listener.cacheHit(call, it)
}
}
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()
}
}
3. 对返回结果进行保存更新等处理
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
if (networkResponse?.code == HTTP_NOT_MODIFIED) {
// 如果服务端返回没有修改, 则返回cacheResponse 略加修改,返回
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
cache.update(cacheResponse, response)
return response.also {
listener.cacheHit(call, it)
}
} else {
cacheResponse.body?.closeQuietly()
}
}
// 更新cache
val response = networkResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
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)
}
}
}
//post put delete move方法是灭有cache的
if (HttpMethod.invalidatesCache(networkRequest.method)) {
try {
cache.remove(networkRequest)
} catch (_: IOException) {
// The cache cannot be written.
}
}
}
上面的核心在于第一步,对cache response和network request进行预处理部分,该部分如下
/** Returns a strategy to use assuming the request can use the network. */
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请求, 但是cache response没有进行握手操作.
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.
if (!isCacheable(cacheResponse, request)) {
//判断上一次的response是否可以被使用
return CacheStrategy(request, null)
}
val requestCaching = request.cacheControl
if (requestCaching.noCache || hasConditions(request)) {
// 如果request用的是no cache, 则不使用cache
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())
}
if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
val builder = cacheResponse.newBuilder()
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"")
}
val oneDayMillis = 24 * 60 * 60 * 1000L
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.
// 这部分的逻辑应该是找到了,进行etag时间进行匹配
val conditionName: String
val conditionValue: String?
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!!)
val conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build()
return CacheStrategy(conditionalRequest, cacheResponse)
}