Introduction to the

On Android, the most popular web requests are OkHttp and Retrofit, which are actually built on top of OkHttp. Based on the previous articles, let’s take a closer look at this framework today.

The previous articles are doing the basics for this article ~

Deep parsing of Java thread pools

Socket Sends Http/Https requests

2020 is over, and the network model is still unclear? -OIS, TCP/IP, HTTP

A simple OkHttp request

From the client’s point of view, a complete Http request can be simplified into three steps,

1, assemble Request (Request method, URL, Request etc.)

2. The client sends a request

3. Receive the response from the server and parse the data

Following these steps, the code for making a network request in the OkHttp framework is as follows:

//构造Request
Request req=new Request.Builder()
                        .url(url)
                        .build();
// Construct an HttpClient
OkHttpClient client=new OkHttpClient();
// Send the request
client.newCall(req)
        .enqueue(new Callback() {
            // Get the Response data from the server
            @Override
            public void onResponse(Call arg0, Response arg1) throws IOException {}@Override
            public void onFailure(Call arg0, IOException arg1) {
                // TODO Auto-generated method stub}});Copy the code

You see, these simple steps are consistent with our normal logical thinking. That’s easy enough to use.

2. OkHttp framework design

First, the entire processing flow chart of OKHttp framework is given:

2.1. Build the Request-Builder pattern

public final class Request {... Omit code...public static class Builder {
    private HttpUrl url;
    private String method;
    private Headers.Builder headers;
    private RequestBody body;
    privateObject tag; }}Copy the code

The Builder pattern is familiar, so only the construction parameters are given here.

Building a Request, ideally, should be an object with the ability to Request. In fact, OkHttp encapsulates the content that users need to care about (such as URL, method, header, etc.) in Request for the sake of simple use by the framework users, while the specific network Request content is stripped inside the framework.

In practice, it is true that all the Request content that the framework user needs to care about is in the Request

Construct the OkHttpClient object

public class OkHttpClient implements Cloneable.Call.Factory.WebSocketCall.Factory {...public static final class Builder {
    Dispatcher dispatcher;
    Proxy proxy;
    List<Protocol> protocols;
    List<ConnectionSpec> connectionSpecs;
    final List<Interceptor> interceptors = new ArrayList<>();
    final List<Interceptor> networkInterceptors = new ArrayList<>();
    ProxySelector proxySelector;
    CookieJar cookieJar;
    Cache cache;
    InternalCache internalCache;
    SocketFactory socketFactory;
    SSLSocketFactory sslSocketFactory;
    CertificateChainCleaner certificateChainCleaner;
    HostnameVerifier hostnameVerifier;
    CertificatePinner certificatePinner;
    Authenticator proxyAuthenticator;
    Authenticator authenticator;
    ConnectionPool connectionPool;
    Dns dns;
    boolean followSslRedirects;
    boolean followRedirects;
    boolean retryOnConnectionFailure;
    int connectTimeout;
    int readTimeout;
    intwriteTimeout; }...@Override 
  public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */); }... }Copy the code

OkHttpClient is still built using the builder pattern.

The first thing to note is that OkHttpClient has a Dispatcher in its Builder, which means that OkHttpClient has a Dispatcher object that can make asynchronous requests

More notably, OkHttpClient implements the Call.Factory interface, creates an instance of the RealCall class (the Call implementation class), and passes a reference to the OkHttpClient object itself as well as the Request object.

As you can see from the simple use of OkHttp at the beginning of this article, before sending the request, the newCall method is called and a Call object is created. In fact, the RealCall object holds a reference to the OkHttpClient and a reference to the Request object, enabling later requests to be made using both objects

2.3 Call interface and its functions

Let’s first look at the Call interface implemented by RealCall

public interface Call extends Cloneable {
  Request request(a);

  Response execute(a) throws IOException;

  void enqueue(Callback responseCallback);

  void cancel(a);

  boolean isExecuted(a);

  boolean isCanceled(a);

  Call clone(a);

  interface Factory {
    Call newCall(Request request); }}Copy the code

Basically all the methods we call on a request are defined in this interface. For example, execute is invoked in synchronous requests and enQueue is invoked in asynchronous requests. This interface is arguably the operational heart of the OkHttp framework. After building the OkHttpClient and Request and calling the newCall method to create the Call object, we are holding the Call object to make the Request.

We continue to look at the source code as shown in the above flowchart. When a request is sent, it is dropped into the request queue, which calls the EnQueue method of RealCall

@Override 
 public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    // It is easy to read, but this part of the code logic is omitted, only the key steps are shown
   // Get the diapatcher from okhttpClient
   Dispatcher dispatcher=client.dispatcher()
   // dump the asynchronous request Runnable into the Dispatcher
   /* * This AsyncCall class implements the Runnable interface *responseCallback which is the response callback we expect */
   dispatcher.enqueue(new AsyncCall(responseCallback));  
  }
