Hello guys, the company is back to business as usual, but the outbreak is still going on. I am sorry for the slow update due to my busy work recently. This article is the second part of the OkHttp principle analysis, which mainly analyzes the specific implementation logic of several default interceptors in OkHttp.

Because a large portion of OkHttp logic is in the interceptor, this article will be lengthy and described in a serialized update format that will be updated each time the interceptor logic is analyzed.

If you are not familiar with OkHttp framework flow, you can first read my blog OkHttp principle analysis 1(framework flow article).


I’m going to perform again

But for the sake of subsequent description, I have simply made a summary of the above. It can be divided into the following steps.

    1. Create OkHttpClient (this can be created using okHttpClient.Builder with some basic parameters set internally)
    1. Create a Request object and set parameters such as the URL and Request type
    1. Create the RealCall object with okHttpClient.newCall (), and create the Transmitter object.
    1. Trigger asynchronous or synchronous requests by ** realcall.enqueue () or realcall.execute ()**.
    1. Call okHttpClient.dispatcher ().enqueue(new AsyncCall(responseCallback)) to prepare the request. The loop runningAsyncCalls and readyAsyncCalls queues find AsyncCall with the same host for reuse and transfer the AsyncCall from readyAsyncCalls to runningAsyncCalls, Abort AsyncCall if runningAsyncCalls exceed 64 and abort AsyncCall if the same host counter is greater than 5.
    1. The runningAsyncCalls loop calls asynccall.executeon (executorService())
    • 6.1. AsyncCall for Runnable, implement the run () method, called AsyncCall. The execute () joint call AsyncCall. GetResponseWithInterceptorChain () sets the interceptor List, Start by setting up a user-defined interceptor. Finally, RealInterceptorChain. Proceed () to start the interceptor.

The startup and operation of interceptors depends on the chain of responsibility mode, which is roughly divided into the following three steps.

    1. First, create a RealInterceptorChain object, use procee() to check for exceptions, and get the current Interceptor object.
    1. It then starts the current Interceptor logic by interceptor. intercept(RealInterceptorChain) and triggers the next Interceptor to start.
    1. If an error such as an exception occurs in the current interceptor, the chain of responsibility is terminated.

We start this article by adding intercepts to the location, in fact, has been described above, the source code shown below.

//RealCall.getResponseWithInterceptorChain();
  Response getResponseWithInterceptorChain(a) throws IOException {
    // Build a complete stack of interceptors
    List<Interceptor> interceptors = new ArrayList<>();
     // Add the interceptor from the time okHttpClient was created to the interceptors
    interceptors.addAll(client.interceptors());
    // Retry interceptor, which handles retry and redirection after a failure
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    // Request conversion interceptor (user request to server request, server response to user response)
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    // Cache interceptor. To be responsible for the
    //1. Return the cache response according to the condition, cache configuration, validity period, etc., can also be added to the cache.
    // set the request header (if-none-match, if-modified-since, etc.)
    //3. Customizable cache interceptor can be configured.
    interceptors.add(new CacheInterceptor(client.internalCache()));
    // Network connection interceptor, which is responsible for establishing a connection with the server.
    interceptors.add(new ConnectInterceptor(client));
    if(! forWebSocket) {// networkInterceptors set when creating okHttpClient
      interceptors.addAll(client.networkInterceptors());
    }
    // Data stream interceptor, mainly responsible for sending and reading data, request packet encapsulation and parsing.
    interceptors.add(new CallServerInterceptor(forWebSocket));
    // Create responsibility chain pattern.
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null.0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      // Start the chain of responsibility
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if(! calledNoMoreExchanges) { transmitter.noMoreExchanges(null); }}}Copy the code

Looking at my comments, OkHttp mainly involves the following default interceptors

    1. client.interceptors(); // User-defined interceptor
    1. RetryAndFollowUpInterceptor retry interceptor (client) / / failure
    1. BridgeInterceptor(client.cookiejar ())// Request conversion interceptor
    1. CacheInterceptor(client.internalcache ())// CacheInterceptor
    1. ConnectInterceptor(client)// Network connection interceptor
    1. CallServerInterceptor(forWebSocket)// Data stream interceptor

OkHttp will put user-defined interceptors default interceptor list head, with priority, and then by creating RealInterceptorChain object, and call the RealInterceptorChain. Proceed () to start the first interceptor, Intercept (Next) executes the logic of the first interceptor and passes the RealInterceptorChain object to the next interceptor, and so on. So let’s do it one by one.


