In the last article in this series, we walked through the source code for OkHTTP and got a first look at the basic execution flow of this powerful networking framework.

The last article, however, was a cursory read of the source code for okHTTP’s entire execution process and understood the basics of how okHTTP works, but it didn’t go into the details (in fact, it’s impossible to go into every source code detail in a single article). In this article, we will take an in-depth look at okHTTP and slowly grasp all the functions in okHTTP.

Source code in today’s article are built on the basis of the last source code analysis, has not seen the last article friends, recommended first to read the network request framework OkHttp3 full solution series – (two) OkHttp workflow analysis. In this article, we learned that the actual execution of network requests is handled by interceptors associated with a chain of interceptors, each of which is responsible for a different function. We will analyze each interceptor in detail, including the important points of caching and connection pooling.

RealCall getResponseWithInterceptorChain method under review – interceptor add and execution of the interceptor chain:

  Response getResponseWithInterceptorChain(a) throws IOException {
    // Create a series of interceptors
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if(! forWebSocket) { interceptors.addAll(client.networkInterceptors()); } interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null.0,
        originalRequest, this, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); .// Execution of interceptor chainResponse response = chain.proceed(originalRequest); .returnresponse; . }Copy the code

RetryAndFollowUpInterceptor – retry, redirect

If the request is created without adding application blocker, then the first interceptor is RetryAndFollowUpInterceptor, meaning “retry and follow up blocker”, is used after the connection fails retry, redirect after follow up the result of the request. Check out its intercept method:

  @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) {
      // Prepare the connection
      transmitter.prepareToConnect(request);
      
      if (transmitter.isCanceled()) {
        throw new IOException("Canceled");
      }

      Response response;
      boolean success = false;
      try {
        // Continue with the next Interceptor
        response = realChain.proceed(request, transmitter, null);
        success = true;
      } catch (RouteException e) {
        // The connection route is abnormal. The request has not been sent yet. Try to recover ~
        if(! recover(e.getLastConnectException(), transmitter,false, request)) {
          throw e.getFirstConnectException();
        }
        continue;
      } catch (IOException e) {
        // The request may have been sent. Try to recover ~
        booleanrequestSendStarted = ! (einstanceof ConnectionShutdownException);
        if(! recover(e, transmitter, requestSendStarted, request))throw e;
        continue;
      } finally {
        // The request failed, the resource was released.
        if (!success) {
          transmitter.exchangeDoneDueToException();
        }
      }

      // associate a response
      if(priorResponse ! =null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null) .build()) .build(); } Exchange exchange = Internal.instance.exchange(response); Route route = exchange ! =null ? exchange.connection().route() : null;
      // If the response code is not empty, the Request will be redirected
      Request followUp = followUpRequest(response, route);

	  // If followUp is empty, no retry is required
      if (followUp == null) {
        if(exchange ! =null && exchange.isDuplex()) {
          transmitter.timeoutEarlyExit();
        }
        return response;
      }

	  // followupBody. isOneShot, no need to retry, return directly
      RequestBody followUpBody = followUp.body();
      if(followUpBody ! =null && followUpBody.isOneShot()) {
        return response;
      }

      closeQuietly(response.body());
      if (transmitter.hasExchange()) {
        exchange.detachWithViolence();
      }

	  // Retry a maximum of 20 times
      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }
      // Assign a value to request againrequest = followUp; priorResponse = response; }}Copy the code

Use while to loop:

Connect with transmitter. The first prepareToConnect (request). As a bridge between the application layer and the network layer, transmitter plays a very important role in connecting, really sending out requests and reading responses. Take a look at the prepareToConnect method:

  public void prepareToConnect(Request request) {
    if (this.request ! =null) {
      if (sameConnection(this.request.url(), request.url()) && exchangeFinder.hasRouteToTry()) {
        return; // The same connection already exists}... }this.request = request;
    // ExchangeFinder is created in preparation for retrieving connections
    this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
        call, eventListener);
  }
Copy the code

Mainly is to create ExchangeFinder combing assignment to the transmitter. ExchangeFinder. An ExchangeFinder is an ExchangeFinder that fetches a requested connection. So let’s take a look at this, and we’ll talk about it later.

A call to realchain.proceed continues passing the request to the next interceptor, retrieving the raw result from the next interceptor. If a connection route exception or I/O exception occurs during this process, recover is called to determine whether to retry recovery. Look at the recover method:

  private boolean recover(IOException e, Transmitter transmitter,
      boolean requestSendStarted, Request userRequest) {
    // The application layer does not retry
    if(! client.retryOnConnectionFailure())return false;

    // If the request cannot be sent again, do not retry
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;

    // The exception that occurred is fatal and will not be retried
    if(! isRecoverable(e, requestSendStarted))return false;

    // If there is no route to try, do not retry
    if(! transmitter.canRetry())return false;

    return true;
  }
Copy the code

If the Recover method returns true, the next loop repeats the request.

If realchain.proceed does not occur and a result response is returned, the result is followed up using the followUpRequest method and a redirected request is constructed. If no follow-up is required (for example, if the response code is 200), null is returned. Look at the followUpRequest method:

  private Request followUpRequest(Response userResponse, @Nullable Route route) throws IOException {...intresponseCode = userResponse.code(); .switch (responseCode) {
      ...
      case HTTP_PERM_REDIRECT:
      case HTTP_TEMP_REDIRECT:
        if(! method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
      case HTTP_MULT_CHOICE:
      case HTTP_MOVED_PERM:
      case HTTP_MOVED_TEMP:
      case HTTP_SEE_OTHER:
        if(! client.followRedirects())return null;

        String location = userResponse.header("Location");
        if (location == null) return null;
        HttpUrl url = userResponse.request().url().resolve(location);

        if (url == null) return null;
        boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
        if(! sameScheme && ! client.followSslRedirects())return null;

        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"); }}if(! sameConnection(userResponse.request().url(), url)) { requestBuilder.removeHeader("Authorization");
        }

        return requestBuilder.url(url).build();

      case HTTP_CLIENT_TIMEOUT:
        ...
      default:
        return null; }}Copy the code

The main idea is to determine if a redirect is needed based on the response code, take the redirected URL from the response, build a new Request and send it back.

Looking down, there is another judgment: try 20 times at most. Here, RetryAndFollowUpInterceptor is finished, is relatively simple, mainly is the connection fails retry, follow-up processing redirection.

BridgeInterceptor – a BridgeInterceptor

Then there is the BridgeInterceptor, which is equivalent to building a bridge between the request initiator and the network executor. It turns the request sent by the application layer into a request recognized by the network layer, and the response executed by the network layer into a result that is convenient for the application layer to use. Look at the code:

// Bridge interceptor
public final class BridgeInterceptor implements Interceptor {

  //Cookie manager, created when OkhttpClient is initialized, defaults to cookiejar.no_cookies
  private final CookieJar cookieJar;

  public BridgeInterceptor(CookieJar cookieJar) {
    this.cookieJar = cookieJar;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();
    
    RequestBody body = userRequest.body();
    if(body ! =null) {
      MediaType contentType = body.contentType();
      if(contentType ! =null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      // If you already know the body Length, add the header "content-length"
      if(contentLength ! = -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
      // If the body length is not known, add the header "transfer-encoding" to indicate that the message is blocked. In this case, the entity in the packet needs to be transferred as a series of blocks. Specific understanding please refer to: [HTTP Transfer - Encoding introduced] (https://blog.csdn.net/Dancen/article/details/89957486)
        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");
    }

	//" accept-encoding: gzip", which means to Accept: Returns the data encoded by gZIP
    // If we manually add "accept-encoding: gzip", then the following if does not enter, transparentGzip is false, and we need to handle data decompression ourselves.
    // If "accept-encoding: gzip" is not manually added, transparentGzip is true and is automatically added, and decompression is automatically processed later.
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") = =null && userRequest.header("Range") = =null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding"."gzip");
    }
	// Get the cookie from the cookieJar and add it to the header
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if(! cookies.isEmpty()) { requestBuilder.header("Cookie", cookieHeader(cookies));
    }
	//" user-agent "needs to be added as a public header
    if (userRequest.header("User-Agent") = =null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }

	// Process the request
    Response networkResponse = chain.proceed(requestBuilder.build());
	// Get the header "set-cookie" from networkResponse and store it in the cookieJar
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

	// If we did not manually add "accept-encoding: gzip", we will create responseBody--GzipSource which can be automatically extracted
    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)));
    }
	// Then build a new response and return it
    return responseBuilder.build();
  }
  
  private String cookieHeader(List<Cookie> cookies) {
    StringBuilder cookieHeader = new StringBuilder();
    for (int i = 0, size = cookies.size(); i < size; i++) {
      if (i > 0) {
        cookieHeader.append("; ");
      }
      Cookie cookie = cookies.get(i);
      cookieHeader.append(cookie.name()).append('=').append(cookie.value());
    }
    returncookieHeader.toString(); }}Copy the code