Copy the code

2.4. Dispatcher performs requests

Let’s look first at the Enqueue method in the Dispatcher Dispatcher and then at the AsyncCall operation

. .// Queue waiting for asynchronous execution
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  // An asynchronous queue in execution
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  // The queue is synchronizing itself
  private final Deque<RealCall> runningSyncCalls = newArrayDeque<>(); .synchronized void enqueue(AsyncCall call) {
    // If the request being executed is less than the set value, the default is 64
    // The number of requests to the same host is smaller than the specified value. The default value is 5
    if (runningAsyncCalls.size() < maxRequests &&
            runningCallsForHost(call) < maxRequestsPerHost) {
        // Add to the queue to start executing the request
      runningAsyncCalls.add(call);
      // Get the current thread pool, or create one
      ExecutorService mExecutorService=executorService();
      // Execute thread
      mExecutorService.execute(call);
    } else {
        // Add to wait queuereadyAsyncCalls.add(call); }}Copy the code

Now let’s look at the class AsyncCall implements the Runnable interface

// It is an inner class of RealCall
//NamedRunnable implements the Runnable interface, encapsulating the run() method as execute().
final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    String host(a) {
      return originalRequest.url().host();
    }

    Request request(a) {
      return originalRequest;
    }

    RealCall get(a) {
      return RealCall.this;
    }

    @Override 
    protected void execute(a) {
      boolean signalledCallback = false;
      try {
        // We get the response here, so we can be sure that the request was made in this method
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this.new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response); }}catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e); }}finally {
        client.dispatcher().finished(this); }}}... .// Where the request is made and the response is received
    Response getResponseWithInterceptorChain(a) throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    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));
    // Issued after a series of operations on request
    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null.null.null.0, originalRequest);
    return chain.proceed(originalRequest);
  }
Copy the code

In getResponseWithInterceptorChain approach, you can see a lot of Interceptor. By name, there are retries, caches, requests. As mentioned earlier, Request is not the whole thing, and OkHttp hides some operations for us. As you can see here, it is handled for us in the Interceptor. This is where the OkHttp framework comes in.

Key classes: OkHttpClient, Request, Call, RealCall, Dispatcher, Response, Interceptor, etc

Are you clear about what each of these key classes is responsible for?

Interceptor: Interceptor

An Interceptor, as its name implies, does something about a Request or Response, but OkHttp uses a chain of responsibility mode to chain all the interceptors together to ensure that they are executed one by one

3.1 Interceptor interface design

Let’s take a look at the Interceptor interface

public interface Interceptor {
  // There is only one interface method
  Response intercept(Chain chain) throws IOException;
    // The chain of responsibility mode is coordinated
  interface Chain {
    // contains the request Resquest
    Request request(a);
    / / get the Response
    Response proceed(Request request) throws IOException;
    // Get the current network connection
    Connection connection(a); }}Copy the code

The overall design of this piece uses the chain of responsibility mode, while in the framework of OkHttp, the effect of using the chain of responsibility mode is to intercept the request and response, and achieve the effect of decoupling various processing. To help you understand, draw a flow chart