1. Client.interceptors () Connection failure retry interceptor


User-defined interceptors have the priority of execution. The specific execution of user-defined logic is not described.


2. RetryAndFollowUpInterceptor (client) connection fails retry interceptors


First start RetryAndFollowUpInterceptor failure retry interceptors, This interceptor can be set to retryOnConnectionFailure(Boolean retryOnConnectionFailure) in the okHttpClient. Builder object. The OkHttpClient object built by default is turned on. The main responsibilities of the interceptor are explained in the official notes.

    1. The IP address cannot be accessed. If the HOST of the URL has multiple IP addresses, the inability to access any single IP address does not fail the entire request. This can improve the availability of multi-night services.
    1. Obsolete pool connections that reuse sockets through a ConnectionPool to reduce request latency, but these connections occasionally time out.
    1. Proxy servers that cannot be accessed can use ProxySelector and eventually return to direct connection.

To be honest, this description is slightly abstract, if I hadn’t used it, I really don’t know what he is talking about, let’s look at it through the source code.

// How many redirection and validation challenges should we try? Chrome follows 21 redirects; Firefox, Curl, and Wget follow 20; Safari follows 16; HTTP/1.0 Recommendation 5.
private static final int MAX_FOLLOW_UPS = 20;

@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Transmitter transmitter = realChain.transmitter();

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      // create ExchangeFinder, Address, RouteSelector by transmitter
      transmitter.prepareToConnect(request);
      // If the current request ends, an exception will be thrown, and the request can be terminated by transmitter.
      if (transmitter.isCanceled()) {
        throw new IOException("Canceled");
      }

      Response response;
      boolean success = false;
      try {
        // Start the next interceptor
        response = realChain.proceed(request, transmitter, null);
        // Exit the loop through this field if the execution is successful.
        success = true;
      } catch (RouteException e) {
        // If the route is abnormal. The request hasn't been sent yet.
        if(! recover(e.getLastConnectException(), transmitter,false, request)) {
          throw e.getFirstConnectException();
        }
        continue;
      } catch (IOException e) {
        // Failed to communicate with the server. The request may have been sent.
        booleanrequestSendStarted = ! (einstanceof ConnectionShutdownException);
        if(! recover(e, transmitter, requestSendStarted, request))throw e;
        continue;
      } finally {
        // The network call throws an exception. Release all resources.
        if (!success) {
          transmitter.exchangeDoneDueToException();
        }
      }

      // If body is not empty
      if(priorResponse ! =null) {
        // Get a new response
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null) .build()) .build(); } Exchange exchange = Internal.instance.exchange(response); Route route = exchange ! =null ? exchange.connection().route() : null;
      // Call followUpRequest() to see if the response needs to be redirected, return the current request if it does not, and return a new request if it does
      Request followUp = followUpRequest(response, route);
      // No redirection is required or cannot be redirected
      if (followUp == null) {
        if(exchange ! =null && exchange.isDuplex()) {
          transmitter.timeoutEarlyExit();
        }
        return response;
      }

      RequestBody followUpBody = followUp.body();
      if(followUpBody ! =null && followUpBody.isOneShot()) {
        return response;
      }

      closeQuietly(response.body());
      if (transmitter.hasExchange()) {
        exchange.detachWithViolence();
      }
      If the number of redirects exceeds the maximum number, an exception is thrown
      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: "+ followUpCount); } request = followUp; priorResponse = response; }}//RetryAndFollowUpInterceptor.recover()
  // Report and try to recover from a communication failure with the server. Return true if {@code e} is recoverable;
  // Return false if the failure is permanent. Requests with body can be recovered only if the body is buffered or if a failure occurs before the request is sent.
 private boolean recover(IOException e, Transmitter transmitter,
      boolean requestSendStarted, Request userRequest) {
    // The user set the retry prohibition
    if(! client.retryOnConnectionFailure())return false;

    // The request body can no longer be sent
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;

    // Exceptions are fatal
    if(! isRecoverable(e, requestSendStarted))return false;

    // There are no more routes to try.
    if(! transmitter.canRetry())return false;

    // For failover, use the same route selector for new connections
    return true;
  }

  /** * check if the response needs to be redirected, return the current request if not, and return a new request if needed. * Find receive {@codeThe HTTP request to be made when userResponse}. This adds authentication headers, follows redirects, or handles client request timeouts. * Null is returned if subsequent actions are unnecessary or not applicable. * /
  private Request followUpRequest(Response userResponse, @Nullable Route route) throws IOException {
    if (userResponse == null) throw new IllegalStateException();
    // Get the response code
    int responseCode = userResponse.code();
    // Request method
    final String method = userResponse.request().method();
    // Response code classification processing
    switch (responseCode) {
      //407 proxy requires authentication
      caseHTTP_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);
      //401 requires authentication
      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
       // More than 300 options. The requested resource can include multiple locations, and a list of resource characteristics and addresses can be returned for user terminal (e.g., browser) selection
      case HTTP_MULT_CHOICE:
      // 301 permanently moved. The requested resource has been permanently moved to the new URI, the return message will include the new URI, and the browser will automatically redirect to the new URI.
      case HTTP_MOVED_PERM:
      // 302 temporary move. Similar to 301. But resources are moved only temporarily.
      case HTTP_MOVED_TEMP:
      // 303 View other addresses. Similar to 301. Use GET and POST requests to view
      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);

        // Do not follow redirects to unsupported protocols.
        if (url == null) return null;

        // If configured, do not follow redirection between SSL and non-SSL.
        boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
        if(! sameScheme && ! client.followSslRedirects())return null;

        // Most redirects do not include the request body.
        Request.Builder requestBuilder = userResponse.request().newBuilder();
        if (HttpMethod.permitsRequestBody(method)) {
          final boolean maintainBody = HttpMethod.redirectsWithBody(method);
          if (HttpMethod.redirectsToGet(method)) {
            requestBuilder.method("GET".null);
          } else {
            RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
            requestBuilder.method(method, requestBody);
          }
          if(! maintainBody) { requestBuilder.removeHeader("Transfer-Encoding");
            requestBuilder.removeHeader("Content-Length");
            requestBuilder.removeHeader("Content-Type"); }}// Delete all authentication headers when redirecting across hosts. This can be annoying for the application layer because they can't keep them.
        if(! sameConnection(userResponse.request().url(), url)) { requestBuilder.removeHeader("Authorization");
        }

        return requestBuilder.url(url).build();
      / / 408 timeout
      case 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 tells us not to retry the request
          return null;
        }

        RequestBody requestBody = userResponse.request().body();
        if(requestBody ! =null && requestBody.isOneShot()) {
          return null;
        }

        if(userResponse.priorResponse() ! =null
            && userResponse.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 The server is temporarily unable to process client requests due to overloading or system maintenance. The length of the delay can be included in the server's retry-after header
      case HTTP_UNAVAILABLE:
        if(userResponse.priorResponse() ! =null
            && userResponse.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;

      default:
        return null; }}//Transmitter.prepareToConnect()
public void prepareToConnect(Request request) {
    if (this.request ! =null) {
      // Determine if the connection is the same, if so, use the previous connection.
      if (sameConnection(this.request.url(), request.url()) && exchangeFinder.hasRouteToTry()) {
        return; // Already ready.
      }
      if(exchange ! =null) throw new IllegalStateException();
      //exchangeFinder
      if(exchangeFinder ! =null) {
        maybeReleaseConnection(null.true);
        exchangeFinder = null; }}this.request = request;
    this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
        call, eventListener);
  }

//Transmitter.createAddress()
 private Address createAddress(HttpUrl url) {
    SSLSocketFactory sslSocketFactory = null;
    HostnameVerifier hostnameVerifier = null;
    CertificatePinner certificatePinner = null;
    if (url.isHttps()) {
      / / HTTPS Settings
      sslSocketFactory = client.sslSocketFactory();
      hostnameVerifier = client.hostnameVerifier();
      certificatePinner = client.certificatePinner();
    }

    return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
        sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
        client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
  }

//ExchangeFinder.ExchangeFinder()
  ExchangeFinder(Transmitter transmitter, RealConnectionPool connectionPool,
      Address address, Call call, EventListener eventListener) {
    this.transmitter = transmitter;
    this.connectionPool = connectionPool;
    this.address = address;
    this.call = call;
    this.eventListener = eventListener;
    // Create a route selector
    this.routeSelector = new RouteSelector(
        address, connectionPool.routeDatabase, call, eventListener);
  }
//RouteSelector.RouteSelector()
  RouteSelector(Address address, RouteDatabase routeDatabase, Call call,
      EventListener eventListener) {
    this.address = address;
    this.routeDatabase = routeDatabase;
    this.call = call;
    this.eventListener = eventListener;

    resetNextProxy(address.url(), address.proxy());
  }
Copy the code

The interceptor has a lot of logic, but it basically does two things, one is retry and one is redirection, and the logic goes something like this.

    1. Obtain the transmitter object via realChain. Transmitter, and enable a while loop,
    1. And then through the transmitter. PrepareToConnect (request) create ExchangeFinder transmitter, the Address, RouteSelector three objects, and determine whether the same connection, Reset ExchangeFinder if maybeReleaseConnection() is required.
    • This interceptor simply creates an ExchangeFinder, But ExchangeFinder has a find() method that primarily finds an available connection from a connectionPool through the internal findHealthyConnection(), which may be reusable, Connect () to get the input/output stream (source/sink) and return an Exchange to the CallServerIntercepter. From this Exchange, you can add the request header and the request body, and read the response header and the response body. Give it to the Intercepter, and pass it up.
    • 2.2. Address is the encapsulation class of request parameters, including URL, port, DNS, SSL, Proxy, ProxySelector, SocketFactory, host name authentication, certificate verification and other logic.
    • 2.3. A RouteSelector focuses on routing and does three main things. 1. Collect all available routes. 2. Select an available route. 3. Use the Set object in RouteDatabase to maintain the information about routes that fail to be connected to prevent wasting time.
    1. Starts the next interceptor in the interceptor list.
    1. Check whether the global variable priorResponse is null. If not, the request succeeded.
    1. Execute followUpRequest() to see if the response requires a redirection, and return to the current request if it does not
    1. Check the number of redirects. If the number exceeds the maximum, exit.
    1. Reset the request and save the current Response to priorResponse to enter the next while loop.

To sum up: Response is obtained through the while loop, and the next request is obtained according to the conditions at the beginning of each loop. If there is no request, response is returned and the loop exits. The condition of the request obtained is determined according to the response status code of the last request, and some subsequent objects are created in the body of the loop


BridgeInterceptor(client.cookiejar ())// Request conversion interceptor


  • Let’s ignore the logic and just look at the name.

The Bridge Interceptor is the Interceptor. Bridge interceptor? Is that a bridge? I need 98K first, preferably with a grenade.

  • After a brief look at the comments and logic of the source code, I found that this is actually a bridge between converting a user-defined object Request into a network Request and converting a network back into a user Response object. How does it act as a bridge? Let’s look at the source code.
 public BridgeInterceptor(CookieJar cookieJar) {
    this.cookieJar = cookieJar;
  }
Copy the code
  • The first is the constructor for the BridgeInterceptor. When creating the BridgeInterceptor object, we pass in the CookieJar, which gets the CookieJar from the OkHttpClient object. . If the user does not set the default use OkHttpClient cookieJar in the Builder, and OkHttpClient. Builder. The default for cookieJar cookieJar. NO_COOKIES.
 @Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    RequestBody body = userRequest.body();
    // Wrap the Request header with the user Request object.
    if(body ! =null) {
      MediaType contentType = body.contentType();
      if(contentType ! =null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      if(contentLength ! = -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding"."chunked");
        requestBuilder.removeHeader("Content-Length"); }}if (userRequest.header("Host") = =null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    if (userRequest.header("Connection") = =null) {
      requestBuilder.header("Connection"."Keep-Alive");
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    // If the user does not set the accept-encoding field, okhttp uses Gzip for compression by default,
    // The accept-encoding set to Gzip tells the server what kind of data Encoding is acceptable to the client.
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") = =null && userRequest.header("Range") = =null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding"."gzip");
    }
    // Add the cookie header
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if(! cookies.isEmpty()) { requestBuilder.header("Cookie", cookieHeader(cookies));
    }

    if (userRequest.header("User-Agent") = =null) {
      // version.userAgent () returns the Version number of okhttp.
      requestBuilder.header("User-Agent", Version.userAgent());
    }
    // Enable the next interceptor in the chain of responsibility and receive the result of the next interceptor's processing.
    Response networkResponse = chain.proceed(requestBuilder.build());
    // Get the Cookie returned by the server and set the receiving header
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
    
    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);
    // Determine whether the server supports gzip compression, and if so, submit the compression to the Okio library for processing
    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
  }
Copy the code

The BridgeInterceptor code is also one piece, but it is relatively simple and can be divided into two parts.

    1. Wrap the user’s Request Request as a network Request.
    1. Wrap the network return Response as the return object that the user needs.

The wrapper network request mainly does special processing to the request header, as noted above. The special one is accept-encoding. If the user does not custom add accept-encoding, the framework adds gzip by default, and the returned data is decoded by gzip. Note that content-Encoding and Content-Length are removed when decompressing automatically, so the Java upper layer calls contentLength with -1. If the user manually adds accept-encoding, transparentGzip is false and the returned data frame is not responsible for decompressing.


4. CacheInterceptor(client.internalcache ())// CacheInterceptor


  • This interceptor, as the name probably tells us, is used to handle caching. What caching? Let me give you a brief description in one paragraph.
  • After an HTTP request is received, a cache is generated on the client. If the cache exists, you can configure two policies before sending subsequent requests
    • 1. Compare caches
    • 2. Force cache

If the comparison cache is used, the data tag comparison or time comparison is used, and then whether to use the cache is intelligently selected by the comparison difference. If the mandatory cache policy is used, the cached data is directly used. In addition, the difference is that the comparison cache will necessarily initiate a network request, but the data will not change and the 304 status code will tell the client to get the data through the cache. The mandatory cache reads directly from the cache without network request.

  • In fact, you can understand the above paragraph, but it is not mapped to the specific scene, I will write a simple example to describe.
        // Create a cache object and set the location and size of the cache.
        Cache cache = new Cache(new File("mnt/sdcard/package/cache"), 1024 * 1024);
        OkHttpClient okHttpClient = new OkHttpClient
                .Builder()
                // Set the cache object
                .cache(cache)
                .build();
        Request request = new Request
                .Builder()
                .url("")
                 //noCache() means noCache and is obtained from the network. If noStore() means noCache and noCache is used. If no write is used, the default cache and cache are used.
                .cacheControl(new CacheControl.Builder().noCache().build())
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {}@Override
            public void onResponse(Call call, Response response) throws IOException {}});Copy the code
  • This is how custom caches are used, but the internals are covered by the Chain of responsibility pattern, which is detailed in the CacheInterceptor class and gives priority to the Intercept () method whenever a chain of responsibility is used.
@Override 
public Response intercept(Chain chain) throws IOException {
    // Obtain the request cache object Response from the request object as a candidate cache. If the cache is configured, the cache is not empty. If the cache is not configured, it is empty by default.Response cacheCandidate = cache ! =null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
    // According to the timestamp, the request object, the local candidate cache object generates a cache policy to determine whether to use network cache, local cache, or both.
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    // If it is not null, network cache is used
    Request networkRequest = strategy.networkRequest;
   // Do not use local cache for null
    Response cacheResponse = strategy.cacheResponse;

    if(cache ! =null) {
      // Trace HTTP responses that meet the strategy
      cache.trackResponse(strategy);
    }
     // If the candidate cache is empty but the local cache is empty, the local cache is invalid. Disable the alternative cache cacheCandidate
    if(cacheCandidate ! =null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If the network cache is disabled and the local cache is not empty, return fial, code 504
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    // If no network request cache is used, local cached data is returned directly.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    // The logic so far represents the use of network requests.
    Response networkResponse = null;
    try {
      // The chain of responsibility starts to go down further.
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If the network returns an empty object, but the local candidate cache is not empty, the candidate cache is closed.
      if (networkResponse == null&& cacheCandidate ! =null) { closeQuietly(cacheCandidate.body()); }}// If the local cache exists, build a reponse object from the local cache and the response returned by the network
    if(cacheResponse ! =null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        // If the network status code is 304, it is obtained from the local cache
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();
        // Update local cache
        cache.update(cacheResponse, response);
        return response;
      } else{ closeQuietly(cacheResponse.body()); }}Build the Response object from the network cache
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if(cache ! =null) {
      // Add the network response to the cache
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }
      // Determine if it is a POST request, if it is, remove the cache and keep only the GET request
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.}}}return response;
  }
Copy the code

To summarize, I’ve summarized the large chunk of code above into the following six steps to help you quickly understand how the interceptor works.

  • 1. Obtain the Response cache object by combining the user-defined cache object with the Request request object.
  • 2. According to the timestamp, the request object and the local response cache object jointly generate the cache policy for this request, which determines whether to use local cache, network cache or both.
  • 3. If the cache configured by the user has been obtained, but the cache policy does not use the local cache this time, the local cache data is disabled to release. If neither the network nor the local cache is used this time, an error 504 is displayed.
  • 4. If the network cache is unavailable, return the cache using the local cache.
  • Proceed (networkRequest) with chain.proceed(networkRequest); Pass on to the next interceptor.
  • 6. If the local cache is not empty and the network request returns a 304 status code, a response response is constructed by the local cache and the network return together and updated to the local cache.
  • 7. If the local cache is empty, the response returned by the network will be returned and stored in the cache file. If it is a POST request, the response will be removed.

Note: Cache policy generation involves several rules

  • 1. If noCache() is manually configured for Request creation, local cache is not used.
  • 2. If noStore() is manually configured, the local cache is not used and the data requested by the network is not cached locally.
  • 3. Check whether the server data has been Modified in if-modified-since. If the data has been Modified, the latest data of the server is returned. (The judgment rule is based on the time. If the time of the last request is > the last update time of the server, the data is not updated.)
  • 4. Use if-none-match to check whether the server data has been modified. If the value is different from the ETag of the server resource, the server returns the value. (ETag is a unique identifier of a resource. Each request is matched with an ETag through if-none-match.)

ConnectInterceptor(client)// Network connection interceptor


This interceptor is primarily used to handle network connection-related interceptors and is called in a chain of responsibility mode, so we’ll first look at the Intercept () method in ConnectInterceptor.

@Override 
public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    // Get the Request object
    Request request = realChain.request();
    // Obtain the Transmitter object through realChain, as for what this object is, don't worry about it first
    Transmitter transmitter = realChain.transmitter();

    // We need the network to meet this requirement. May be used to validate the condition GET. Check whether the current request type is POST or GET. If the request type is POST, network check is required.
    booleandoExtensiveHealthChecks = ! request.method().equals("GET");
    // The key logic is that the Exchange object is created by transmitter.newExchange () and then called realchain.proceed () to start the next interceptor passed.
    Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
    return realChain.proceed(request, transmitter, exchange);
  }
Copy the code
  • Yes, that’s right, the interceptor only has these lines of code. NewExchange (Chain, doExtensiveHealthChecks); newExchange(Chain, doExtensiveHealthChecks); Internal logic.
 /** Returns a new Exchange object with request and Response. */
  Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    synchronized (connectionPool) {
      if (noMoreExchanges) {
        throw new IllegalStateException("released");
      }
      if(exchange ! =null) {
        throw new IllegalStateException("cannot make a new request because the previous response "
            + "is still open: please call response.close()"); }}// Only this sentence is the core, we need to continue tracking
    ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
    Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);

    synchronized (connectionPool) {
      this.exchange = result;
      this.exchangeRequestDone = false;
      this.exchangeResponseDone = false;
      returnresult; }}Copy the code
  • Of the above logic, only the generation logic for Exchange Dec objects is unclear, so we need to keep tracking

