// Attach the prior response if it exists. Such responses never have a body. //5. 根据上一个Response结果构建一个新的response对象,且这个对象的body为空 if (priorResponse != null) { response = response.newBuilder() .priorResponse(priorResponse.newBuilder() .body(null) .build()) .build(); } //6. 根据请求码创建一个新的请求,以供下一次重试请求使用 Exchange exchange = Internal.instance.exchange(response); Route route = exchange != null ? exchange.connection().route() : null; Request followUp = followUpRequest(response, route); //7. 如果第六步构建的出来的Request为空,则不再进行,直接返回Response if (followUp == null) { if (exchange != null && exchange.isDuplex()) { transmitter.timeoutEarlyExit(); } return response; } //8. 构建的Request对象出存在请求body且为一次性请求,则直接返回Response,也不进行重试。 RequestBody followUpBody = followUp.body(); if (followUpBody != null && followUpBody.isOneShot()) { return response; } closeQuietly(response.body()); if (transmitter.hasExchange()) { exchange.detachWithViolence(); } // 9. 判断当前重试次数是否已经到达最大次数(默认20),如果到达,则直接抛出异常 if (++followUpCount > MAX_FOLLOW_UPS) { throw new ProtocolException("Too many follow-up requests: " + followUpCount); } //10. 如果上述没有抛出异常或者中断循环,则进入while循环,开始下一次重试过程 request = followUp; priorResponse = response; } }
1. 首先尝试去创建一个请求流,准备链接;
2. 开始执行,进入后续拦截器,真正进行网络请求;
3. 发生RouteException:路由链接出现异常,且多次重试后没有一次正常;然后判断当前路由异常是否可恢复,不可恢复的时候抛出FirstConnectException,可恢复则进行充实,具体判断逻辑如下:
/** • 是否可恢复 */ private boolean recover(IOException e, Transmitter transmitter, boolean requestSendStarted, Request userRequest) { // The application layer has forbidden retries. if (!client.retryOnConnectionFailure()) return false; // We can’t send the request body again. if (requestSendStarted && requestIsOneShot(e, userRequest)) return false; // This exception is fatal. if (!isRecoverable(e, requestSendStarted)) return false; // No more routes to attempt. if (!transmitter.canRetry()) return false; // For failure recovery, use the same route selector with a new connection. return true; } private boolean requestIsOneShot(IOException e, Request userRequest) { RequestBody requestBody = userRequest.body(); return (requestBody != null && requestBody.isOneShot()) || e instanceof FileNotFoundException; } private boolean isRecoverable(IOException e, boolean requestSendStarted) { // If there was a protocol problem, don’t recover. if (e instanceof ProtocolException) { return false; } // If there was an interruption don’t recover, but if there was a timeout connecting to a route // we should try the next route (if there is one). if (e instanceof InterruptedIOException) { return e instanceof SocketTimeoutException && !requestSendStarted; } // Look for known client-side or negotiation errors that are unlikely to be fixed by trying // again with a different route. if (e instanceof SSLHandshakeException) { // If the problem was a CertificateException from the X509TrustManager, // do not retry. if (e.getCause() instanceof CertificateException) { return false; } } if (e instanceof SSLPeerUnverifiedException) { // e.g. a certificate pinning error. return false; } // An example of one we might want to retry with a different route is a problem connecting to a // proxy and would manifest as a standard IOException. Unless it is one we know we should not // retry, we return true and try a new route. return true; }
上述代码逻辑如下:
- 首先判断是否允许重试,根据我们创建请求的时候配置的重试开关,则不允许重试;
- 第二层判断如果请求已经开始,且当前请求最多只能被发送一次的情况下,则不允许重试;
- 判断当前请求是否可恢复的,以下异常场景不可恢复:
a. ProtocolException,协议异常
b. SocketTimeoutException,Socket链接超时且请求没有开始
c. SSLHandshakeException && CertificateException :
表示和服务端约定的安全级别不匹配异常,引起基本为证书引起的,这种链接是不可用的。
d. SSLPeerUnverifiedException
对等实体认证异常,也就是说对等个体没有被验证,类似没有证书,
或者在握手期间没有建立对等个体验证; - 判断是否存在其他可重试的路由,如果不存在,不允许重试;
- 不属于上述情况判断可以重试;
4. 发生IOException异常,是否可恢复判断逻辑参照上述RouteException判断逻辑;
5. 根据上一个Response结果构建一个新的response对象,且这个对象的body为空;
6. 根据请求码创建一个新的请求,以供下一次重试请求使用:会根据上一次的请求结果添加认证头信息,跟踪重定向或处理客户端请求超时等。
代码如下:
private Request followUpRequest(Response userResponse, @Nullable 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);