1.RetryAndFollowUpInterceptor

As the first interceptor built into OKHTTP, it has the following functions:

  • 1. Create StreamAllocation
  • 2, call RealInterceptorChain. Proceed () for network requests
  • 3. Determine whether to request again according to the abnormal result or response result

In conjunction with the code, the reconnection mechanism is implemented through while(true)

Retry mechanism The retry mechanism consists of two layers:

  • 1. Exceptions thrown during requests (connection suggestions, HTTP requests)
  • 2. After the request succeeds, obtain the status code and determine whether to retry based on the status code

There is a maximum number of retries in the second case, but the retries in the first case are likely to be in an infinite loop. OKHTTP provides a custom interceptor to address this problem.

If the Request is properly executed, all interceptors are executed recursively, but if an exception occurs on the Request’s link, if RouteException or IOExcept is thrown, Will probably be RetryAndFollowUpInterceptor capture, and then judge whether this exception can reconnect, if can reconnect, BridgeInterceptor blocker continues to start for processing, In this case, if you only use the custom NormalInterceptors, you cannot sense the reconnection. You can use the custom NetworkInterceptor to sense the reconnection.

@Override 
public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();
    // 1. Create the StreamAllocation object
    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    this.streamAllocation = streamAllocation;

    int followUpCount = 0;
    Response priorResponse = null;
    // 2. Implement reconnection via while(true)
    while (true) {
        // 3. If the message "cancel" is used to cancel the request, the reconnection mechanism is cancelled
        if (canceled) {
            streamAllocation.release();
            throw new IOException("Canceled");
        }
        Response response;
        boolean releaseConnection = true;
        try {
            // 4. Give the request to the RealInterceptorChain, and then catch and analyze the exception of the request chain
            response = realChain.proceed(request, streamAllocation, null.null);
            releaseConnection = false;
        } catch (RouteException e) {
            // 5. If a routing exception occurs, the ConnectInterceptor interceptor finds that the routing exception occurs in the TCP connection phase
            // Use recover to determine whether the exception needs to be thrown or can be reconnected
            if(! recover(e.getLastConnectException(), streamAllocation,false, request)) {
                throw e.getFirstConnectException();
            }
            releaseConnection = false;
            continue;
        } catch (IOException e) {
            // 6. Check whether the I/O exception needs to be thrown or reconnection can continue.
            RequestSendStarted (); // Recover (); // Recover (); // Recover ();
            // There are two possible situations at this stage: (1) an exception occurs before the HTTP request is sent to the server,
            // (2) The request is sent to the server, but the connection is disconnected due to server faults. As a result, I/O exceptions occur
            booleanrequestSendStarted = ! (einstanceof ConnectionShutdownException);
            if(! recover(e, streamAllocation, requestSendStarted, request))throw e;
            releaseConnection = false;
            continue;
        } finally {
            if (releaseConnection) {
                streamAllocation.streamFailed(null); streamAllocation.release(); }}// 7. If the command output is displayed, the request is sent to the Server and the response is received from the Server, but the response code is different from 200
        if(priorResponse ! =null) {
            response = response.newBuilder()
                               .priorResponse(priorResponse.newBuilder()
                               .body(null)
                               .build())
                               .build();
        }
        Request followUp;
        try {
            // 8. If the response is normal, determine whether to retry based on the request result
            followUp = followUpRequest(response, streamAllocation.route());
        } catch (IOException e) {
            streamAllocation.release();
            throw e;
        }
        if (followUp == null) {
            streamAllocation.release();
            return response;
        }
        closeQuietly(response.body());
        if (++followUpCount > MAX_FOLLOW_UPS) {
            streamAllocation.release();
            throw new ProtocolException("Too many follow-up requests: " + followUpCount);
        }
        if (followUp.body() instanceof UnrepeatableRequestBody) {
            streamAllocation.release();
            throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
        }
        if(! sameConnection(response, followUp.url())) { streamAllocation.release(); streamAllocation =new StreamAllocation(client.connectionPool(),
                createAddress(followUp.url()), call, eventListener, callStackTrace);
            this.streamAllocation = streamAllocation;
        } else if(streamAllocation.codec() ! =null) {
            throw new IllegalStateException("Closing the body of " + response
                + " didn't close its backing stream. Bad interceptor?"); } request = followUp; priorResponse = response; }}Copy the code

2.RouteException

When RouteException is thrown, do the following to determine whether reconnection is required;

  • 1. Use the Recover method to check whether the RouteException can be reconnected
  • 2. If the connection cannot be reconnected, raise this exception. If the connection can be reconnected, set releaseConnection bit to false to indicate that the connection is not released
  • Continue to enter the next loop and make the network request
2.1 Recover in RouteException

Combining with the above, are RouteException RealConnection. Connect and StreamAllocation. The newStream thrown in, this stage is to establish a TCP connection, the process of request has yet to start this process, Note that in the case of RouteException, the third parameter of recover is false, indicating that the request has not been sent

@return true: Can be reconnected,false: Do not support heavy connectionprivate boolean recover(IOException e, StreamAllocation streamAllocation,
      boolean requestSendStarted, Request userRequest) {
    streamAllocation.streamFailed(e);
    // 1. If the application layer does not support reconnection (external interface is provided to improve the priority, and some functions of SDK development are available, please refer to the design here)
    if(! client.retryOnConnectionFailure())return false;
    // 2. The request body does not support the retry mechanism
    if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
    // 3. Check whether the exception supports reconnection
    if(! isRecoverable(e, requestSendStarted))return false;
    // 4. If there are no more routes, there is no support for heavy connection
    if(! streamAllocation.hasMoreRoutes())return false;
    // 5. The remaining scenes can be reconnected
    return true;
}
Copy the code
2.2 isRecoverable
@return true: Allows reconnection in the case of this exception,false: Reconnection is not allowed in the case of this exceptionprivate boolean isRecoverable(IOException e, boolean requestSendStarted) {
    // 1. If the protocol is abnormal
    if (e instanceof ProtocolException) {
        return false;
    }
    // 2. If a timeout exception occurs before the request is issued, it is also allowed to reconnect in that case
    if (e instanceof InterruptedIOException) {
        return e instanceofSocketTimeoutException && ! requestSendStarted; }// Look for known client-side or negotiation errors that are unlikely to be fixed by trying
    // again with a different route.
    // 3. If the fault is related to certificate verification, reconnection is not allowed
    if (e instanceof SSLHandshakeException) {
        if (e.getCause() instanceof CertificateException) {
            return false; }}if (e instanceof SSLPeerUnverifiedException) {
        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.
    // 4. Unless the exception is an exception that we explicitly know cannot be reconnected, we will try to do the next reconnection and switch to
    // Next Route (here the next Route includes routes from the same Proxy, or switch to the next Proxy and traverse all its routes)
    return true;
}
Copy the code