exchangeFinder.find(client, chain, doExtensiveHealthChecks); Methods.

  public ExchangeCodec find(OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    int connectTimeout = chain.connectTimeoutMillis();
    int readTimeout = chain.readTimeoutMillis();
    int writeTimeout = chain.writeTimeoutMillis();
    int pingIntervalMillis = client.pingIntervalMillis();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();

    try {
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
              writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
      return resultConnection.newCodec(client, chain);
    } catch (RouteException e) {
      trackFailure();
      throw e;
    } catch (IOException e) {
      trackFailure();
      throw newRouteException(e); }}Copy the code
  • Does this find() method look familiar? Began I thought it was a code capability can I talk to OKHTTP above the author, then I want to understand that the first interceptor RetryAndFollowUpInterceptor analysis of related logic.
  • At this time we use exchangeFinder object is via a transmitter RetryAndFollowUpInterceptor blocker. PrepareToConnect () to create, I paste the one described above.
    • ExchangeFinder has a find() method that finds an available connection from a connectionPool primarily through the internal findHealthyConnection(), which may be reusable, Realconnection.connect () is called to get the input/output stream (source/sink) and the corresponding Http2Connection object.
  • The final returned Exchange Dec object was generated through realConnection.newCodec (Client, chain) and we follow up.
  ExchangeCodec newCodec(OkHttpClient client, Interceptor.Chain chain) throws SocketException {
    if(http2Connection ! =null) {
      return new Http2ExchangeCodec(client, this, chain, http2Connection);
    } else {
      socket.setSoTimeout(chain.readTimeoutMillis());
      source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
      sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
      return new Http1ExchangeCodec(client, this, source, sink); }}Copy the code
  • The logic is clear at this point, the realConnection.newCodec () method returns ExchangeCodec, which is an interface with only two implementation classes, Http2ExchangeCodec and Http1ExchangeCodec respectively. If http2Connection is not empty, create and return the Http2ExchangeCodec object directly, and then return it to the transmitter.newExchange () method, Finally returned to the original ConnectInterceptor. Intercept () method, The Exchange object is then passed to the next interceptor via realchain.proceed.

