The source code

RetryAndFollowUpInterceptor interceptor is used to implement retry and redirection

All interceptors implement the Intercept method

@Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val realChain = chain as RealInterceptorChain var request = chain.request val call = realChain.call var followUpCount = 0 var priorResponse: Response? = null var newExchangeFinder = true var recoveredFailures = listOf<IOException>() while (true) { call.enterNetworkInterceptorExchange(request, newExchangeFinder) var response: Response var closeActiveExchange = true try { if (call.isCanceled()) { throw IOException("Canceled") } try { response = realChain.proceed(request) newExchangeFinder = true } catch (e: RouteException) { // The attempt to connect via a route failed. The request will not have been sent. if (! recover(e.lastConnectException, call, request, requestSendStarted = false)) { throw e.firstConnectException.withSuppressed(recoveredFailures) } else { recoveredFailures += e.firstConnectException } newExchangeFinder = false continue } catch (e: IOException) { // An attempt to communicate with a server failed. The request may have been sent. if (! recover(e, call, request, requestSendStarted = e ! is ConnectionShutdownException)) { throw e.withSuppressed(recoveredFailures) } else { recoveredFailures += e } newExchangeFinder = false continue } // 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 = call.interceptorScopedExchange val followUp = followUpRequest(response, exchange) if (followUp == null) { if (exchange ! = null && exchange.isDuplex) { call.timeoutEarlyExit() } closeActiveExchange = false return response } val followUpBody = followUp.body if (followUpBody ! = null && followUpBody.isOneShot()) { closeActiveExchange = false return response } response.body? .closeQuietly() if (++followUpCount > MAX_FOLLOW_UPS) { throw ProtocolException("Too many follow-up requests: $followUpCount") } request = followUp priorResponse = response } finally { call.exitNetworkInterceptorExchange(closeActiveExchange) } } }Copy the code

The first step is to remove the request from the interceptor chain and put it into an infinite loop. Since it is an infinite loop, the condition for breaking out of the loop is the most important

The first if is simple and throws an exception out of the loop if the request is cancelled. The second and third if calls the RECOVER method with the comment “Attempt to connect via route failed. The request will not be sent “.

/** * 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. * Reports and attempts to recover from failed communication with the server. Returns true if 'e' is recoverable, or false if the failure is permanent. * Only requests with body are allowed to resume sending if the request body is buffered, or if the failure occurred before the request. */ private fun recover( e: IOException, call: RealCall, userRequest: Request, requestSendStarted: Boolean ): Boolean {// The application layer has forbidden retries. if (! Client. RetryOnConnectionFailure) return false / / We can 't send the request body again. / / We can't send the request body. If (requestSendStarted && requestIsOneShot(e, userRequest)) return false // This exception is fatal. if (! IsRecoverable (e, requestSendStarted)) return false // No more routes to attempt. if (! call.retryAfterFailure()) return false // For failure recovery, Use the same route selector with a new connection. // For failover, use the same route selector with a new connection. return true }Copy the code

You see the Recover method, five ifs, and if any of them fire it returns false, and it throws an exception out of the loop. That is, requests are continually retried unless retry is prohibited or a fatal bug occurs.

Just finished RetryAndFollowUpInterceptor retry mechanism implementation, now again is how to realize the mechanism of value orientation.

To see the comments on the followUpRequest method: find the HTTP request response received [userResponse]. This will add authentication headers, follow redirection or handle client request timeouts. If a follow-up is either unnecessary or not applicable, null is returned. This method is used to handle redirects and timeouts.

On the request redirect piece of the source code, it is necessary to have a certain understanding of the response code, so we first briefly review the interval and type of the response code

  • 100-199: The request is being processed
  • 200-299: Request accepted (e.g. 200 request successful)
  • 300-399: The request is redirected (for example, 307 address redirected)
  • 400-499: Client request parameters error (for example: 403 request rejected, 404 request address does not exist)
  • 500-599: Server resolution request error (for example: 500 server error, 503 interface error)

When the status code is 300-399, the buildRedirectRequest method is called. When the status code is other, the corresponding exception is thrown

private fun buildRedirectRequest(userResponse: Response, method: String): Request? { // Does the client allow redirects? // Does the client allow redirection? if (! client.followRedirects) return null val location = userResponse.header("Location") ? : return null // Don't follow redirects to unsupported protocols. val url = userResponse.request.url.resolve(location) ? : return null // If configured, don't follow redirects between SSL and non-SSL. val sameScheme = url.scheme == userResponse.request.url.scheme if (! sameScheme && ! Most client. FollowSslRedirects) return null / / redirects don 't include a request body. / / Most redirection request body. val requestBuilder = userResponse.request.newBuilder() if (HttpMethod.permitsRequestBody(method)) { val responseCode = userResponse.code val maintainBody = HttpMethod.redirectsWithBody(method) || responseCode == HTTP_PERM_REDIRECT || responseCode == HTTP_TEMP_REDIRECT if (HttpMethod.redirectsToGet(method) && responseCode ! = HTTP_PERM_REDIRECT && responseCode ! = HTTP_TEMP_REDIRECT) { requestBuilder.method("GET", null) } else { val requestBody = if (maintainBody) userResponse.request.body else null requestBuilder.method(method, requestBody) } if (! maintainBody) { requestBuilder.removeHeader("Transfer-Encoding") requestBuilder.removeHeader("Content-Length") requestBuilder.removeHeader("Content-Type") } } // When redirecting across hosts, drop all authentication headers. This // is potentially annoying to the application layer since they have no // way to Retain them. // Remove all authentication headers when redirecting across hosts. This can be annoying to the application layer because they don't have the means to preserve them. if (! userResponse.request.url.canReuseConnectionFor(url)) { requestBuilder.removeHeader("Authorization") } return requestBuilder.url(url).build() }Copy the code

First, null is returned if redirection is disabled

Second, if the Location is not redirected, null is returned

The third judgment, do not configure SSL and non-SSL direct redirection return null

The request header is then wrapped and the request is returned

Refer to the blog

www.jianshu.com/p/40636d32c…

The next article

Juejin. Cn/post / 700772…