OkHttp 源码剖析系列(二)——拦截器大体流程分析
系列索引
本系列文章基于 OkHttp3.14
OkHttp 源码剖析系列(一)——请求的发起及拦截器机制概述
OkHttp 源码剖析系列(六)——连接复用机制及连接的建立
前言
之前的文章介绍到了 OkHttp 的拦截器机制的整体概述,现在让我们依次研究一下其拦截器的实现。
RetryAndFollowUpInterceptor
前面提到,RetryAndFollowUpInerceptor
负责了 HTTP 请求的重定向功能,那让我们先了解一下 HTTP 协议中的重定向。
HTTP 中的重定向
HTTP 协议提供了一种重定向的功能,它通过由服务器返回特定格式的响应从而触发客户端的重定向。其对应的 Response Code 格式为 3XX,并且会在 Response Header 的 Location
字段中放入新的 URL,这样我们客户端就可以根据该 Location 字段所指定的 URL 重新请求从而得到需要的数据。
其过程如下图所示:
img其中重定向对应的状态码及含义如下表所示(摘自维基百科):
image-20190730211626735重定向与服务器转发的区别
可以发现,重定向和服务器转发请求是有些相似的,它们有什么不同呢?
-
重定向是客户端行为,而服务器转发则是服务端行为
-
重定向我们的客户端发出了多次请求,而转发我们的客户端只发出了一次请求。
-
重定向的控制权在客户端,转发的控制权在服务端。
代码分析
接下来让我们研究一下 RetryAndFollowUpInterceptor
的实现原理,我们看到 RetryAndFollowUpInterceptor.intercept
方法:
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
// 获取transmitter
Transmitter transmitter = realChain.transmitter();
int followUpCount = 0;
Response priorResponse = null;
while (true) {
// 进行一些连接前的准备工作
transmitter.prepareToConnect(request);
// 处理取消事件
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
Response response;
boolean success = false;
try {
// 调用chain的proceed方法获取下层得到的结果
response = realChain.proceed(request, transmitter, null);
success = true;
} catch (RouteException e) {
// 若不满足重定向的条件,抛出异常
if (!recover(e.getLastConnectException(), transmitter, false, request)) {
throw e.getFirstConnectException();
}
// 满足重定向条件,重试
continue;
} catch (IOException e) {
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
// 不满足重定向条件,抛出异常
if (!recover(e, transmitter, requestSendStarted, request)) throw e;
// 满足重定向条件,重试
continue;
} finally {
if (!success) {
// 若抛出了异常,释放资源
transmitter.exchangeDoneDueToException();
}
}
// 在本次response中设置上一次的response,其body为空
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Exchange exchange = Internal.instance.exchange(response);
Route route = exchange != null ? exchange.connection().route() : null;
// 根据response code获取重定向后的request
Request followUp = followUpRequest(response, route);
if (followUp == null) {
// 不再需要重定向,停止timeout计时并返回response
if (exchange != null && exchange.isDuplex()) {
transmitter.timeoutEarlyExit();
}
return response;
}
RequestBody followUpBody = followUp.body();
if (followUpBody != null && followUpBody.isOneShot()) {
return response;
}
closeQuietly(response.body());
if (transmitter.hasExchange()) {
exchange.detachWithViolence();
}
// 重定向不超过20次,否则抛出异常
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
// 修改下次重定向的request
request = followUp;
// 记录上一次的response
priorResponse = response;
}
}
可以看到,这里外部通过一个循环,实现不断重定向,可以看一下循环内主要做了什么:
- 进行一些预处理
- 调用
chain.proceed
方法进行请求获取Response
- 过程中若下层抛出异常,则尝试重定向
- 若不满足重定向条件,则抛出异常
- 若出现其他未知的异常,则通过抛出异常释放资源
- 在本次
Response
中设置上一次的 ResponsepriorResponse
,且body为空 - 根据
Response
中的 response code 进行重定向,调用followUpRequest
方法获取重定向后的 requestfollowUp
- 若重定向后的
followUp
为 null,说明不再需要重定向,停止 timeout 计时并返回Response
- 若重定向超过指定次数(默认 20 次),则抛出异常。
- 若仍未返回,则需要下一次重定向,对下一次的
request
等变量进行赋值。
让我们看看 followUpRequest
方法做了什么:
private Request followUpRequest(Response userResponse, @Nullable Route route) throws IOException {
if (userResponse == null) throw new IllegalStateException();
int responseCode = userResponse.code();
final String method = userResponse.request().method();
switch (responseCode) {
case HTTP_PROXY_AUTH: // 407
// ...
// 代理身份认证
case HTTP_UNAUTHORIZED: // 401
// ...
// 身份认证
case HTTP_PERM_REDIRECT: // 308
case HTTP_TEMP_REDIRECT: // 307
// 307、308 两种状态码不对 GET、HEAD 以外的请求重定向
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
case HTTP_MULT_CHOICE: // 300
case HTTP_MOVED_PERM: // 301
case HTTP_MOVED_TEMP: // 302
case HTTP_SEE_OTHER: // 303
// 若客户端关闭了重定向,则直接返回 null
if (!client.followRedirects()) return null;
// 获取LocationHeader以获取重定向目标
String location = userResponse.header("Location");
if (location == null) return null;
HttpUrl url = userResponse.request().url().resolve(location);
// ...
Request.Builder requestBuilder = userResponse.request().newBuilder();
// 处理重定向使用的method
if (HttpMethod.permitsRequestBody(method)) {
final boolean maintainBody = HttpMethod.redirectsWithBody(method);
if (HttpMethod.redirectsToGet(method)) {
requestBuilder.method("GET", null);
} else {
RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
requestBuilder.method(method, requestBody);
}
if (!maintainBody) {
requestBuilder.removeHeader("Transfer-Encoding");
requestBuilder.removeHeader("Content-Length");
requestBuilder.removeHeader("Content-Type");
}
}
// 重新构建request
return requestBuilder.url(url).build();
case HTTP_CLIENT_TIMEOUT: // 408
// 408 说明需要重新发送一次相同的请求
// ...
return userResponse.request();
case HTTP_UNAVAILABLE: // 503
// ...
return null;
default:
return null;
}
}
可以看到,主要是针对重定向的几个状态码进行特殊处理,从中取出 Location
字段,构造重定向后的 request
。
BridgeInterceptor
BridgeInterceptor
的名字取的非常形象,它就像一座桥梁,连接了用户与服务器。在用户向服务器发送请求时,它会把用户所构建的请求转换为向服务器请求的真正的 Request
,而在服务器返回了响应后,它又会将服务器所返回的响应转换为用户所能够使用的 Response
。
让我们看到 BridgeInterceptor.intercept
方法:
@Override
public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
// 将一些userRequest中的属性设置进builder中
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");
}
boolean transparentGzip = false;
// 若未设置Accept-Encoding,自动设置gzip
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
// 将userRequest中的cookies设置进builder
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
// 设置user-agent
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
// 读取服务端响应
Response networkResponse = chain.proceed(requestBuilder.build());
// 对响应的header进行处理
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
// 根据服务端的响应构建新的Response,并将userRequest设置为其request
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
// 若之前设置了gzip压缩且response中也包含了gzip压缩,则进行gzip解压
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)));
}
return responseBuilder.build();
}
可以看到,这里主要对 Header 进行处理,将一些原来 request
中的 Header 进行处理后设置进了新 request
,并用其进行请求。其中若调用者未设置 Accept-Encoding
,则它会默认设置 gzip。
而在对 response
处理时,若之前设置了 gzip,则进行 gzip 解压。这种自动解压会自动将 Content-Length
、Content-Encoding
字段从 Header 中移除,因此上层可能会获取到 -1。
而这里关于 Cookie
的处理我们暂时不关心,后续文章中再对其作介绍。
CacheInterceptor
CacheInterceptor
主要负责了对缓存的读取以及更新,让我们看看其 intercept
方法:
@Override
public Response intercept(Chain chain) throws IOException {
// 尝试获取缓存的cache
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 传入当前时间、request以及从缓存中取出的cache,构建缓存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
// 通过缓存策略获取新的request
Request networkRequest = strategy.networkRequest;
// 通过缓存策略获取缓存中取出的response
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// 根据缓存策略若不能使用网络且没有缓存,则请求失败,构建一个请求失败的Response并返回
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 (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
// 网络请求获取response
networkResponse = chain.proceed(networkRequest);
} finally {
// 如果IO的过程中出现了crash,回收资源
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// 如果缓存中有缓存,并且请求的code为304,则结合缓存及网络请求结果后返回,并且更新缓存中的内容
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) { // 304
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 {
closeQuietly(cacheResponse.body());
}
}
// 构建response
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
// 对请求响应进行缓存
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
可以看到,这里主要是以下步骤
- 尝试从缓存中获取了缓存的
response
- 根据 当前时间、
request
、缓存的response
构建缓存策略。 - 若缓存策略不能使用网络(
networkRequest == null
),且无缓存(cacheResponse == null
),则直接请求失败。 - 若缓存策略不能使用网络,由于前面有判断所以可以确定有缓存,直接构建缓存的
response
并返回。 - 调用
chain.proceed
网络请求获取response
- 对 code 304 作出处理,结合本地及网络返回数据构建
response
并返回 - 构建网络请求的所获得的
response
,并且由于该网络请求并未进行过缓存,进行缓存并返回结果
而关于缓存相关的具体实现这里先不过多做介绍,后面会专门开一篇文章进行分析,这里主要以流程为主。
ConnectInterceptor
ConnectInterceptor
主要负责的是与服务器的连接的建立,它的代码非常短:
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
Transmitter transmitter = realChain.transmitter();
boolean doExtensiveHealthChecks = !request.method().equals("GET");
// 构建Exchange
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
return realChain.proceed(request, transmitter, exchange);
}
这里主要是调用 transmitter.newExchange
构建一个 Exchange
,之后调用了 realChain.proceed(request, transmitter, exchange)
方法。
这个 Exchange
类究竟是什么呢?我们看到它的 JavaDoc:
Transmits a single HTTP request and a response pair. This layers connection management and events on {@link ExchangeCodec}, which handles the actual I/O.
也就是说 Exchange
类可以将 ExchangeCodec
这个类的连接管理及事件进行分层,而 ExchangeCodec
是一个真正执行 I/O 的类,看来这个类主要是进行一些连接管理的事务。在 newExchange
的过程中可能就创建/复用了客户与服务器的连接。
这里具体的连接获取过程我们暂时先不做介绍,在后续文章中会详细进行介绍,此篇文章更偏向整体流程的讲解。
CallServerInterceptor
CallServerInterceptor
是整个网络请求链的最后一个拦截器,它真正实现了对服务器 Response
的读取,让我们看看它的实现:
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Exchange exchange = realChain.exchange();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
// 写入请求头
exchange.writeRequestHeaders(request);
boolean responseHeadersStarted = false;
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// 对 100-continue 这一 header 做特殊处理
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
exchange.flushRequest();
responseHeadersStarted = true;
exchange.responseHeadersStart();
responseBuilder = exchange.readResponseHeaders(true);
}
if (responseBuilder == null) {
// 写入请求体
if (request.body().isDuplex()) {
// Prepare a duplex body so that the application can send a request body later.
exchange.flushRequest();
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, true));
request.body().writeTo(bufferedRequestBody);
} else {
// Write the request body if the "Expect: 100-continue" expectation was met.
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, false));
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
} else {
exchange.noRequestBody();
if (!exchange.connection().isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
// from being reused. Otherwise we're still obligated to transmit the request body to
// leave the connection in a consistent state.
exchange.noNewExchangesOnConnection();
}
}
} else {
exchange.noRequestBody();
}
if (request.body() == null || !request.body().isDuplex()) {
exchange.finishRequest();
}
if (!responseHeadersStarted) {
exchange.responseHeadersStart();
}
if (responseBuilder == null) {
// 读取响应头
responseBuilder = exchange.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (code == 100) {
// server sent a 100-continue even though we did not request one.
// try again to read the actual response
// 读取响应头
response = exchange.readResponseHeaders(false)
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
exchange.responseHeadersEnd(response);
// 读取响应体
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(exchange.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
exchange.noNewExchangesOnConnection();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
这里代码量非常多,但其实核心是下面几步:
- 写入
Request Header
- 写入
Request Body
- 读取
Response Header
- 读取
Response Body
其具体实现我们后续文章再进行介绍,到了这里整个责任链的大体流程我们就分析完了。