在上一篇 okhttp篇3:RealCall_yolan6824的博客-CSDN博客 中讲到RealCall无论是在execute还是enqueue方法中,都是通过getResponseWithInterceptorChain方法获取Request对应的Response的。而getResponseWithInterceptorChain这个方法,又是通过RealInterceptorChain这个类串起一系列拦截器顺序执行得到Response的。所以这一篇,先将RealInterceptorChain及第一个拦截器 RetryAndFollowUpInterceptor。
RealInterceptorChain
// RealInterceptorChain
public final class RealInterceptorChain implements Interceptor.Chain {
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request,
Call call,EventListener eventListener,
int connectTimeout, int readTimeout, int writeTimeout) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
this.call = call;
this.eventListener = eventListener;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}
@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
// httpCodec != null,代表已经有一个stream了
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
// 每一个proceed方法都会新创建一个RealInterceptorChain,对应一个Interceptor
// 简单的说,RealInterceptorChain就是负责携带下一个Interceptor要用的参数的
// index指向拦截器列表中当前拦截器的索引。
// 通过index+1更新调用interceptor.intercept方法
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
// 将next这个chain传给下一个interceptor
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
}
RealInterceptorChain最重要的就是proceed方法。如上面的注释所说,每调用一次chain.proceed方法,都会新创建一个RealInterceptorChain。
这些RealInterceptorChain共享一开始由RealCall传入的拦截器列表,但是index(对应拦截器的索引)会在每一次proceed方法中更新,实现顺序调用interceptor.proceed方法。
因此,客户端自己实现的拦截器,依然需要调用chain.proceed方法(否则剩下的拦截器不会执行),并且可以通过proceed方法,改变Request, StreamAllocation, HttpCodec , RealConnection这四个参数的值。
RetryAndFollowUpInterceptor
从RealCall的getResponseWithInterceptorChain,可以看出,如果用户没有自定义拦截器,那么RetryAndFollowUpInterceptor就是实际上第一个执行的拦截器。
intercept
// RetryAndFollowUpInterceptor
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
// 新创建StreamAllocation
streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
call, eventListener, callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
while (true) {// while循环进行request重试
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
// 通过proceed方法,传入新的参数给后面的拦截器使用。
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), false, request)) {// 路由失败
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Request followUp = followUpRequest(response);// 根据response构建下一个要请求的Request,一般用于重定向
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
closeQuietly(response.body());
if (++followUpCount > MAX_FOLLOW_UPS) {// 控制最多只能再次请求20次
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
request = followUp;
priorResponse = response;
}
}
followup在翻译中,代表的是跟进的意思,在这里可以理解成一个请求的后续请求,比如说,如果一个请求,因为url改变了,返回了302,那么这时候,可以通过再发送一个新的请求,带上新的url去请求Response。okhttp为我们把这一步给做了。
在上面的intercept方法中,新创建了一个StreamAllocation。这个StreamAllocation是负责协调Connection,Stream,Call三者的关系的。这个后面会详细讲。在这里可以理解成,它可以负责资源的释放。
总结一下intercept方法干的事情:
- 如果已经cancel,直接调用streamAllocation.release(释放相关资源)
- 调用chain.proceed获取response
- 如果获取到RouteException:路由异常 / IOException
- client.retryOnConnectionFailure() == false / streamAllocation.hasMoreRoutes() == false / ProtocolException
- 不重试
- socket超时会重试,SSLHandshakeException,只要不是证书错误,会重试
- client.retryOnConnectionFailure() == false / streamAllocation.hasMoreRoutes() == false / ProtocolException
- 如果获取到RouteException:路由异常 / IOException
- 根据response获取request(下面的followupRequest方法)
- 如果获取到的request为空-->除了认证错误,超时,重定向,其他情况返回的request都是空,这种情况会直接调用streamAllocation.release() 释放资源。
- 如果获取到的request不为空,证明需要重试。
- 重试的次数最多为20次。
也就是说,路由异常,认证错误,socket超时,重定向,RetryAndFollowUpInterceptor都会按一定的规则,为我们重新构造Request,重新请求。重试的次数最多是20次。
followUpRequest
private Request followUpRequest(Response userResponse) throws IOException {
if (userResponse == null) throw new IllegalStateException();
Connection connection = streamAllocation.connection();
Route route = connection != null
? connection.route()
: null;
int responseCode = userResponse.code();
final String method = userResponse.request().method();
switch (responseCode) {
case HTTP_PROXY_AUTH:// 407,代理验证,报告客户端需要使用代理服务器进行身份验证。
Proxy selectedProxy = route != null
? route.proxy()
: client.proxy();
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
}
return client.proxyAuthenticator().authenticate(route, userResponse);
case HTTP_UNAUTHORIZED:// 401,未授权,需要重新进行身份验证
return client.authenticator().authenticate(route, userResponse);
case HTTP_PERM_REDIRECT:// 307,308临时重定向,确保请求方法(get/post)和消息主体不会发生变化
case HTTP_TEMP_REDIRECT:
// "If the 307 or 308 status code is received in response to a request other than GET
// or HEAD, the user agent MUST NOT automatically redirect the request"
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
// fall-through
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:// 永久重定向,method(get/post)有可能会变化
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
// Does the client allow redirects?
if (!client.followRedirects()) return null;
String location = userResponse.header("Location");// 获取response中的新地址
if (location == null) return null;
HttpUrl url = userResponse.request().url().resolve(location);// 根据新地址,获取一个新的HttpUrl
// Don't follow redirects to unsupported protocols.
if (url == null) return null;
// If configured, don't follow redirects between SSL and non-SSL.
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
if (!sameScheme && !client.followSslRedirects()) return null;
// Most redirects don't include a request body.
Request.Builder requestBuilder = userResponse.request().newBuilder();
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");
}
}
// When redirecting across hosts, drop all authentication headers. This
// is potentially annoying to the application layer since they have no
// way to retain them.
if (!sameConnection(userResponse, url)) {
requestBuilder.removeHeader("Authorization");
}
return requestBuilder.url(url).build();
case HTTP_CLIENT_TIMEOUT:
// 408's are rare in practice, but some servers like HAProxy use this response code. The
// spec says that we may repeat the request without modifications. Modern browsers also
// repeat the request (even non-idempotent ones.)
if (!client.retryOnConnectionFailure()) {
// The application layer has directed us not to retry the request.
return null;
}
if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
return null;
}
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
// We attempted to retry and got another timeout. Give up.
return null;
}
return userResponse.request();
default:
return null;
}
}
followRequests方法,主要是根据第一次request返回的response,看看需不需要再次请求,主要分为以下情形:
- 407 --> 需要使用代理服务器重新进行身份认证
- 401-> 直接进行身份认证
- 307/308(method == "get"/"head"), 300/301/302/304 依赖client.followRedirects()
- 获取Response中的url,重新构建Request
- 408(请求超时) -- 依赖client.retryOnConnectionFailure()
- 原request重新请求
- 其他情况,都返回空
总结
RetryAndFollowUpInterceptor,跟名字一样,主要负责重试。
跟重试相关联的api(默认都是true,允许重试):
- client.followRedirects()
- client.retryOnConnectionFailure()
需要重试的情况一般包括:
- 路由异常
- 身份认证失败
- 重定向
- 请求超时
最多重试20次。重试的时候,如果发现已经cancel了,取消重试。
下一篇讲下一个拦截器。