First, before chain.proceed() executes, a header is added to the request: “Content-type”, “Content-Length” or “transfer-encoding”, “Host”, “Connection”, “accept-encoding”, “Cookie”, “user-agent”, The network layer is actually executable requests. Among them, notice that there is no cookie handling by default, requiring us to configure our own cookieJar when initializing OkhttpClient.

Chain.proceed () after executing, store the cookie from the response header into the cookieJar (if any), and if the request header “accept-encoding is not manually added: Gzip “, then the responseBody — GzipSource is created to extract automatically, and a new response is constructed to return.

It looks like BridgeInterceptor is a little easier to understand, too.

CacheInterceptor – CacheInterceptor

CacheInterceptor, a CacheInterceptor that provides access to the network request cache. We make a network request, and if it is sent and read through the network every time, it is inefficient. If the same request has been executed once before, can the result be saved and used directly this time? CacheInterceptor does just that, using local caching wisely, effectively reducing network overhead and response latency.

Before parsing the CacheInterceptor source code, take a look at the HTTP caching mechanism:

First request:

Second request:

Cache expiration
Whether it is modified after expiration
Thoroughly understand HTTP caching mechanisms and principles

The CacheInterceptor Intercept method is coded as follows:

  public Response intercept(Chain chain) throws IOException {
  	// Use reques' URL as a candidate to fetch the response from the cache (CacheStrategy determines whether to use it).Response cacheCandidate = cache ! =null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
	// Get cache policy based on request and candidate Response.
	// Cache policy determines whether to use cache: Strategy.net workRequest is null, no network is used; Strategy. cacheResponse is null and no cache is used.
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;
	
	// Update statistics according to the cache policy: number of requests, number of network requests, and number of cache uses
    if(cache ! =null) {
      cache.trackResponse(strategy);
    }
	// There is a cache but it cannot be used
    if(cacheCandidate ! =null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If the network request and cache are not available, return 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 you don't use the network, cacheResponse is no longer empty.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

	// networkRequest! = NULL (cacheResponse may be null or! Null)
	//networkRequest ! = null, the network request is ready, so the interceptor chain continues processing
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null&& cacheCandidate ! =null) { closeQuietly(cacheCandidate.body()); }}// If the network request returns 304, the server resource has not changed, then the network response and the cache response are combined, and then the cache is updated.
    if(cacheResponse ! =null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))/ / in combination with the header
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())// Request events
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())// Accept events
            .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();
        cache.update(cacheResponse, response);
        return response;
      } else {
      	// If it is not 304, the server resource has been updated
        closeQuietly(cacheResponse.body());
      }
    }

    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if(cache ! =null) {
      // Network responses are cacheable (neither request nor response headers cache-control is 'no-store')
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Write cache
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }
      
	  //OkHttp only caches get requests by default, because GET requests are usually persistent, and POST is usually an interactive operation that doesn't make much sense to cache
	  // Remove the cache without a GET request
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.}}}return response;
  }
Copy the code

The overall idea is clear: Use the CacheStrategy to determine whether and how to use caching.

