缓存的作用
- 减少请求次数,较少服务器压力。
- 本地数据读取更快,让页面不会空白几百毫秒。
- 在无网络的情况下提供数据。
HTTP
缓存是最好的减少客户端服务器往返次数的方案,缓存提供了一种机制来保证客户端或者代理能够存储一些东西,而这些东西将会在稍后的 HTTP
响应中用到的。(即第一次请求了,到了客户端,缓存起来,下次如果请求还需要一些资源,就不用到服务器去取了)这样,就不用让一些资源再次跨越整个网络了。
HTTP 缓存相关知识
HTTP 报文
我们之前说的 Reuqest
和 Response
,在 HTTP
中叫做报文。客户端向服务器请求数据,发送请求(request)报文,服务器向客户端下发返回数据,返回响应(response)报文,报文信息主要分为两部分。
- head ,包括请求报文头和响应报文头,用于添加一些附加信息,比如
cookie
、 缓存策略等。 - body ,包含数据的主体部分,请求或响应真正想要传输的内容。
缓存分类
按照 “是否向服务器发起请求来进行信息对比” 来分类,可分为 “ 强制缓存” 和 “ 对比缓存 ”。
强制缓存: 如果有缓存,而缓存有效,则不再和服务器交互了,直接返回缓存内容。
对比缓存: 如果有缓存,无论缓存有否有效,跟服务器交互,如果返回 “缓存是有效的”,则直接用缓存内容,如果服务器自己判断缓存信息无效,会直接返回新的响应结果。
请求头header中有关缓存的设置 详解
上述说到的跟服务器交互,是通过响应头相关内容来进行的。
Cache-Control
它是一个处于 Request
以及 Response
的 Headers
中的一个字段,对于请求的指令及响应的指令,它有如下不同的取值:
请求缓存指令
max-age=
:设置缓存存储的最大有效时长,超过时间则被认为过期,时间是相对于请求的时间。 max-stale=
:表明客户端愿意接收一个已经过期的资源,可以设置一个指定的秒数,过期的响应不能超过该给定的时间。 min-fresh=
:表示客户端希望获取一个能在指定的秒数内保持其最新状态的响应。 no-cache :可以在本地和服务器缓存,但是这个缓存要服务器验证才可以使用。
no-store:彻底禁用缓存,本地和服务器都不缓存,每次都从服务器获取。
no-transform:不得对资源进行转换或转变,Content-Encoding、 Content-Range、 Content-Type 等 Header 不能由代理修改。
only-if-cached:只用缓存的响应,永远不向服务器请求新的数据。
响应缓存指令
must-revalidate:一旦资源过期(比如已经超过max-age),在成功向原始服务器验证之前,缓存不能用该资源响应后续请求。
no-cache :可以在本地和服务器缓存,但是这个缓存要服务器验证才可以使用。
no-store:彻底禁用缓存,本地和服务器都不缓存,每次都从服务器获取。
no-transform:不得对资源进行转换或转变,Content-Encoding、 Content-Range、 Content-Type 等 Header 不能由代理修改。
public:表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容(例如,该响应没有 max-age 指令或 Expires 消息头)。
private:表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它),私有缓存可以缓存响应内容。
proxy-revalidate:与 must-revalidate 作用相同,但它仅适用于共享缓存(如代理),并被私有缓存忽略。
max-age=
:设置缓存存储的最大周期,超过这个的时间缓存被认为过期,时间是相对于请求的时间。 s-maxage=
:覆盖 max-age 或者 Expires 头,但它仅适用于共享缓存(如代理),并被私有缓存忽略。
其中我们常用的就是加粗的几个字段(max-age、max-stale、no-cache)。
Expires
Expires
头是 HTTP1.0
中的内容,它的作用类似于 Cache-Control:max-age
,它告诉浏览器缓存的过期时间,这段时间浏览器就可以不用直接再向服务器请求了。这里面有个问题,由于到期时间是服务器生成的,但是客户端的时间可能和服务器有误差,所以这就会导致误差,所以到了 HTTP1.1
基本上不适用 expires
了。
Last-Modified / If-Modified-Since
这两个字段需要配合上面的 Cache-Control
来使用。
Last-Modified: 在响应头返回,告诉响应资源最后修改时间。
If-Modified-Since: 在请求头中添加,当客户端缓存过期时(max-age 到达),发现该资源具有 Last-Modified 字段,可以在 Header 中填入 If-Modified-Since 字段,它的值就是 Last-Modified 中的值。服务器收到该时间后会与该资源的最后修改时间进行比较,若最后修改的时间更新一些,则会对整个资源响应,否则说明该资源在访问时未被修改,响应状态码 304 ,告知客户端继续使用缓存的资源。
Etag / If-None-Match
ETag/If-None-Match (优先级高于Last-Modified/If-Modified-Since) 同样也需要配合上面的 Cache-Control 来使用。
Etag:请求的资源在服务器中的唯一标识,规则由服务器决定,在响应头里面添加返回。
If-None-Match:若客户端在缓存过期时(max-age 到达),发现该资源具有 Etag 字段,就可以在 Header 中填入 If-None-Match 字段,它的值就是 Etag 中的值,之后服务器就会根据这个唯一标识来寻找对应的资源,根据其更新与否情况返回给客户端 200 或 304。
总体的请求流程如下图:
OkHttp 中的缓存机制
有了上面讲到的缓存相关知识,我们现在开始正式地分析里面的缓存逻辑。
InternalCache
CacheInterceptor 中 InternalCache 对象对缓存进行 CRUD 操作。 InternalCache 只是一个接口,它定义了对 HTTP 请求的缓存的 CRUD 接口。让我们看看它的定义:
// 官方禁止开发者实现该类,这是 OkHttp 内部的缓存接口,只能使用 Cache 类
public interface InternalCache {
Response get(Request request) throws IOException;
CacheRequest put(Response response) throws IOException;
void remove(Request request) throws IOException;
void update(Response cached, Response network);
void trackConditionalCacheHit();
void trackResponse(CacheStrategy cacheStrategy);
}
Cache
public final class Cache implements Closeable, Flushable {
// 真正起作用的类
private final DiskLruCache cache;
public Cache(File directory, long maxSize) {
this(directory, maxSize, FileSystem.SYSTEM);
}
Cache(File directory, long maxSize, FileSystem fileSystem) {
this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}
// InternalCache 接口实现
final InternalCache internalCache = new InternalCache() {
@Override public Response get(Request request) throws IOException {
return Cache.this.get(request);
}
@Override public CacheRequest put(Response response) throws IOException {
return Cache.this.put(response);
}
@Override public void remove(Request request) throws IOException {
Cache.this.remove(request);
}
@Override public void update(Response cached, Response network) {
Cache.this.update(cached, network);
}
@Override public void trackConditionalCacheHit() {
Cache.this.trackConditionalCacheHit();
}
@Override public void trackResponse(CacheStrategy cacheStrategy) {
Cache.this.trackResponse(cacheStrategy);
}
};
}
由源码可知,Cache 类并不是 InternalCache 的实现类,而是在内部构造了一个实现类,然后在方法实现中转调到了 Cache 类中的 CRUD 相关实现,为什么不直接实现接口,反而在内部持有了一个实现了 InternalCache 的内部对象 internalCache 呢?原因在后面会一步步分析。
Cache 类内部有一个 DiskLruCache 类,看来 OkHttp 的缓存的实现是基于 DiskLruCache 实现的,Cache 中的 CRUD 操作都是在对 DiskLruCache 对象进行操作。
Cache 的配置入口
如果我们需要 OkHttp帮忙做缓存处理,可以通过 OkHttpClient 进行配置,示例如下:
File cacheFile = new File(cachePath); // 缓存路径
int cacheSize = 10 * 1024 * 1024; // 缓存大小10MB
Cache cache = new Cache(cacheFile, cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
需要指定一个缓存文件和缓存文件的最大值,通过这种方式就可以配置缓存了。但是如果我们想自定义缓存实现类,不可以,官方禁止。在 OkHttpClient 的 builder 里面,有如下方法:
void setInternalCache(InternalCache internalCache) {
this.internalCache = internalCache;
this.cache = null;
}
该方法不是公有的,用于内部调用,当设置 InternalCache 时,cache 会被置为 Null ,cache 就是 Cache 类,这也是为什么 Cache 不直接实现 InternalCache 接口的原因,如果实现了,那么根据多态原理,这里也可以传入 Cache 类了,这跟逻辑实现不符。
缓存的增、删、改、查操作
增操作——put()方法
CacheRequest put(Response response) {
String requestMethod = response.request().method();
//判断请求如果是"POST"、"PATCH"、"PUT"、"DELETE"、"MOVE"中的任何一个
if (HttpMethod.invalidatesCache(response.request().method())) {
try {
// 删除现有缓存并结束
remove(response.request());
} catch (IOException ignored) {
// The cache cannot be written.
}
return null;
}
// 判断请求如果不是Get则不进行缓存,直接返回null
if (!requestMethod.equals("GET")) {
// 官方给的解释是缓存get方法得到的Response效率高,其它方法的Response没有缓存效率低。
// 通常通过get方法获取到的数据都是固定不变的的,因此缓存效率自然就高了。
// 其它方法会根据请求报文参数的不同得到不同的Response,因此缓存效率自然而然就低了。
return null;
}
//判断请求中的http数据包中headers是否有符号"*"的通配符,有则不缓存直接返回null
if (HttpHeaders.hasVaryAll(response)) {
return null;
}
//由Response对象构建一个Entry对象,Entry是Cache的一个内部类
Entry entry = new Entry(response);
DiskLruCache.Editor editor = null;
try {
editor = cache.edit(key(response.request().url()));
if (editor == null) {
return null;
}
//把这个entry写入
entry.writeTo(editor);
return new CacheRequestImpl(editor);
} catch (IOException e) {
abortQuietly(editor);
return null;
}
}
public static String key(HttpUrl url) {
return ByteString.encodeUtf8(url.toString()).md5().hex();
}
上述的缓存的 KEY 的生成规则是,先把 url 用 utf-8 编码后,进行 md5 加密操作,然后转为 16 进制。
删操作——move()方法
void remove(Request request) throws IOException {
cache.remove(key(request.url()));
}
改操作——update()方法
void update(Response cached, Response network) {
//用response构造一个Entry对象
Entry entry = new Entry(network);
//从命中缓存中获取到的DiskLruCache.Snapshot
DiskLruCache.Snapshot snapshot = ((CacheResponseBody) cached.body()).snapshot;
//从DiskLruCache.Snapshot获取DiskLruCache.Editor()对象
DiskLruCache.Editor editor = null;
try {
editor = snapshot.edit(); // Returns null if snapshot is not current.
if (editor != null) {
//将entry写入editor中
entry.writeTo(editor);
editor.commit();
}
} catch (IOException e) {
abortQuietly(editor);
}
}
查操作——get()方法
Response get(Request request) {
//获取url经过MD5和HEX的key
String key = key(request.url());
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
//根据key来获取一个snapshot,由此可知我们的key-value里面的value对应的是snapshot
snapshot = cache.get(key);
if (snapshot == null) {
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}
//利用前面的Snapshot创建一个Entry对象。存储的内容是响应的Http数据包Header部分的数据。snapshot.getSource得到的是一个Source对象 (source是okio里面的一个接口)
try {
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
} catch (IOException e) {
Util.closeQuietly(snapshot);
return null;
}
//利用entry和snapshot得到Response对象,该方法内部会利用前面的Entry和Snapshot得到响应的Http数据包Body(body的获取方式通过snapshot.getSource(ENTRY_BODY)得到)创建一个CacheResponseBody对象;再利用该CacheResponseBody对象和第三步得到的Entry对象构建一个Response的对象,这样该对象就包含了一个网络响应的全部数据了。
Response response = entry.response(snapshot);
//对request和Response进行比配检查,成功则返回该Response。匹配方法就是url.equals(request.url().toString()) && requestMethod.equals(request.method()) && OkHeaders.varyMatches(response, varyHeaders, request);其中Entry.url和Entry.requestMethod两个值在构建的时候就被初始化好了,初始化值从命中的缓存中获取。因此该匹配方法就是将缓存的请求url和请求方法跟新的客户请求进行对比。最后OkHeaders.varyMatches(response, varyHeaders, request)是检查命中的缓存Http报头跟新的客户请求的Http报头中的键值对是否一样。如果全部结果为真,则返回命中的Response。
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
return response;
}
CacheInterceptor 类详解
@Override public Response intercept(Chain chain) throws IOException {
// 如果存在缓存,则从缓存中取出,有可能为 null
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 获取缓存策略对象
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
// 获取策略对象的请求
Request networkRequest = strategy.networkRequest;
// 获取策略对象的响应
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body());
}
// If we're forbidden from using the network and the cache is insufficient, fail.
// 表明不进行网络请求,且缓存不存在或者过期,返回504错误
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();
}
// cacheResponse 不为 null ,即缓存有效,不使用网络,返回缓存响应
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
// 缓存无效且需要使用网络,执行下一个拦截器
networkResponse = chain.proceed(networkRequest);
} finally {
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
if (cacheResponse != null) {
// 响应码为 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();
cache.trackConditionalCacheHit();
// 更新缓存
cache.update(cacheResponse, response);
// 返回响应结果
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
// 使用网络响应
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
// 设置了缓存文件夹
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// 缓存到本地
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
// 删除缓存
cache.remove(networkRequest);
} catch (IOException ignored) {
}
}
}
return response;
}
CacheStrategy 详解
该类主要是结合头信息 Cache-Control 的处理,根据头信息给定的策略信息来做相应的处理。
CacheStrategy 使用 Factory 模式进行构造,通过 Factory的get() 方法获取 CacheStrategy 的对象,参数如下:
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
if (cacheResponse != null) {
this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
Headers headers = cacheResponse.headers();
//获取 cacheReposne 的 header 里的值
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);
}
}
}
}
构造方法主要是对一些变量的初始化。接着我们看到 Factory.get 方法,通过该方法我们就获得了 CacheStrategy 对象:
public CacheStrategy get() {
//获取当前的缓存策略
CacheStrategy candidate = getCandidate();
//如果是网络请求不为null并且请求里面的cacheControl是只用缓存
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;
}
这里首先通过 getCandidate 方法获取到了对应的缓存策略。如果发现我们的请求中指定了禁止使用网络,只使用缓存(指定 CacheControl 为 only-if-cached ),则创建一个 networkRequest 及 cacheResponse 均为 null 的缓存策略。我们接着看到 getCandidate 方法:
private CacheStrategy getCandidate() {
if (cacheResponse == null) {
// //如果没有缓存响应,返回一个没有响应的策略
// 需要进行网络请求,而且缓存不存在或者过期,直接访问网络
return new CacheStrategy(request, null);
}
//如果是https,丢失了握手,返回一个没有响应的策略
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
// 响应不能被缓存
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//获取请求头里面的CacheControl
CacheControl requestCaching = request.cacheControl();
//如果请求里面设置了不缓存,则不缓存
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
//获取响应的年龄
long ageMillis = cacheResponseAge();
//获取上次响应刷新的时间
long freshMillis = computeFreshnessLifetime();
//如果请求里面有最大持久时间要求,则两者选择最短时间的要求
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
long minFreshMillis = 0;
//如果请求里面有最小刷新时间的限制
if (requestCaching.minFreshSeconds() != -1) {
//用请求中的最小更新时间来更新最小时间限制
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
//最大验证时间
long maxStaleMillis = 0;
//响应缓存控制器
CacheControl responseCaching = cacheResponse.cacheControl();
//如果响应(服务器)那边不是必须验证并且存在最大验证秒数
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
//更新最大验证时间
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
//缓存响应
return new CacheStrategy(null, builder.build());
}
String conditionName;
String conditionValue;
if (etag != null) {
conditionName = "If-None-Match";
conditionValue = etag;
} else if (lastModified != null) {
conditionName = "If-Modified-Since";
conditionValue = lastModifiedString;
} else if (servedDate != null) {
conditionName = "If-Modified-Since";
conditionValue = servedDateString;
} else {
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
}
在缓存策略的创建中,主要是以下几步:
- 没有缓存 response,直接进行寻常网络请求。
- HTTPS 的 response 丢失了握手相关数据,丢弃缓存直接进行网络请求。
- 缓存的 response 的 code 不支持缓存,则忽略缓存,直接进行寻常网络请求。
- 对 Cache-Control 中的字段进行处理,主要是计算缓存是否还能够使用(比如超过了 max-age 就不能再使用)。
- 对 If-None-Match、If-Modified-Since 字段进行处理,填入相应 Header(同时可以看出 Etag 确实比 Last-Modified 优先级要高。
我们可以发现,OkHttp 中实现了一个 CacheControl 类,用于以面向对象的形式表示 HTTP 协议中的 Cache-Control Header,从而支持获取 Cache-Control 中的值。
同时可以看出,我们的缓存策略主要存在以下几种情况:
request != null, response == null:执行寻常网络请求,忽略缓存。
request == null, response != null:采用缓存数据,忽略网络数据。
request != null, response != null:存在 Last-Modified、Etag 等相关数据,结合 request 及缓存中的 response。
request == null, response == null:不允许使用网络请求,且没有缓存,在 CacheInterceptor 中会构建一个 504 的 response。
ConnectInterceptor 分析
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
// 获取流
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
// 获取连接
RealConnection connection = streamAllocation.connection();
// 所有入参的对象都创建出来了,开始下个拦截器来请求
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
获取对应的流对象和连接对象,将这两个对象传入到下一个拦截器开始进行请求。
CallServerInterceptor 分析
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
// 写入请求头
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
responseBuilder = httpCodec.readResponseHeaders(true);
}
// 写入请求体
if (responseBuilder == null) {
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
} else if (!connection.isMultiplexed()) {
streamAllocation.noNewStreams();
}
}
httpCodec.finishRequest();
// 读取响应头
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
// 读取响应体
int code = response.code();
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
// 返回响应结果
return response;
}
该拦截器主要做了5步:
写入请求头 - 写入请求体 - 读取响应头 - 读取响应体 - 返回响应结果