OKHttp3 Part Source Reading

Posted by sribala on Sat, 11 May 2019 00:48:53 +0200

When we want to integrate OKHttp3 into our project, we need to use the OkHttpClient.Builder() method to create an instance of OkHttpClient, which contains the following attributes:

  final Dispatcher dispatcher;
  final Proxy proxy;
  final List<Protocol> protocols;
  final List<ConnectionSpec> connectionSpecs;
  final List<Interceptor> interceptors;
  final List<Interceptor> networkInterceptors;
  final ProxySelector proxySelector;
  final CookieJar cookieJar;
  final Cache cache;
  final InternalCache internalCache;
  final SocketFactory socketFactory;
  final SSLSocketFactory sslSocketFactory;
  final CertificateChainCleaner certificateChainCleaner;
  final HostnameVerifier hostnameVerifier;
  final CertificatePinner certificatePinner;
  final Authenticator proxyAuthenticator;
  final Authenticator authenticator;
  final ConnectionPool connectionPool;
  final Dns dns;
  final boolean followSslRedirects;
  final boolean followRedirects;
  final boolean retryOnConnectionFailure;
  final int connectTimeout;
  final int readTimeout;
  final int writeTimeout;
  final int pingInterval;

Attributes are very many, and the source code of this analysis is very relevant: Dispatcher - used for request distribution, Interceptor - interceptor.

Interestingly, when constructing OkHttpClient, we introduce the Builder mode, that is, when setting the value of an attribute of OkHttpClient, we return OkHttpClient itself, so that we can use chain coding to set the attributes of OkHttpClient in turn.

Next, each step of the network request is analyzed.

First, when we want to execute a network request, we usually call as follows:

reponse = okHttpClient.newCall(request).execute();

As you can see, we first call the newCall() method in okHttpClient and get an example of RealCall. Let's see what RealCall is.

final class RealCall implements Call {
  final OkHttpClient client;
  final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;

  /** The application's original request unadulterated by redirects or auth headers. */
  final Request originalRequest;
  final boolean forWebSocket;

  // Guarded by this.
  private boolean executed;

  RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }

  @Override 
  public Request request() {
    ...
  }

  @Override 
  public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

  private void captureCallStackTrace() {
    ...
  }

  @Override
  public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

  @Override 
  public void cancel() {
    ...
  }

  @Override 
  public synchronized boolean isExecuted() {
    ...
  }

  @Override 
  public boolean isCanceled() {
    ...
  }

 
  final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

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

    String host() {
      ...
    }

    Request request() {
      ...
    }

    RealCall get() {
      return RealCall.this;
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        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);
      }
    }
  }

  /**
   * Returns a string that describes this call. Doesn't include a full URL as that might contain
   * sensitive information.
   */
  String toLoggableString() {
    ...
  }

  String redactedUrl() {
    ...
  }

  Response getResponseWithInterceptorChain() throws IOException {
    ...
  }
}

There is too much code above, but at present only one method - execute() needs to be concerned, because this method is called when we make network requests, as follows:

reponse = okHttpClient.newCall(request).execute();

Next, the steps of execute() method are analyzed.

  @Override
  public Response execute() throws IOException {
    
    //Make judgments and do not allow requests to be repeated. Because each request is encapsulated as RealCall, and RealCall has a field called executed to save whether the current request has been executed.
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

As described in the code above, when the current request RealCall is not executed out of date, the execution of the request begins. First, the executed(this) method in the dispenser Dispatcher is invoked, and the current RealCall is used as the input parameter. So what exactly is Dispatcher like? Let's look at Dispatcher's code:

public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private Runnable idleCallback;

  /** Executes calls. Created lazily. */
  private ExecutorService executorService;

  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

You can see several more important member variables in Dispatcher

  1. maxRequests - Maximum number of requests
  2. maxRequestsPerHost - Maximum number of requests per host
  3. executorService - a thread pool
  4. readyAsyncCalls - Asynchronous Ready Waiting Queue
  5. Running AsyncCalls - Asynchronous Execution Queue
  6. Running SyncCalls - Synchronized execution queue.

When we execute the request, we first make the following call:

client.dispatcher().executed(this);

What is this line of code doing?

 /** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

Oh, it was adding the current request RealCall to the asynchronous execution queue.
Next, we use the interceptor to do a series of interception operations. We can see that in this try code, there is a final {} code segment behind it.

finally {
      client.dispatcher().finished(this);
}

What is this code doing? So far we haven't seen the real part of executing network requests, so will the execution of network requests be in the finished() method?
You can see that the finished(realcall) method is actually the called finished(runningSyncCalls, realcall, false) method.

 /** Used by {@code Call#execute} to signal completion. */
  void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
  }

So what's going on in the finished(runningSyncCalls, realcall, false) method, which has three parameters

  1. Task queue - runningSyncCalls
  2. Request task - realCall
  3. PromCall - What is this? Do not make

We paste out the code of the finished(runningSyncCalls, realcall, false) method:

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    //A runnable object, the real task executor
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

As you can see from the above, the third parameter of our finished() method is false, so it must be executed to if (promoteCalls) promoteCalls(); this step. So what did you do in the promoteCalls() method?

private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

As can be seen from the above, the promoteCalls() method mainly traverses the readyAsyncCalls for the asynchronous ready queue, extracts the request, adds the request to the asynchronous execution request queue, and executes it through the thread pool, that is, executorService().execute(call;), until the number of tasks in the asynchronous execution queue exceeds the maximum number of requests, it will exit.
At the same time, if there are no waiting ready requests or tasks being executed in the current distributor, idleCallback in Dispatcher will be used to execute the current task, which is actually to execute a runnable.

Topics: network DNS Attribute OkHttp