Okhttp3源码解析(5)-拦截器RetryAndFollowUpInterceptor
程序员文章站
2022-04-15 15:44:28
### 前言 回顾: [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析](https://www.jianshu.com/p/bf1d01b79ce7) [Okhttp3源码解析( ......
### 前言
回顾:
[okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f)
[okhttp3源码解析(1)-okhttpclient分析](https://www.jianshu.com/p/bf1d01b79ce7)
[okhttp3源码解析(2)-request分析](https://www.jianshu.com/p/5a85345c8ea7)
[okhttp3源码解析(3)-call分析(整体流程)](https://www.jianshu.com/p/4ed79472797a)
[okhttp3源码解析(4)-拦截器与设计模式](https://www.jianshu.com/p/b8817597f269)
上节讲了拦截器与设计模式,今天讲`retryandfollowupinterceptor`,如果我们没有去自定义拦截器, 那`retryandfollowupinterceptor`是第一个拦截器。
### 初始化
首先先看`retryandfollowupinterceptor`被添加的位置:
![](https://img2018.cnblogs.com/blog/1312938/201908/1312938-20190829084744888-554996670.png)
初始化位置:
call实例化方法中:
```
private realcall(okhttpclient client, request originalrequest, boolean forwebsocket) {
this.client = client;
this.originalrequest = originalrequest;
this.forwebsocket = forwebsocket;
this.retryandfollowupinterceptor = new retryandfollowupinterceptor(client, forwebsocket);
}
```
找到了初始化的位置, 下面去`retryandfollowupinterceptor`种分析!
### retryandfollowupinterceptor解析
从上节我们就知道拦截器中的**intercept()是核心!** 这里贴出代码:
```
@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;
}
}
```
我先贴出一个`while循环`的流程图:
![](https://img2018.cnblogs.com/blog/1312938/201908/1312938-20190829084745296-805720763.png)
根据流程图和源码可以分析`retryandfollowupinterceptor`主要做了以下内容,**后两点都是发生在`while循环`中**:
- **初始化streamallocation 对象**
- **网络请求-chain.proceed() ,对在请求时发生的异常进行捕获以及对应的重连机制**
- **followuprequest 对响应码进行处理**
下面可以逐块代码分析:
###### 1.初始化streamallocation 对象
`streamallocation`类是**协调三个实体之间的关系** 三个实体是:`connections`,`streams`,`calls`
我们请求网络时需要传递它
![](https://img2018.cnblogs.com/blog/1312938/201908/1312938-20190829084745679-501952792.png)
`streamallocation`在这大家简单了解一下就可以了.
###### 2.网络请求时异常捕获-以及重连机制
网络请求如下:
```
response = realchain.proceed(request, streamallocation, null, null);
```
如果请求发现异常,我们通过try/catch捕获
```
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;
}
```
-`routeexception` 路由异常
- `ioexception` io异常
捕获后都做了`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;
}
```
这里需要注意的是如果可以重连,执行 continue;
`continue`含义: 继续循环,(不执行 循环体内`continue` 后面的语句,直接进行下一循环)
###### 3.` followuprequest` 对响应码进行处理
先看看具体`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;
}
}
```
不难看出, 是根据响应码进行判断的。
- http_proxy_auth 407 代理身份验证
- http_unauthorized 401 未授权
- http_perm_redirect 308 重定向
- http_temp_redirect 307 重定向
- http_mult_choice 300 multiple choices
- http_moved_perm 301 moved permanently
- http_moved_temp 302 temporary redirect
- http_see_other 303 see other
- http_client_timeout 408 request time-out
- http_unavailable 503 service unavailable
对于这些响应码都做了处理:
1.返回null
```
if (followup == null) {
if (!forwebsocket) {
streamallocation.release();
}
return response;
}
```
2.其他异常情况直接抛异常了
强调:
`max_follow_ups`字段, 表示最大的重定向次数
```
/**
* how many redirects and auth challenges should we attempt? chrome follows 21 redirects; firefox,
* curl, and wget follow 20; safari follows 16; and http/1.0 recommends 5.
*/
private static final int max_follow_ups = 20;
```
```
if (++followupcount > max_follow_ups) {
streamallocation.release();
throw new protocolexception("too many follow-up requests: " + followupcount);
}
```
这节就说到这,希望对大家有所帮助.....
![](https://img2018.cnblogs.com/blog/1312938/201908/1312938-20190829084746645-654895211.png)
大家可以关注我的微信公众号:「秦子帅」一个有质量、有态度的公众号!
![公众号](https://img2018.cnblogs.com/blog/1312938/201908/1312938-20190829084746832-730035128.jpg)
下一篇: 新手必读:零基础Python学习步骤安排
推荐阅读
-
spring5 源码深度解析----- 被面试官给虐懵了,竟然是因为我不懂@Configuration配置类及@Bean的原理
-
axios 源码解析(下) 拦截器的详解
-
spring5 源码深度解析-----ApplicationContext容器refresh过程
-
Okhttp3源码解析(4)-拦截器与设计模式
-
Spring5源码解析6-ConfigurationClassParser 解析配置类
-
Okhttp3源码解析(2)-Request分析
-
Okhttp3源码解析(3)-Call分析(整体流程)
-
Okhttp3源码解析(1)-OkHttpClient分析
-
Java进阶篇5_SpringMVC的简介、SpringMVC的组件解析、SpringMVC的数据响应、SpringMVC获取请求数据、SpringMVC拦截器、SpringMVC异常处理机制
-
Okhttp3源码解析(5)-拦截器RetryAndFollowUpInterceptor