How CacheStrategy is captured is not covered here. You need to know: Strategy.net workRequest is null and no network is used. Strategy. cacheResponse is null and no cache is used.

So following this line of thinking, a series of judgments follows:

  1. If networkRequest and cacheResponse are both null, that is, networkRequest and cache are unavailable, 504 is returned

  2. If networkRequest is null, cacheResponse is not null. If networkRequest is null, cacheResponse is not null

  3. If networkResponse is not null, whether cacheResponse is null or not, the device requests the network and obtains networkResponse

  4. If the value of cacheResponse is not null and the value of networkresponse. code is 304, the server resource is not modified and the cache is valid. Then, combine the networkResponse with the cacheResponse and update the cache

  5. Then 3, if cacheResponse is null or if cacheResponse is not null but NetworkResponse. code is not 304, write to the cache, return a response, end ~

Moving on, the logic above is based on CacheStrategy, so let’s take a look at how it is generated, which looks like a line of code:

    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Copy the code

If you pass the request request and candidate cache cacheCandidate to the Factory class, call the get method.

    public Factory(long nowMillis, Request request, Response cacheResponse) {
      this.nowMillis = nowMillis;
      this.request = request;
      this.cacheResponse = cacheResponse;

      if(cacheResponse ! =null) {
      	// Get the request time and response time of the candidate cache, and get the expiration time, modification time, resource marker, and so on from the header (if any).
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
        Headers headers = cacheResponse.headers();
        for (int i = 0, size = headers.size(); i < size; i++) {
          String fieldName = headers.name(i);
          String value = headers.value(i);
          if ("Date".equalsIgnoreCase(fieldName)) {
            servedDate = HttpDate.parse(value);
            servedDateString = value;
          } else if ("Expires".equalsIgnoreCase(fieldName)) {
            expires = HttpDate.parse(value);
          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
            lastModified = HttpDate.parse(value);
            lastModifiedString = value;
          } else if ("ETag".equalsIgnoreCase(fieldName)) {
            etag = value;
          } else if ("Age".equalsIgnoreCase(fieldName)) {
            ageSeconds = HttpHeaders.parseSeconds(value, -1); }}}}public CacheStrategy get(a) {
      CacheStrategy candidate = getCandidate();

      if(candidate.networkRequest ! =null && request.cacheControl().onlyIfCached()) {
        // We're forbidden from using the network and the cache is insufficient.
        return new CacheStrategy(null.null);
      }

      return candidate;
    }
Copy the code

The Factory constructor gets the request time, response time, expiration time, modification time, resource tag, and so on for the candidate response.

