1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
| public final class RetryAndFollowUpInterceptor implements Interceptor {
private static final int MAX_FOLLOW_UPS = 20;
private final OkHttpClient client;
private final boolean forWebSocket;
private volatile StreamAllocation streamAllocation;
private Object callStackTrace;
private volatile boolean canceled;
public RetryAndFollowUpInterceptor(OkHttpClient client, boolean forWebSocket) {
this.client = client;
this.forWebSocket = forWebSocket;
}
// 取消,不需要看了
public void cancel() {
canceled = true;
StreamAllocation streamAllocation = this.streamAllocation;
if (streamAllocation != null) streamAllocation.cancel();
}
@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
Response priorResponse = null;
// 又是个永动机
while (true) {
Response response;
boolean releaseConnection = true;
try {
// 调用下一个拦截器,最终获取到response
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
//发现异常就重试
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
//发现异常就重试
...
continue;
} finally {
// 释放连接
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// 附加前一个response,这种都是没有body
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)//没有body
.build())
.build();
}
// 根据上一个response,生成重定向需要的request
Request followUp = followUpRequest(response, streamAllocation.route());
// 如果不再需要重定向,就直接返回response了
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
// 关闭 response的body
closeQuietly(response.body());
// 如果重定向/重试次数大于20次就会报错
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
...
// 如果不是同一个主机地址,则连接不能复用,只能释放上一个,并且新建一个连接;否则将复用之前创建好的连接
if (!sameConnection(response, followUp.url())) {
// 用request重建连接
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
}
request = followUp;
priorResponse = response;
}
}
// 拼装地址,包含主机、端口号、DNS等等
private Address createAddress(HttpUrl url) {
SSLSocketFactory sslSocketFactory = null;
HostnameVerifier hostnameVerifier = null;
CertificatePinner certificatePinner = null;
// 判断是否https
if (url.isHttps()) {
sslSocketFactory = client.sslSocketFactory();
hostnameVerifier = client.hostnameVerifier();
certificatePinner = client.certificatePinner();
}
return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
}
// 尝试从与服务器通信失败中恢复(重试)
private boolean recover(IOException e, StreamAllocation streamAllocation,
boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
// 重试
// return false - 应用层拒绝了重试,这是我们在初始化client时设置的
if (!client.retryOnConnectionFailure()) return false;
...
// 如果有更多的路由,比如DNS返回了多个CDN的ip地址,就切换线路重试,否则取消。
if (!streamAllocation.hasMoreRoutes()) return false;
...
return true;
}
// 判断连接是否可以恢复
private boolean isRecoverable(IOException e, boolean requestSendStarted) {
...
}
// 根据responseCode判断,如果response需要重定向,则创建一个重定向所需的request
private Request followUpRequest(Response userResponse, Route route) throws IOException {
int responseCode = userResponse.code();
final String method = userResponse.request().method();
switch (responseCode) {
...
case HTTP_UNAUTHORIZED:// OAuth2.0 授权码方式要求重定向到一个授权页面
return client.authenticator().authenticate(route, userResponse);
...
// 响应码是3xx系列就需要重定向处理
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
// 客户端允许重定向吗?
if (!client.followRedirects()) return null;
// 原来response的head中有这么个东东
String location = userResponse.header("Location");
if (location == null) return null;
// 根据location生成重定向的url
// 这个resolve方法是真复杂,懒得看了
HttpUrl url = userResponse.request().url().resolve(location);
// 不会重定向到不支持的协议
if (url == null) return null;
// 不会在http和https之间重定向
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
if (!sameScheme && !client.followSslRedirects()) return null;
// 大多数的重定向都不需要requestBody
// 其实这里就是依据条件来分别调用建造者模式,最终会构建一个符合条件的request
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");
}
}
// 如果重定向的不是之前的连接,就移除OAuth-token,安全性考量
if (!sameConnection(userResponse, url)) {
requestBuilder.removeHeader("Authorization");
}
// 完活返回
return requestBuilder.url(url).build();
case HTTP_CLIENT_TIMEOUT:
case HTTP_UNAVAILABLE:
// 如果客户端超时,会采用各种方式重试,重试失败会返回null
...
return userResponse.request();
...
return null;
default:
return null;
}
}
private int retryAfter(Response userResponse, int defaultDelay) {
String header = userResponse.header("Retry-After");
if (header == null) {
return defaultDelay;
}
// https://tools.ietf.org/html/rfc7231#section-7.1.3
// currently ignores a HTTP-date, and assumes any non int 0 is a delay
if (header.matches("\\d+")) {
return Integer.valueOf(header);
}
return Integer.MAX_VALUE;
}
/**
* Returns true if an HTTP request for {@code followUp} can reuse the connection used by this
* engine.
*/
private boolean sameConnection(Response response, HttpUrl followUp) {
HttpUrl url = response.request().url();
return url.host().equals(followUp.host())
&& url.port() == followUp.port()
&& url.scheme().equals(followUp.scheme());
}
}
|