In fact, this interceptor is one of the most core interceptors in OKHTTP. This description does not cover the deepest level of code analysis. In order to provide a clear description of the complete logic of ConnectInterceptor, I have summarized the following steps to understand the internal principles and logic as quickly as possible.

The intercept() method has two steps of logic

  • 1. Firstly, obtain the transmitter object through realChain. Transmitter (), which is the transfer station of communication between the application layer and the network layer. Every request corresponds to a transmitter.
  • 2. Through the transmitter. NewExchange (.. ,…). Get the Exchange object and proceed with realchain.proceed (… Transmitter, exchange) to the next interceptor, the core is the process of obtaining exchange.

Getting Exchange is broken down into the following steps

  • 1. Follow up transmitter. NewExchange () to obtain Exchange Dec through Exchange Finder.find(Client, chain, doExtensiveHealthChecks).
  • 2. Through New Exchange(…) Parameters such as ExchangeCodec are passed in and obtained into an Exchange object.

The problem translates to the process of getting an Exchange Dec object

  • Note that ExchangeCodec is an interface with only two implementation classes, Http2ExchangeCodec and Http1ExchangeCodec, and ExchangeCodec is obtained through ExchangeFinder.find (…). To obtain.
  • ExchangeFinder object is initialized in the RetryAndFollowUpInterceptor blocker logic, through specific exchangeFinder. RepareToConnect initialize ().
  • 2. ExchangeFinder. The find () joint call exchangeFinder. FindHealthyConnection (…). The RealConnection object is obtained (the logic at this point is to find available healthy connections).
  • 3. Through RealConnection. NewCodec (…). An Exchange Dec object is obtained.

