classRetryAndFollowUpInterceptor(privateval client: OkHttpClient) : Interceptor { //... @Throws(IOException::class) overridefunintercept(chain: Interceptor.Chain): Response { var request = chain.request() val realChain = chain as RealInterceptorChain val transmitter = realChain.transmitter() var followUpCount = 0 var priorResponse: Response? = null while (true) { // 为 request 准备 stream transmitter.prepareToConnect(request)
if (transmitter.isCanceled) { throw IOException("Canceled") }
var response: Response var success = false try { // 丢给下一个拦截器 response = realChain.proceed(request, transmitter, null) success = true } catch (e: RouteException) { // 路由异常 RouteException // The attempt to connect via a route failed. The request will not have been sent. if (!recover(e.lastConnectException, transmitter, false, request)) { // 检测路由异常是否能重新连接 throw e.firstConnectException } continue// 可以重新连接,重新走 while 循环 } catch (e: IOException) { // 检测该IO异常是否能重新连接 // An attempt to communicate with a server failed. The request may have been sent. val requestSendStarted = e !is ConnectionShutdownException if (!recover(e, transmitter, requestSendStarted, request)) throw e continue } finally { // The network call threw an exception. Release any resources. if (!success) { transmitter.exchangeDoneDueToException() // 释放 } }
// priorResponse 是用来保存前一个 Resposne 的,当发现需要重定向,则将当前 Resposne 设置给priorResponse,再执行一遍流程,这里可以看到将前一个 Response 和当前的 Resposne 结合在一起了,直到不需要重定向了,则将 priorResponse 和 Resposne 结合起来。 // 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() }
val exchange = response.exchange val route = exchange?.connection()?.route() val followUp = followUpRequest(response, route) // 判断是否需要重定向,如果需要重定向则返回一个重定向的 Request,没有则为 null
classRetryAndFollowUpInterceptor(privateval client: OkHttpClient) : Interceptor { //... /** * Report and attempt to recover from a failure to communicate with a server. Returns true if * `e` is recoverable, or false if the failure is permanent. Requests with a body can only * be recovered if the body is buffered or if the failure occurred before the request has been * sent. */ privatefunrecover( e: IOException, transmitter: Transmitter, requestSendStarted: Boolean, userRequest: Request ): Boolean { // 判断 OkHttpClient 是否支持失败重连的机制 // The application layer has forbidden retries. if (!client.retryOnConnectionFailure()) returnfalse
// 与之前版本的 UnrepeatableRequestBody 类似,只能请求一次的请求 // We can't send the request body again. if (requestSendStarted && requestIsOneShot(e, userRequest)) returnfalse
// 检测该异常是否是致命的 // This exception is fatal. if (!isRecoverable(e, requestSendStarted)) returnfalse
// 是否有更多的路线 // No more routes to attempt. if (!transmitter.canRetry()) returnfalse // For failure recovery, use the same route selector with a new connection. returntrue } privatefunrequestIsOneShot(e: IOException, userRequest: Request): Boolean { val requestBody = userRequest.body return (requestBody != null && requestBody.isOneShot()) || e is FileNotFoundException } // ... }
classRetryAndFollowUpInterceptor(privateval client: OkHttpClient) : Interceptor { //... privatefunisRecoverable(e: IOException, requestSendStarted: Boolean): Boolean { // ProtocolException 属于严重异常,不能进行重新连接 // If there was a protocol problem, don't recover. if (e is ProtocolException) { returnfalse }
// 当异常为中断异常时 // 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 is InterruptedIOException) { return e is 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 is SSLHandshakeException) { // If the problem was a CertificateException from the X509TrustManager, // do not retry. if (e.cause is CertificateException) { returnfalse } } // 验证异常 if (e is SSLPeerUnverifiedException) { // e.g. a certificate pinning error. returnfalse } // 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. returntrue } // ... }
classExchangeFinder( privateval transmitter: Transmitter, privateval connectionPool: RealConnectionPool, privateval address: Address, privateval call: Call, privateval eventListener: EventListener ) { // ... /** Returns true if a current route is still good or if there are routes we haven't tried yet. */ funhasRouteToTry(): Boolean { synchronized(connectionPool) { if (nextRouteToTry != null) { returntrue } if (retryCurrentRoute()) { // Lock in the route because retryCurrentRoute() is racy and we don't want to call it twice. nextRouteToTry = transmitter.connection!!.route() returntrue } return (routeSelection?.hasNext() ?: false) || routeSelector.hasNext() } }
/** * Return true if the route used for the current connection should be retried, even if the * connection itself is unhealthy. The biggest gotcha here is that we shouldn't reuse routes from * coalesced connections. */ privatefunretryCurrentRoute(): Boolean { return transmitter.connection != null && transmitter.connection!!.routeFailureCount == 0 && transmitter.connection!!.route().address().url.canReuseConnectionFor(address.url) } // ... }
classRetryAndFollowUpInterceptor(privateval client: OkHttpClient) : Interceptor { //... /** * Figures out the HTTP request to make in response to receiving `userResponse`. This will * either add authentication headers, follow redirects or handle a client request timeout. If a * follow-up is either unnecessary or not applicable, this returns null. */ @Throws(IOException::class) privatefunfollowUpRequest(userResponse: Response, route: Route?): Request? { val responseCode = userResponse.code()
val method = userResponse.request().method when (responseCode) { // 407 HTTP_PROXY_AUTH -> { val selectedProxy = route!!.proxy() if (selectedProxy.type() != Proxy.Type.HTTP) { throw ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy") } return client.proxyAuthenticator().authenticate(route, userResponse) }
// 308, 307 HTTP_PERM_REDIRECT, 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 != "GET" && method != "HEAD") { returnnull } return buildRedirectRequest(userResponse, method) }
// 408 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. returnnull }
val requestBody = userResponse.request().body if (requestBody != null && requestBody.isOneShot()) { returnnull } val priorResponse = userResponse.priorResponse() if (priorResponse != null && priorResponse.code() == HTTP_CLIENT_TIMEOUT) { // We attempted to retry and got another timeout. Give up. returnnull }
if (retryAfter(userResponse, 0) > 0) { returnnull }
return userResponse.request() }
// 503 HTTP_UNAVAILABLE -> { val priorResponse = userResponse.priorResponse() if (priorResponse != null && priorResponse.code() == HTTP_UNAVAILABLE) { // We attempted to retry and got another timeout. Give up. returnnull }
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) { // specifically received an instruction to retry without delay return userResponse.request() }
returnnull } else -> returnnull } } // ... }
当返回码满足某些条件时就重新构造一个 Request,不满足就返回 null
1 2 3 4 5 6
if (followUp == null) { if (exchange != null && exchange.isDuplex) { transmitter.timeoutEarlyExit() } return response }