The get method calls getCandidate() to get an instance of the cache policy.

    private CacheStrategy getCandidate(a) {
      // No cache: network request
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }

      // HTTPS, but no handshake: network request
      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }

      // Network response not cacheable (request or response header cache-control is 'no-store') : network request
      if(! isCacheable(cacheResponse, request)) {return new CacheStrategy(request, null);
      }
	  // Request header cache-control is no-cache or request header "if-modified-since" or "if-none-match" : network request
	  // do not use caching or request manually added headers "if-modified-since" or "if-none-match"
      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }

      CacheControl responseCaching = cacheResponse.cacheControl();

	   // Cache age
      long ageMillis = cacheResponseAge();
      // Cache validity period
      long freshMillis = computeFreshnessLifetime();
	  // Compare the validity period in the request header with a smaller value
      if(requestCaching.maxAgeSeconds() ! = -1) {
        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
      }

	  // Minimum acceptable remaining validity period (Min-fresh indicates that the client is unwilling to accept the cache of min-fresh remaining validity period <=min-fresh).
      long minFreshMillis = 0;
      if(requestCaching.minFreshSeconds() ! = -1) {
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
      }
	  // Maximum acceptable expiration time (a max-stale directive indicates that a client is willing to accept a cache that has expired by 1 hour, for example)
      long maxStaleMillis = 0;
      if(! responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() ! = -1) {
      		// The first judgment is whether it is required to go to the server to verify the resource status
      		// Second judgment: get a max-stale value. If it is not equal to -1, the cache can be used for a specified period of time after expiration
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }
      
	  If the response header does not require ignoring the local cache and the consolidated cache age is less than the consolidated expiration time, then the cache is available
      if(! responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) { Response.Builder builder = cacheResponse.newBuilder();// If the minimum acceptable remaining valid time is not met, add a 110 warning
        if (ageMillis + minFreshMillis >= freshMillis) {
          builder.addHeader("Warning"."110 HttpURLConnection \"Response is stale\"");
        }
        / / isFreshnessLifetimeHeuristic said there was no expiration time, so is more than one day, will add a 113 warning
        long oneDayMillis = 24 * 60 * 60 * 1000L;
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
          builder.addHeader("Warning"."113 HttpURLConnection \"Heuristic expiration\"");
        }
        
        return new CacheStrategy(null, builder.build());
      }

      // Cache is out of date
      // Then find Etag, lastModified, servedDate in the cache
      String conditionName;
      String conditionValue;
      if(etag ! =null) {
        conditionName = "If-None-Match";
        conditionValue = etag;
      } else if(lastModified ! =null) {
        conditionName = "If-Modified-Since";
        conditionValue = lastModifiedString;
      } else if(servedDate ! =null) {
        conditionName = "If-Modified-Since";
        conditionValue = servedDateString;
      } else {
      	// Execute the normal network request
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }

	  // If so, add it to the header of the network request.
      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
          
      ConditionalRequest conditionalRequest conditionalRequest conditionalRequest conditionalRequest conditionalRequest conditionalRequest conditionalRequest conditionalRequest If yes, return to 304. If not, the network request will be executed.
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }
Copy the code

The same judgment that has entered a number of columns:

  1. No cache, HTTPS but no handshake, request no cache, ignore cache, or manually configure cache expiration are all direct network requests.
  2. If none of the above is true, then a cache is used (perhaps with a warning) if the cache is not expired.
  3. If the cache expires, but the response header has Etag, Last-Modified, Date, these headers are added for conditional network requests.
  4. If the cache expires and the response header is not set to Etag, last-Modified, Date, the network request is made.

Going back to the get() method:

    public CacheStrategy get(a) {
      CacheStrategy candidate = getCandidate();
      if(candidate.networkRequest ! =null && request.cacheControl().onlyIfCached()) {
        // We're forbidden from using the network and the cache is insufficient.
        return new CacheStrategy(null.null);
      }
      return candidate;
    }
Copy the code

If a cache exists, it is invalid. If a cache exists, it is invalid. If a cache exists, it is invalid, and both values are null.

Ok, here okHTTP cache mechanism source code is finished. As you can see, okHTTP’s caching mechanism is consistent with the first two diagrams of HTTP’s caching mechanism, but with a lot of added detail.

Also, notice that reads and writes to the cache are done through InternalCache. InternalCache is passed in as client.internalCache() when creating a CacheInterceptor instance. InternalCache is used internally by OKHTTP, like a proxy, and InternalCache instances are cache-like attributes. The Cache is passed in when we initialize OkHttpClient. So there is no caching if no Cache instance is passed in.

        OkHttpClient client = new OkHttpClient.Builder()
                .cache(new Cache(getExternalCacheDir(),500 * 1024 * 1024))
                .build();
Copy the code

Okhttp DiskLruCache allows you to add, delete, change, or query a Cache. This is not the focus of this article.

conclusion

Well, the content of this article is finished here, or quite rich. To explain in detail the working principle of three interceptors: RetryAndFollowUpInterceptor, BridgeInterceptor, CacheInterceptor CacheInterceptor of them request cache handling, is the important knowledge points. So far, I haven’t seen actual network connections and request response reads and writes, which will be covered in the next article – the remaining two interceptors are ConnectInterceptor and CallServerInterceptor. (Otherwise it would be too long 😁)

Welcome to continue to follow, thank you!

.

Your likes, comments, favorites, forwarding, is a great encouragement to me!

.

Welcome to follow my official account: