上一篇文章我们说了OKHTTP的重定向和跟进拦截器OKHTTP深入浅出(四)----拦截器(1)RetryAndFollowUpInterceptor_王胖子总叫我减肥的博客-CSDN博客
下面我们来说说桥拦截器ridgeInterceptor和缓存拦截器CacheInterceptor
1、桥拦截器ridgeInterceptor
? ? ?桥拦截器相当于应用层和网络层之间的桥梁,相当于 在 请求发起端 和 网络执行端 架起一座桥,把应用层发出的请求(请求发送端)?变为 网络层认识的请求(网络执行端),把网络层执行后的响应 变为 应用层可以直接使用的结果。
public final class BridgeInterceptor implements Interceptor {
//Cookie 管理器,初始化okhttpClient的时候创建的
private final CookieJar cookieJar;
public BridgeInterceptor(CookieJar cookieJar) {
this.cookieJar = cookieJar;
}
@Override public Response intercept(Chain chain) throws IOException {
//获取请求发送端的请求
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
// 封装处理了contentType、contentLength、Transfer-Encoding、
// Host、Connection、Accept-Encoding、Cookie、User-Agent等请求头
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
// 如果服务器支出Gzip压缩,客户端不用设置,OKhttp会自动帮我们开启Gzip压缩和解压
// 如果客户端设置了Gzip,那就需要自己解压服务器返回的数据
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
// 从CookieJar中获取 cookies,添加到header
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
// 把处理好的请求往下传递,执行后续的拦截器的逻辑
Response networkResponse = chain.proceed(requestBuilder.build());
// 获取networkResponse中的header的“set-cookie” ,存入到Cookiejar
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
// 获取networkResponse的Builder
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
//处理返回的Response的"Content-Encoding"、"Content-Length"、"Content-Type"等返回头
//如果我们没有手动添加"Accept-Encoding: gzip",这里会创建能自动解压的
//responseBody-GzipSource
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
//返回处理好的Response
return responseBuilder.build();
}
/** Returns a 'Cookie' HTTP request header with all cookies, like {@code a=b; c=d}. */
private String cookieHeader(List<Cookie> cookies) {
StringBuilder cookieHeader = new StringBuilder();
for (int i = 0, size = cookies.size(); i < size; i++) {
if (i > 0) {
cookieHeader.append("; ");
}
Cookie cookie = cookies.get(i);
cookieHeader.append(cookie.name()).append('=').append(cookie.value());
}
return cookieHeader.toString();
}
}
? ? ? 首先在Response networkResponse = chain.proceed(requestBuilder.build());真正的请求执行之前,获取了请求的Builder,对请求添加了header:“Content-Type”、“Content-Length” 或 “Transfer-Encoding”、“Host”、“Connection”、“Accept-Encoding”、“Cookie”、“User-Agent”,将请求转换成网络层真正可执行的请求。其中,注意到,默认是没有cookie处理的,需要我们在初始化OkHttpClient的时候配置我们自己的CookieJar。
? ? ?chain.proceed() 执行后,先把响应header中的cookie存入cookieJar(如果有),然后如果没有手动添加请求header “Accept-Encoding: gzip”,那么会通过 创建能自动解压的responseBody——GzipSource,接着构建新的response返回。 ?
2、缓存拦截器 CacheInterceptor
? ? ?缓存拦截器提供网络请求缓存的读取。如果每次请求都经过网络的发送和读取,会很耗时,假如一个相同的请求已经执行了一次,那么这一次我们是不是可以直接用上次的结果呢?这就是缓存拦截器的功能。
大概了解一下http的缓存
第一次请求
?第二次请求
?
public final class CacheInterceptor implements Interceptor {
final @Nullable InternalCache cache;
public CacheInterceptor(@Nullable InternalCache cache) {
this.cache = cache;
}
@Override public Response intercept(Chain chain) throws IOException {
// 获取候选缓存(cache不为空的情况)
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 1
// 获取缓存执行的逻辑
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
// networkRequest == null ,不使用网络
Request networkRequest = strategy.networkRequest;
// cacheResponse == null 不使用缓存
Response cacheResponse = strategy.cacheResponse;
// 根据缓存策略更新统计指标:请求次数、使用缓存次数
if (cache != null) {
cache.trackResponse(strategy);
}
// 有缓存,但是缓存返回的cacheResponse为空。说明不能用,关闭
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// networkRequest == null ,不能使用网络,并且cacheResponse为null,返回504
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// 如果不需要使用网络,直接返回缓存的数据
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
// 走到这里,我们已经把networkRequest == null的情况分析完了,说明往下
// networkRequest != null,但是cacheResponse可能为null,可能不为!null
// 因此下面就是没有命中缓存的情况下,进行网络请求,获取Response
// 具体的步骤(判断缓存是否命中,命中返回缓存,未命中使用网络请求Response并添加缓存)
Response networkResponse = 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) {
closeQuietly(cacheCandidate.body());
}
}
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
// 如果缓存结果不为空,并且code为304,说明缓存没有发生变化,继续使用缓存数据
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response 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;
} else {
// 如果code不是304,说明服务端资源有更新,关闭缓存
closeQuietly(cacheResponse.body());
}
}
// 如果缓存未命中,获取网络返回的Response
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
// 如果网络请求可以缓存(请求和响应头的cache-control都不是“no -store”)
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// 将网络请求Response保存到缓存中
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
// OKhttp只会对get请求进行缓存,不是就移除缓存
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
注释1,是缓存拦截器的核心,获取缓存拦截器的缓存策略。将主要的参数有当前的时间戳、请求、缓存传入到CacheStrategy.Factory中,然后调用了其中的get()方法。
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
先看一下CacheStrategy.Factory
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
//解析cacheResponse,把参数赋值给自己的成员变量
if (cacheResponse != null) {
// 获取获选缓存的请求时间、响应时间、从header中获取过期时间、修改时间、资源标记时间(有的话)
this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
Headers headers = cacheResponse.headers();
for (int i = 0, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
if ("Date".equalsIgnoreCase(fieldName)) {
servedDate = HttpDate.parse(value);
servedDateString = value;
} else if ("Expires".equalsIgnoreCase(fieldName)) {
expires = HttpDate.parse(value);
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
lastModified = HttpDate.parse(value);
lastModifiedString = value;
} else if ("ETag".equalsIgnoreCase(fieldName)) {
etag = value;
} else if ("Age".equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(value, -1);
}
}
}
}
再看一下get()方法。使用缓存的响应,返回满足请求的策略。
public CacheStrategy get() {
// 获取候选缓存
CacheStrategy candidate = getCandidate();
// 如果候选缓存的网络请求不为空,并且请求设置了只能试用缓存,此时即使有缓存,也是过期缓存
// 因此会 new 一个新的 CacheStrategy(缓存策略)
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
return new CacheStrategy(null, null);
}
return candidate;
}
在深入看一下get()方法中的CacheStrategy candidate = getCandidate();
private CacheStrategy getCandidate() {
// 如果不使用缓存就返回一个空的response的CacheStrategy
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// 如果是HTTPS,但是没有握手,就进行网络请求
// Drop the cached response if it's missing a required handshake.
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
// 不可使用缓存(请求或响应头的cache-control为“no-strore”)进行网络请求
// 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)) {
return new CacheStrategy(request, null);
}
//求头的Cache-Control是no-cache 或者 请求头有"If-Modified-Since"或"If-None-Match"
// 进行网络请求
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
// 强制缓存
CacheControl responseCaching = cacheResponse.cacheControl();
// 缓存的年龄
long ageMillis = cacheResponseAge();
// 缓存的有效期
long freshMillis = computeFreshnessLifetime();
// 判断强制缓存是否有效,是的话就返回缓存数据,比较请求头的有效期,取最小值
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
//可接受的最小剩余有效时间(min-Fresh标识了客户端不愿意接受剩余有效期<=min-fresh的缓存)
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
// 可接受的最大过期时间
// (max-stale指令标示了客户端愿意接收一个已经过期了的缓存,例如 过期了 1小时 还可以用)
long maxStaleMillis = 0;
//第一个判断时候要求必须去服务器验证资源
//第二个判断,获取max-stale的值,不等于-1,说明缓存过期后,还能试用指定的时长
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
// 如果响应头没有要求忽略本地缓存 且 整合后的缓存年龄 小于 整合后的过期时间,那么缓存就可以用
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
// 没有满足“可接受的最小 剩余有效时间”,加个110警告
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
//isFreshnessLifetimeHeuristic表示没有过期时间,那么大于一天,就加个113警告
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
return new CacheStrategy(null, builder.build());
}
//缓存协议修改了request
//缓存是过去的,找缓存中的Etag、lastModified、serverdate
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
String conditionName;
String conditionValue;
if (etag != null) {
// etag协商缓存
conditionName = "If-None-Match";
conditionValue = etag;
} else if (lastModified != null) {
// Last-Modified协商缓存
conditionName = "If-Modified-Since";
// 最后修改时间
conditionValue = lastModifiedString;
} else if (servedDate != null) {
// Last-Modified协商缓存
conditionName = "If-Modified-Since";
// 服务器最后修改时间
conditionValue = servedDateString;
} else {
// 没有协商缓存,返回一个空的Response的CacheStrategy
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
// 设置header
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
//conditionalRequest表示 有条件的网络请求:
//有缓存但过期了,去请求网络 询问服务端,还能不能用。能用侧返回304,不能则正常执行网路请求。
return new CacheStrategy(conditionalRequest, cacheResponse);
}
getCandidate()方法主要做了以下判断,获取缓存策略:
- 没有缓存、HTTPS没有握手、不能缓存、忽略缓存、手动配置缓存过期,直接网络请求
- 不属于以上情况,如果缓存没有过期,就使用缓存(可能要添加警告)
- 缓存过期,但响应头有Etag、last-modified、data,就添加这些响应头进行协商网络请求
- 如果缓存过期了,且响应头没有设置Etag,Last-Modified,Date,就进行网络请求
|