In the OkHttp implementation, the chain of responsibility flows across the interceptors. The chain that wraps the Request passes through the interceptors, and the response is reversed from each interceptor.

3.2 Responsibility chain mode to achieve the interceptor function

Let’s look at the OkHttp source code to implement this process

Response getResponseWithInterceptorChain(a) throws IOException {

    List<Interceptor> interceptors = new ArrayList<>();
    // The developer's own Interceptor
    interceptors.addAll(client.interceptors());
    // This Interceptor is a redirection that handles request failures
    interceptors.add(retryAndFollowUpInterceptor);
    // The Interceptor's job is to add headers or other information to the request
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    // The Interceptor's responsibility is to check whether the cache exists, read the cache, update the cache, etc
    interceptors.add(new CacheInterceptor(client.internalCache()));
    // The Interceptor's job is to establish a connection between the client and the server
    interceptors.add(new ConnectInterceptor(client));
    if(! forWebSocket) {// Add a developer - defined netlayer interceptor
      interceptors.addAll(client.networkInterceptors());
    }
    // The Interceptor's job is to send specific requests
    interceptors.add(new CallServerInterceptor(forWebSocket));

    // a chain that wraps this request
    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null.null.null.0, originalRequest);
    Pass the chain to the first Interceptor
    return chain.proceed(originalRequest);
  }
Copy the code

From the above code, the Chain is finally built and execution ends, so the flow of the interceptor is implemented in the Chain. Let’s move on to the RealInterceptorChain implementation

public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final StreamAllocation streamAllocation;
  private final HttpCodec httpCodec;
  private final Connection connection;
  private final int index;
  private final Request request;
  private int calls;

  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, Connection connection, int index, Request request) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request; }...@Override 
 public Response proceed(Request request) throws IOException {
            Proceed (.....) Methods.
    return proceed(request, streamAllocation, httpCodec, connection);
  }

    This method is used to get the next Interceptor in the list and call its Intercept () method
  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, Connection connection) throws IOException {
    if (index >= interceptors.size()) throw newAssertionError(); calls++; .// Call the next interceptor in the chain.
    // Index +1
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Get the first Interceptor from the list
    Interceptor interceptor = interceptors.get(index);
    // Then call the Interceptor's Intercept () method and wait for a Response
    // When the interceptor executes, it can call the proceed method from next to proceed the entire linkResponse response = interceptor.intercept(next); . .return response;
  }
Copy the code

Because this is the code logic level, we view the time to pay attention to see the above code comment ha. The most critical steps are the above notes. The interceptor takes the Chain Chain(with all interceptors and the index of the next interceptor) and executes the next interceptor in the Chain within the interceptor, one after the other, to go through the Chain

Below we see RetryAndFollowUpInterceptor this interceptor implementation, whether or not according to what we think, call the proceed of the chain method

.// Call its own intercept() method directly
 @Override 
 public Response intercept(Chain chain) throws IOException { Request request = chain.request(); . . Response response =null;
      boolean releaseConnection = true;
      try {
        / / here by continuing to call RealInterceptorChain. Proceed () this method
        Get the next Interceptor from the RealInterceptorChain list
        // Then continue calling interceptor.Intercept () and wait for a Response to return
        response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null.null);
        releaseConnection = false;
      } catch (RouteException e) {
        ...
      } catch (IOException e) {
       ...
      } finally{... }}}...Copy the code

This explains how the OkHttp interceptor part uses the chain of responsibility pattern to intercept requests and responses.

Do you understand?

But how does OkHttp actually make network requests? It’s in the CallServerInterceptor, the last interceptor. Requests are ultimately made using sockets

In addition, there is no Dispatcher Dispatcher, which is implemented internally using a cache-type thread pool. Here’s the code for a quick demonstration

  public synchronized ExecutorService executorService(a) {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher".false));
    }
    return executorService;
  }
Copy the code

If you want to know more about thread pool, you can find out more about thread pool parameters in the Java thread pool.