RetryAndFollowUpInterceptor过滤器
在上一篇笔记中梳理了OkHttp源码的大体执行流程,在HTTP请求中主要就是包含请求体的设置,请求数据的回调,请求数据的解析,线程的切换这些内容,其中主要分析了执行流程,包括:
- 创建
OkHttpClient对象 - 创建
Request对象 - 创建请求结果的回调,即
Callback对象 - 向
OkHttpClient配置请求体和请求回调 - 开启异步线程执行任务。
同时,通过上一篇笔记也知道了,当请求数据的任务提交之后,主要执行的操作是在各个Interceptor中,通过递归执行各个Interceptor,最终返回我们需要的Response对象,这篇笔记主要分析RetryAndFollowUpInterceptor过滤器,主要参考的文章是okhttp源码分析(二)-RetryAndFollowUpInterceptor过滤器
在上一篇笔记的最后,通过创建RealInterceptorChain对象,执行其proceed()方法,在这个方法中取出RetryAndFollowUpInterceptor对象,执行其中的intercept(Chain)方法,下面是这个方法的源码:
@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);
this.streamAllocation = streamAllocation;
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
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(), streamAllocation, false, request)) {
throw e.getFirstConnectException();
}
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, streamAllocation, 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;
try {
followUp = followUpRequest(response, streamAllocation.route());
} catch (IOException e) {
streamAllocation.release();
throw e;
}
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
closeQuietly(response.body());
if (++followUpCount > MAX_FOLLOW_UPS) {
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);
this.streamAllocation = streamAllocation;
} 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;
}
}
可以看到,这个方法的源码是比较长的。首先会获取到RealInterceptorChain中保存的Request对象,这个参数是在创建RealInterceptorChain的时候传递进去的,就是之前构造的Request对象。
接着将传递进来的Chain对象强制转换为RealInterceptorChain对象,因为在创建Chain对象的时候就直接创建的RealInterceptorChain对象,所以这里是没问题的。
//在AsyncCall中创建的Chain对象:
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
接着获取Call对象,仍然是之前设置的RealCall对象,从上面的源码也可以看出来,传递Call对象的时候是将this传递进去了。
接着会获取事件监听,EventListener是在创建RealCall的时候创建的。下面就是获取相关对象的源码:
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
获取完相关对象之后,接下来会创建StreamAllocation对象(这个对象是用来协调Connections,Streams,Calls三者之间的关系的):
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
在创建完相关的对象之后,接着进入到一个死循环while(true),在循环体中,首先判断当前的请求是否已经取消,如果取消了则直接通过抛出异常的方式结束循环:
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
如果请求没有取消,则通过realChain.proceed(params)方法执行下一个Interceptor,同时将releaseConnection变量设置为false(这个变量默认为true)。同时会尝试捕获RouteException,如果发现了异常,则会调用recover()方法,下面是这个方法的源码:
private boolean recover(IOException e, StreamAllocation streamAllocation,
boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure()) return false;
// We can't send the request body again.
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
// This exception is fatal.
if (!isRecoverable(e, requestSendStarted)) return false;
// No more routes to attempt.
if (!streamAllocation.hasMoreRoutes()) return false;
// For failure recovery, use the same route selector with a new connection.
return true;
}
在recover方法中,进行如下的判断:
- 判断如果在
OkHttpClient中设置了在连接失败的时候不进行重试,则直接返回false,通过查看OkHttpClient中的源码可以发现,这个参数的默认值是true. - 接着判断如果当前请求已经发送并且当前请求的请求体是
UnreapeatableRequestBody(不可重复的请求体),则返回false,但是由于UnrepeatableRequestBody是一个接口,并且并没有找到实现这个接口的类,因此这里应该默认为是不成立. - 接着执行
isRecoverable()方法,在这个方法中会分别判断当前的异常是否属于ProtocolException(协议异常),InterruptedIOException(IO中断异常),SSLHandshakeException并且是CertificateException(SSL协议证书异常),SSLPeerUnverifiedException(SSLPeer未验证异常),如果属于这些异常,则返回false. - 接着判断是否还有更多的路由可以试,如果没有,返回
false. - 其它情况返回
true.
经过上面的判断,如果返回false,表示不可以进行恢复,此时会抛出异常,然后将releaseConnection设置为false,同时执行continue语句,执行下一次循环。需要注意的是这里还有finally语句块,当然之前还会进行catch IOException的判断,但是和之前的异常捕获是相同的。下面是finally语句块中的内容:
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
在finally块中的注释可以知道,如果在执行上面的语句的时候发现了一个未经捕获的异常,则会释放所有资源。调用streamFailed(null)方法。这是因为在finally块中会首先判断releaseConnection变量的值,这个值默认是true,当成功执行try中的语句或者捕获了相关异常的时候会被设置为false,只有抛出一个没有呗捕获的异常的时候才会是true。
成功执行上面的try语句块中的内容之后,接着会判断priorResponse是否为空,如果不为空,则会将之前的请求到的Response附加到当前请求到的Response中。源码如下:
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
接下来会创建一个跟进的Request也就是followup,通过followUpRequest()方法为这个变量赋值,源码如下:
private Request followUpRequest(Response userResponse, 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:
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:
return client.authenticator().authenticate(route, userResponse);
case HTTP_PERM_REDIRECT:
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:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
// Does the client allow redirects?
if (!client.followRedirects()) return null;
String location = userResponse.header("Location");
if (location == null) return null;
HttpUrl url = userResponse.request().url().resolve(location);
// 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;
}
if (retryAfter(userResponse, 0) > 0) {
return null;
}
return userResponse.request();
case HTTP_UNAVAILABLE:
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
// We attempted to retry and got another timeout. Give up.
return null;
}
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
// specifically received an instruction to retry without delay
return userResponse.request();
}
return null;
default:
return null;
}
}
在这个方法中,主要是根据不同的请求状态码设置不同的Request对象,默认是返回null。
获取到followUp实例之后会判断followUp实例是否为null,如果为null,则释放资源,返回当前获取到的Response。这里要和上面的priorResponse结合起来看,如果followUp不是null,则可以认定是请求状态码不是200,出现了某些问题,开始执行后面的代码,在最后会将当次请求到的Response设置给priorResponse,然后开始下一次循环请求。这样也就解释了为什么需要判断priorResponse的情况,同时也解释了为什么会将priorResponse的body设置为null。
在此基础上,我们可以深入到Response.Builder的priorResponse()方法里面去看:
public Builder priorResponse(@Nullable Response priorResponse) {
if (priorResponse != null) checkPriorResponse(priorResponse);
this.priorResponse = priorResponse;
return this;
}
private void checkPriorResponse(Response response) {
if (response.body != null) {
throw new IllegalArgumentException("priorResponse.body != null");
}
}
可以看到,在给当前Response设置priorResponse的时候,如果priorResponse的body不为空则会抛出异常。
继续分析如果followUp不为空的时候,首先会执行closeQuietly(response.body())关闭当前连接,源码如下:
//Utils类中的closeQuietly方法:
public static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
这里传递参数的时候使用的是response.body,也就是ResponseBody类的实例,这个类实现了Closeable接口,也就是这里会执行ResponseBody的close()方法,源码如下:
private final BufferedSource source;
private Reader delegate;
@Override public void close() throws IOException {
closed = true;
if (delegate != null) {
delegate.close();
} else {
source.close();
}
}
接着会判断当前请求次数是否大于最大允许的请求次数(20),如果超过了,则释放资源并抛出异常:
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
接着判断当前请求体的body是否是UnrepeatableRequestBody,如果是,同样释放资源,抛出异常:
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
接着判断response的连接和followUp的连接是否相同(response是通过执行chain.proceed()获取到的,followUp是通过判断response中的statusCode获取到的)。如果不相同,则会通过followUp.url创建新的StreamAllowcation对象。
如果相同,但是streamAllocation.codec() != null则会抛出异常,因为在RetryAndFollowUpInterceptor中并没有设置StreamAllocation中的codec属性的值,所以这里如果出现codec != null则说明当前的interceptor有问题,从异常信息也可以推断这一点:
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
最后将request和priorResponse分别进行赋值:
request = followUp;
priorResponse = response;
整个执行流程如下: