1
16 package okhttp3.internal.http;
17
18 import java.io.FileNotFoundException;
19 import java.io.IOException;
20 import java.io.InterruptedIOException;
21 import java.net.ProtocolException;
22 import java.net.Proxy;
23 import java.net.SocketTimeoutException;
24 import java.security.cert.CertificateException;
25 import javax.annotation.Nullable;
26 import javax.net.ssl.SSLHandshakeException;
27 import javax.net.ssl.SSLPeerUnverifiedException;
28 import okhttp3.HttpUrl;
29 import okhttp3.Interceptor;
30 import okhttp3.OkHttpClient;
31 import okhttp3.Request;
32 import okhttp3.RequestBody;
33 import okhttp3.Response;
34 import okhttp3.Route;
35 import okhttp3.internal.Internal;
36 import okhttp3.internal.connection.Exchange;
37 import okhttp3.internal.connection.RouteException;
38 import okhttp3.internal.connection.Transmitter;
39 import okhttp3.internal.http2.ConnectionShutdownException;
40
41 import static java.net.HttpURLConnection.HTTP_CLIENT_TIMEOUT;
42 import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
43 import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
44 import static java.net.HttpURLConnection.HTTP_MULT_CHOICE;
45 import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
46 import static java.net.HttpURLConnection.HTTP_SEE_OTHER;
47 import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
48 import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;
49 import static okhttp3.internal.Util.closeQuietly;
50 import static okhttp3.internal.Util.sameConnection;
51 import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT;
52 import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT;
53
54
58 public final class RetryAndFollowUpInterceptor implements Interceptor {
59
63 private static final int MAX_FOLLOW_UPS = 20;
64
65 private final OkHttpClient client;
66
67 public RetryAndFollowUpInterceptor(OkHttpClient client) {
68 this.client = client;
69 }
70
71 @Override public Response intercept(Chain chain) throws IOException {
72 Request request = chain.request();
73 RealInterceptorChain realChain = (RealInterceptorChain) chain;
74 Transmitter transmitter = realChain.transmitter();
75
76 int followUpCount = 0;
77 Response priorResponse = null;
78 while (true) {
79 transmitter.prepareToConnect(request);
80
81 if (transmitter.isCanceled()) {
82 throw new IOException("Canceled");
83 }
84
85 Response response;
86 boolean success = false;
87 try {
88 response = realChain.proceed(request, transmitter, null);
89 success = true;
90 } catch (RouteException e) {
91
92 if (!recover(e.getLastConnectException(), transmitter, false, request)) {
93 throw e.getFirstConnectException();
94 }
95 continue;
96 } catch (IOException e) {
97
98 boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
99 if (!recover(e, transmitter, requestSendStarted, request)) throw e;
100 continue;
101 } finally {
102
103 if (!success) {
104 transmitter.exchangeDoneDueToException();
105 }
106 }
107
108
109 if (priorResponse != null) {
110 response = response.newBuilder()
111 .priorResponse(priorResponse.newBuilder()
112 .body(null)
113 .build())
114 .build();
115 }
116
117 Exchange exchange = Internal.instance.exchange(response);
118 Route route = exchange != null ? exchange.connection().route() : null;
119 Request followUp = followUpRequest(response, route);
120
121 if (followUp == null) {
122 if (exchange != null && exchange.isDuplex()) {
123 transmitter.timeoutEarlyExit();
124 }
125 return response;
126 }
127
128 RequestBody followUpBody = followUp.body();
129 if (followUpBody != null && followUpBody.isOneShot()) {
130 return response;
131 }
132
133 closeQuietly(response.body());
134 if (transmitter.hasExchange()) {
135 exchange.detachWithViolence();
136 }
137
138 if (++followUpCount > MAX_FOLLOW_UPS) {
139 throw new ProtocolException("Too many follow-up requests: " + followUpCount);
140 }
141
142 request = followUp;
143 priorResponse = response;
144 }
145 }
146
147
153 private boolean recover(IOException e, Transmitter transmitter,
154 boolean requestSendStarted, Request userRequest) {
155
156 if (!client.retryOnConnectionFailure()) return false;
157
158
159 if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;
160
161
162 if (!isRecoverable(e, requestSendStarted)) return false;
163
164
165 if (!transmitter.canRetry()) return false;
166
167
168 return true;
169 }
170
171 private boolean requestIsOneShot(IOException e, Request userRequest) {
172 RequestBody requestBody = userRequest.body();
173 return (requestBody != null && requestBody.isOneShot())
174 || e instanceof FileNotFoundException;
175 }
176
177 private boolean isRecoverable(IOException e, boolean requestSendStarted) {
178
179 if (e instanceof ProtocolException) {
180 return false;
181 }
182
183
184
185 if (e instanceof InterruptedIOException) {
186 return e instanceof SocketTimeoutException && !requestSendStarted;
187 }
188
189
190
191 if (e instanceof SSLHandshakeException) {
192
193
194 if (e.getCause() instanceof CertificateException) {
195 return false;
196 }
197 }
198 if (e instanceof SSLPeerUnverifiedException) {
199
200 return false;
201 }
202
203
204
205
206 return true;
207 }
208
209
214 private Request followUpRequest(Response userResponse, @Nullable Route route) throws IOException {
215 if (userResponse == null) throw new IllegalStateException();
216 int responseCode = userResponse.code();
217
218 final String method = userResponse.request().method();
219 switch (responseCode) {
220 case HTTP_PROXY_AUTH:
221 Proxy selectedProxy = route != null
222 ? route.proxy()
223 : client.proxy();
224 if (selectedProxy.type() != Proxy.Type.HTTP) {
225 throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
226 }
227 return client.proxyAuthenticator().authenticate(route, userResponse);
228
229 case HTTP_UNAUTHORIZED:
230 return client.authenticator().authenticate(route, userResponse);
231
232 case HTTP_PERM_REDIRECT:
233 case HTTP_TEMP_REDIRECT:
234
235
236 if (!method.equals("GET") && !method.equals("HEAD")) {
237 return null;
238 }
239
240 case HTTP_MULT_CHOICE:
241 case HTTP_MOVED_PERM:
242 case HTTP_MOVED_TEMP:
243 case HTTP_SEE_OTHER:
244
245 if (!client.followRedirects()) return null;
246
247 String location = userResponse.header("Location");
248 if (location == null) return null;
249 HttpUrl url = userResponse.request().url().resolve(location);
250
251
252 if (url == null) return null;
253
254
255 boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
256 if (!sameScheme && !client.followSslRedirects()) return null;
257
258
259 Request.Builder requestBuilder = userResponse.request().newBuilder();
260 if (HttpMethod.permitsRequestBody(method)) {
261 final boolean maintainBody = HttpMethod.redirectsWithBody(method);
262 if (HttpMethod.redirectsToGet(method)) {
263 requestBuilder.method("GET", null);
264 } else {
265 RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
266 requestBuilder.method(method, requestBody);
267 }
268 if (!maintainBody) {
269 requestBuilder.removeHeader("Transfer-Encoding");
270 requestBuilder.removeHeader("Content-Length");
271 requestBuilder.removeHeader("Content-Type");
272 }
273 }
274
275
276
277
278 if (!sameConnection(userResponse.request().url(), url)) {
279 requestBuilder.removeHeader("Authorization");
280 }
281
282 return requestBuilder.url(url).build();
283
284 case HTTP_CLIENT_TIMEOUT:
285
286
287
288 if (!client.retryOnConnectionFailure()) {
289
290 return null;
291 }
292
293 RequestBody requestBody = userResponse.request().body();
294 if (requestBody != null && requestBody.isOneShot()) {
295 return null;
296 }
297
298 if (userResponse.priorResponse() != null
299 && userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
300
301 return null;
302 }
303
304 if (retryAfter(userResponse, 0) > 0) {
305 return null;
306 }
307
308 return userResponse.request();
309
310 case HTTP_UNAVAILABLE:
311 if (userResponse.priorResponse() != null
312 && userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
313
314 return null;
315 }
316
317 if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
318
319 return userResponse.request();
320 }
321
322 return null;
323
324 default:
325 return null;
326 }
327 }
328
329 private int retryAfter(Response userResponse, int defaultDelay) {
330 String header = userResponse.header("Retry-After");
331
332 if (header == null) {
333 return defaultDelay;
334 }
335
336
337
338 if (header.matches("\\d+")) {
339 return Integer.valueOf(header);
340 }
341
342 return Integer.MAX_VALUE;
343 }
344 }
345