Github: okhttp 分析版本: 930d4d0
This interceptor recovers from failures and follows redirects as necessary
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor { //... @Throws(IOException::class) override fun intercept(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 if (followUp == null) { // 不需要重定向 if (exchange != null && exchange.isDuplex) { transmitter.timeoutEarlyExit() } // 返回 response return response } // 需要重定向 val followUpBody = followUp.body if (followUpBody != null && followUpBody.isOneShot()) { return response } // 关闭响应流 response.body()?.closeQuietly() if (transmitter.hasExchange()) { exchange?.detachWithViolence() } // 重定向次数++,并且小于最大重定向次数MAX_FOLLOW_UPS(20) if (++followUpCount > MAX_FOLLOW_UPS) { throw ProtocolException("Too many follow-up requests: $followUpCount") } request = followUp priorResponse = response } } // ... }
RouteException
和 IOException
异常检测都会调用 recover()
方法进行判断重试
recover(e: IOException, transmitter: Transmitter, requestSendStarted: Boolean, userRequest: Request)
class RetryAndFollowUpInterceptor(private val 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. */ private fun recover( e: IOException, transmitter: Transmitter, requestSendStarted: Boolean, userRequest: Request ): Boolean { // 判断 OkHttpClient 是否支持失败重连的机制 // The application layer has forbidden retries. if (!client.retryOnConnectionFailure()) return false // 与之前版本的 UnrepeatableRequestBody 类似,只能请求一次的请求 // 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 fun requestIsOneShot(e: IOException, userRequest: Request): Boolean { val requestBody = userRequest.body return (requestBody != null && requestBody.isOneShot()) || e is FileNotFoundException } // ... }
requestSendStarted isRecoverable()
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor { //... private fun isRecoverable(e: IOException, requestSendStarted: Boolean): Boolean { // ProtocolException 属于严重异常,不能进行重新连接 // If there was a protocol problem, don't recover. if (e is 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 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) { return false } } // 验证异常 if (e is 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 } // ... }
class Transmitter( private val client: OkHttpClient, private val call: Call ) { // ... fun canRetry(): Boolean { return exchangeFinder!!.hasStreamFailure() && exchangeFinder!!.hasRouteToTry() } // ... }
进到 ExchangeFinder
中:
class ExchangeFinder( private val transmitter: Transmitter, private val connectionPool: RealConnectionPool, private val address: Address, private val call: Call, private val eventListener: EventListener ) { // ... /** Returns true if a current route is still good or if there are routes we haven't tried yet. */ fun hasRouteToTry(): Boolean { synchronized(connectionPool) { if (nextRouteToTry != null) { return true } if (retryCurrentRoute()) { // Lock in the route because retryCurrentRoute() is racy and we don't want to call it twice. nextRouteToTry = transmitter.connection!!.route() return true } 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. */ private fun retryCurrentRoute(): Boolean { return transmitter.connection != null && transmitter.connection!!.routeFailureCount == 0 && transmitter.connection!!.route().address().url.canReuseConnectionFor(address.url) } // ... }
找相同 address 的链接
class RetryAndFollowUpInterceptor(private val 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) private fun followUpRequest(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) } // 401 HTTP_UNAUTHORIZED -> return client.authenticator().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") { return null } return buildRedirectRequest(userResponse, method) } // 300, 301, 302, 303 HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> { 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. return null } val requestBody = userResponse.request().body if (requestBody != null && requestBody.isOneShot()) { return null } val priorResponse = userResponse.priorResponse() if (priorResponse != null && 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() } // 503 HTTP_UNAVAILABLE -> { val priorResponse = userResponse.priorResponse() if (priorResponse != null && 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 } else -> return null } } // ... }
当返回码满足某些条件时就重新构造一个 Request,不满足就返回 null
if (followUp == null) { if (exchange != null && exchange.isDuplex) { transmitter.timeoutEarlyExit() } return response }
当不需要重定向,也就是返回的为 null ,直接返回 response