The problem again translates to the process of getting a RealConnection

  • 1.exchangeFinder.findHealthyConnection(…) * * death cycle through exchangeFinder. FindConnection () * * find connections are available, and at the same time, not with labels, convenient later removed.
  • 2. The first attempt to obtain the connection will be made from the transmitter. Connection.
  • 3. Obtain the connection from the connection pool for the second time using address, host, port, proxy and other information. If no match is found, obtain the routing information (multiple routes, that is, multiple IP addresses) of the proxy, and try to obtain the connection from the connection pool again. (transmitterAcquirePooledConnection () method is to try to connect method from a thread pool)
  • 4. Create a RealConnection instance for the third time to set up a connection with the server through TCP + TLS handshake.
  • 5. During the process of creating a RealConnection instance, you need to continue matching from the connection pool. If a match is found, the newly created connection is released; if no match is found, RealConnection is added to the connection pool.

Step by step, we get the Exchange entity. At the same time, we need to pay attention to the question: How to determine whether RealConnection is a monitored connection?

  • 1. Run socket.isclosed (), isInputShutdown (), and isOutputShutdown () to check whether the socket is available.
  • 2. Http2 connection through http2Connection. IsShutdown ()
  • 3. The socket uses setSoTimeout () to set a one-second delay to check whether the Stream is available. If yes, the connection is unavailable.

  1. CallServerInterceptor(forWebSocket)// Data stream interceptor

Yay yay, it’s finally the last blocker, this blog has been going on for too long, it’s finally coming to an end. The CallServerInterceptor interceptor is mainly used for network connection, receiving and converting data, and all preparations are made for the last step of the request. We’ll look directly at the code, as we did with the interceptor before, and look directly at the Intercept () method.

@Override
public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Exchange exchange = realChain.exchange();
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();
    // This is a request Header
    exchange.writeRequestHeaders(request);

    boolean responseHeadersStarted = false;
    Response.Builder responseBuilder = null;
    // If the request is neither get nor Head, and the body of the request is not empty (in other words, it is a POST request with the request body)
    if(HttpMethod.permitsRequestBody(request.method()) && request.body() ! =null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return
      // what we did get (such as a 4xx response) without ever transmitting the request body.
      // If (Expect: 100-continue) is added to the header, wait for the server to return a response with "HTTP/1.1 100 continue ", and then decide whether to send the body based on the response content. If the server can receive the body, it will return null.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        // Ask the server if it wants to accept the body
        exchange.flushRequest();
        responseHeadersStarted = true;
        exchange.responseHeadersStart();
        // The server returns the result here
        responseBuilder = exchange.readResponseHeaders(true);
      }
      // If responseBuilder is null, the server receives the RequestBody
      if (responseBuilder == null) {
         // Unless a subclass overrides the isDuplex () method, this method returns false by default
        // The following logic is to write the body.
        if (request.body().isDuplex()) {
          // Prepare a duplex body so that the application can send a request body later.
          exchange.flushRequest();
          BufferedSink bufferedRequestBody = Okio.buffer(exchange.createRequestBody(request, true));
           // Write request body information
          request.body().writeTo(bufferedRequestBody);
        } else {
          // Write the request body if the "Expect: 100-continue" expectation was met.
          BufferedSink bufferedRequestBody = Okio.buffer(exchange.createRequestBody(request, false));
           // Write request body informationrequest.body().writeTo(bufferedRequestBody); bufferedRequestBody.close(); }}else {
        // The server does not receive the body.
        exchange.noRequestBody();
        // This is used to determine whether the http2Connection in the RealConnection object is empty
        if(! exchange.connection().isMultiplexed()) {// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
          // from being reused. Otherwise we're still obligated to transmit the request body to
          // leave the connection in a consistent state.
          // If it is empty, it is HTTP1.0, and the server does not receive the body, then the connection should be closed to prevent HTTP1.0 from being reused, since only HTTP2.0 can be reused.exchange.noNewExchangesOnConnection(); }}}else {
      // Without the body, the request ends.
      exchange.noRequestBody();
    }

    if (request.body() == null| |! request.body().isDuplex()) {// End of request
      exchange.finishRequest();
    }

    if(! responseHeadersStarted) {// Read the response header to start the callback
      exchange.responseHeadersStart();
    }

    if (responseBuilder == null) {
      // Read the response header
      responseBuilder = exchange.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(exchange.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (code == 100) {
       // If the server returns 100, try again to get the real response.
      // server sent a 100-continue even though we did not request one.
      // try again to read the actual response
      response = exchange.readResponseHeaders(false)
          .request(request)
          .handshake(exchange.connection().handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();

      code = response.code();
    }
    The callback reads the end of the response header
    exchange.responseHeadersEnd(response);
    // If the status code is 101, get the response body
    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build();
    }
    // If the key in the Header is "Connection" and the value is "close", the Connection needs to be closed after the request.
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      exchange.noNewExchangesOnConnection();
    }
    // If the status code is 204/205, return data should be null. If the return length is greater than 0, a ProtocolException is reported.
    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }
Copy the code

The source code for the CallServerInterceptor interceptor ends here. The interceptor is the core of the network IO in OKHTTP, so for ease of understanding, LET me summarize what’s going on inside the interceptor.

  • 1. The first object through the chain of responsibility for RealInterceptorChain objects, and access to Exchange object, through Exchange. WriteRequestHeaders () to write the header into the.
  • 2. If the Request contains a body, check whether the Request header contains “Expect: 100-continue” key-value pairs. If yes, wait for the server to return “HTTP/1.1 100 CONTINUE” to determine whether to send the body. (If null is returned, 100 status code is returned and body needs to be sent. If 4** status code is returned, body is not sent.)
  • 3. Through okio. Buffer () with exchange. CreateRequestBody (request, true), and constructs the BufferedSink object, Then write the request body via request.body().writeto (BufferedSink).
  • 4. After sending the request, send the read response header callback via Exchange’s responseHeadersStart() and start reading the response header with readResponseHeaders (false). , after reading you need to call exchange. ResponseHeadersEnd (response) callback told that reads the response header end.
  • 5. Start by response. NewBuilder (). The body (exchange. OpenResponseBody (response)). The build (); Read the response body. (Reading is done in the Http2ExchangeCodec class.)
  • 6. Finally, if the header key value pair Connection is close, you need to close the Connection at the end of the request.

After so long, I have finally finished writing it. I believe that you also have a deeper understanding of OKHTTP process. If there is any mistake, I hope you correct me. If you find it rewarding, please give it a thumbs up.