1. Introduction

First of all why I want to write this blog, mainly because the network is now using okHTTP3, so in the interview, will ask about the principle of OKHTTP, and the principle of the Internet, also looked at the next, either too short, the core of a brush, or long, looking at the circle. So I wanted to see if I could explain okhttp3 in the simplest way possible. Of course, if you are not familiar with this framework, you can click here to learn all about OKHttp3.

Look at the picture first

This is a simple get request. Let’s start with 1,2,3,4. Let’s start with point 1.


2. Create okhttp

The first thing you need to do to use a network request is initialize it and see what properties it has.

File location: okhttpClient.java

    final Dispatcher dispatcher;/ / scheduler
    final Proxy proxy;/ / agent
    final List<Protocol> protocols;/ / agreement
    final List<ConnectionSpec> connectionSpecs;// Transport layer version and connection protocol
    final List<Interceptor> interceptors;/ / the interceptor
    final List<Interceptor> networkInterceptors;// Network interceptor
    final EventListener.Factory eventListenerFactory;
    final ProxySelector proxySelector;// Proxy selector
    final CookieJar cookieJar;//cookie
    final Cache cache;/ / cache cache
    final InternalCache internalCache;// Internal cache
    final SocketFactory socketFactory;/ / socket factory
    final SSLSocketFactory sslSocketFactory;// The socket factory is used for HTTPS
    final CertificateChainCleaner certificateChainCleaner;// Verify the acknowledgement reply, which applies to the host name of the HTTPS request connection
    final HostnameVerifier hostnameVerifier;// Confirm the host name
    final CertificatePinner certificatePinner;/ / certificate chain
    final Authenticator proxyAuthenticator;// Proxy authentication
    final Authenticator authenticator;// Local province authentication
    final ConnectionPool connectionPool;// Link pool overuses connections
    final Dns dns; / / domain name
    final boolean followSslRedirects;// SSL redirection
    final boolean followRedirects;// Local redirect
    final boolean retryOnConnectionFailure;// Retry connection failed
    final int connectTimeout;// Connection timed out
    final int readTimeout;// Read times out
    final int writeTimeout;// Write timeout
Copy the code

Okay, so that’s it, that’s all, that’s basically enough, but how to assign is a problem, you can’t write it all in the constructor, right? What are some good design patterns? The kind that separates usage from complex builds? Congratulations, that’s the builder pattern.

Sure enough, okHttp3 uses it too. Look at the source code

public static final class Builder {...public Builder(a) {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      connectTimeout = 10 _000;
      readTimeout = 10 _000;
      writeTimeout = 10 _000;
      pingInterval = 0;
    }

    public OkHttpClient build(a) {
      return new OkHttpClient(this); }... }Copy the code

That’s easy, with the default values set, so when we call it from outside it’s easy, just write it like this.

OkHttpClient mClient = new OkHttpClient.Builder() // Builder pattern, create instance
                           .build();
Copy the code

OK, step 1 is done

To summarize

The first step is to create an instance of okHttp3 using the Builder pattern, which encapsulates the necessary properties for use, such as timeout, interceptor, and so on


3. Create a Request

This is step 2, so step 1 is to create an instance of okHttp3, so step 2 is to create the request information. So without further ado, let’s see what properties it has.

File location: Request

  final HttpUrl url; // Interface address
  final String method; // Post or get
  final Headers headers; // Header field of the Http message
  final RequestBody body; If the request is a GET request, the body object passes null. If the request is a POST request, it needs to be set.
  final Object tag;
Copy the code

Ok, so if you want to make it easy and easy to automatically set the defaults, let’s continue with our Builder mode

  public static class Builder {
    HttpUrl url;
    String method;
    Headers.Builder headers;
    RequestBody body;
    Object tag;

    public Builder(a) {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }

    public Request build(a) {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this); }}Copy the code

When you use it directly, it gives you a default Get. But now that it’s wrapped, we can call it this way

 Request mRequest = new Request.Builder() // Builder pattern to create request information
                .url("https://www.baidu.com")
                .build();
Copy the code

OK, step 2 is done

To summarize

The second step is to create an instance of the request information using the Builder pattern, which encapsulates the necessary attributes for use, such as request mode, request header information, interface address, and so on


4. Create the Call object

Ok, moving on to step 3, first of all, from the okHttpClient and the Request object, we can build the Call object that actually makes the Request. Looking at the source code, we know that call is an interface, and it is the RealCall that actually makes the request, and we create it

File location: okhttpClient.java

  @Override public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }
Copy the code

Look what it did

  RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    final EventListener.Factory eventListenerFactory = client.eventListenerFactory();

    this.client = client; // The okHttpClient instance created in step 1
    this.originalRequest = originalRequest; // Request instance created in step 2
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); // Redirect interceptor, it will be said later

    // TODO(jwilson): this is unsafe publication and not threadsafe.
    this.eventListener = eventListenerFactory.create(this);
  }
Copy the code

Okay, step 3 is done

To summarize

Step 3 is to create the RealCall object. The real request is handed to the RealCall class, which implements the Call method and is the real core code.


5. Realcall asynchronous request (part 1)

Start analyzing the last step and this is the key, so let’s do it step by step. Let’s start with our call

call.enqueue(new Callback(){ ... });
Copy the code

Then look at the realCall.enqueue () method

@Override 
public void enqueue(Callback responseCallback) {
    1.synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    2.client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
Copy the code
  1. Synchronized (this) ensures that each call can only be executed once and cannot be repeated. If you want the exact same call, you can use the following methods
  2. Dispatcher (). Enqueue (new AsyncCall(responseCallback))

So let’s look at new AsyncCall(responseCallback)

 final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

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

Encapsulate our callback and inherit the Runnable interface. Ok, let’s move on to client.dispatcher().enqueue().

File location: dispatcher.java

synchronized void enqueue(AsyncCall call) {
    1.if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      2.runningAsyncCalls.add(call);
      3.executorService().execute(call);
    } else {
      4.readyAsyncCalls.add(call); }}Copy the code
  1. RunningAsyncCalls. Size () < maxRequests for a maximum of 64 requests. RunningCallsForHost (call) < maxRequestsPerHost)
  2. If the condition is met, the runnable being requested is added to the asynchronous request queue being executed.
  3. This AsyncCall is then executed through the thread pool
  4. AsyncCall is added to the readyAsyncCalls queue if the number of tasks in progress exceeds the set maximum or if the number of hosts currently requested by the network exceeds the set maximum.
To summarize

In the call. The enqueue (new Callback () {… }) after execution, the first thing to do is

Call realcall.call.enqueue () to check whether the current call has been executed and raise an exception. If not, wrap callback as AsyncCall. Then call the dispatcher.enqueue() method (the Dispatcher dispatcher created in okHttpClient).

2. In the dispatcher.enqueue() method, check whether the number of ongoing requests and the number of hosts requesting network requests exceeds the maximum value. If the maximum value is exceeded, the request is placed on a waiting queue; if not, it is placed on an executing queue and the thread pool is called to execute it.


6. Realcall asynchronous request (part 2)

6.1 the executorService. Execute

After lunch, we will continue to analyze the source code, and we will use the thread pool to execute it if the condition is met

executorService().execute(call);
Copy the code

Take a look at our thread pool

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

If the maximum value is set to integer.max_value, it will affect the performance of the core thread. The answer is no, because in the dispatcher.enqueue() method, there is a limit on the number of requests. If the number of requests exceeds the specified maximum, they will be placed in a waiting queue.

Executorservice.execute (Call), which executes the run() method of the call method, the AsyncCall run method, which is actually in its parent NamedRunnable. In the namedrunable.run () method, we actually call execute(), which is implemented by subclasses, which calls asynccall.execute ().

Executorservice.execute (call) -> namedrunnable.run () -> asynccall.execute () So asynccall.execute ()

6.2 AsyncCall. The execute ()

Look at the source file location: realcall.java

@Override protected void execute(a) {
      boolean signalledCallback = false;
      try {
        1.Response response = getResponseWithInterceptorChain();
        2.if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this.new IOException("Canceled"));
        } else {
          signalledCallback = true;
          3.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 {
        4.client.dispatcher().finished(this); }}Copy the code
  1. Execute the interceptor chain and return Response
  2. Whether blocker redirection interceptor in the chain has been canceled, if cancelled, is executed responseCallback. OnFailure (), this is our outside in step 3, pass the Callback method Callback the onFailure () () method.
  3. If not, go onResponse, which is the onResponse() method in Callback(). Returns the result. Of course, this is all in the child thread.
  4. This is actually calling the Finished method in the Dispatcher method. So let’s see


6.3 the dispatcher. Finished ()

Look at the source code

  void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
  }

  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      1.if(! calls.remove(call))throw new AssertionError("Call wasn't in-flight!");
      2.if (promoteCalls) promoteCalls();
      3.runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0&& idleCallback ! =null) { idleCallback.run(); }}Copy the code
  1. Remove the request from the executing task queue
  2. Call promoteCalls() to adjust the request queue
  3. Recalculate the number of requests

6.4 the dispatcher. Finished ()

private void promoteCalls(a) {
    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.}}Copy the code

It is simply a matter of traversing the waiting queue of requests and joining the queue of executing requests until the number of concurrent requests and the number of hosts currently requesting network requests reaches the upper limit.

At this point, okHTTP asynchrony analysis is complete

1. What is dispatcher? The Dispatcher is used to maintain the status of requests and maintain a thread pool. Used to execute requests.

Isn’t that easy, young man? Is it over? You think too much. Let’s analyze the core part


7. getResponseWithInterceptorChain()

Look at the source

Response getResponseWithInterceptorChain(a) throws IOException {
    // Add interceptor, responsibility chain mode
    List<Interceptor> interceptors = new ArrayList<>();

    // The intercept set when configuring okhttpClient is set by the user
    interceptors.addAll(client.interceptors());

    // Handles retries and redirects after failures
    interceptors.add(retryAndFollowUpInterceptor);

    /** Is responsible for converting user-constructed requests into requests sent to the server, and converting responses returned by the server into user-friendly response handling configuration requests and other information. Bridge from application code to network code. First, it builds network requests based on user requests. Then it continues to call the network. Finally, it builds the user response from the network response. * /
    interceptors.add(new BridgeInterceptor(client.cookieJar()));

    The processing cache configuration returns cached responses based on conditions (response caching exists and is set to invariant, or the response is within an expiration date)
    // Set request headers (if-none-match, if-modified-since, etc.) the server may return 304(unmodified)
    // The user can configure the cache interceptor
    interceptors.add(new CacheInterceptor(client.internalCache()));

    // The connection server is responsible for establishing the connection with the server. This is the real request network
    interceptors.add(new ConnectInterceptor(client));
    if(! forWebSocket) { interceptors.addAll(client.networkInterceptors()); }// Perform the flow operations (write out the request body, get the response data) to send the request data to the server and read the response data from the server
    // Encapsulates and parses HTTP request packets
    interceptors.add(new CallServerInterceptor(forWebSocket));

    // Responsibility chain, add the above interceptor to the responsibility chain
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null.null.null.0, originalRequest);
    return chain.proceed(originalRequest);
  }
Copy the code

Add interceptors to a collection, use the interceptor collection as a construction parameter, create an object (RealInterceptorChain), and call its proceed method. And then you keep going down.

I was going to write the logic of interceptors again, but, ni, my husband pinched the numbers and realized that you are easy to confuse, so let’s write our own




1. Define interfaces

public interface Interceptor {

    interface Chain{

        String request(a);

        String proceed(String request); }}Copy the code

2. To define a RetryAndFollowInterceptor (redirect interceptor)

public class RetryAndFollowInterceptor implements Interceptor{

    @Override
    public String interceptor(Chain chain) {

        System.out.println("RetryAndFollowInterceptor_start");
        String response = chain.proceed(chain.request());
        System.out.println("RetryAndFollowInterceptor_start");

        returnresponse; }}Copy the code

2. Define another BridgeInterceptor

public class BridgeInterceptor implements Interceptor{
    
    @Override
    public String interceptor(Chain chain) {

        System.out.println("BridgeInterceptor_start");
        String response = chain.proceed(chain.request());
        System.out.println("BridgeInterceptor_end");
        
        returnresponse; }}Copy the code

3. The last interceptor, CallServerInterceptor

public class CallServerInterceptor implements Interceptor {

    @Override
    public String interceptor(Chain chain) {

        System.out.println("CallServerInterceptor_start");
        System.out.println("---------- send data to server: data is"+ chain.request());
        System.out.println("CallServerInterceptor_end");

        return "Successful landing."; }}Copy the code

4. Define a RealInterceptorChain object

public class RealInterceptorChain implements Interceptor.Chain{

    private List<Interceptor> interceptors;
    private int index;
    private String request;

    public RealInterceptorChain(List<Interceptor> interceptors, int index, String request) {

        this.interceptors = interceptors;
        this.index = index;
        this.request = request;
    }

    @Override
    public String request(a) {

        return request;
    }

    @Override
    public String proceed(String request) {

        if(index >= interceptors.size()) {

            return null;
        }

        // This is the responsibility chain mode, which adds its index+1 and creates a RealInterceptorChain object
        RealInterceptorChain next = new RealInterceptorChain(interceptors, index + 1, request);

        Interceptor Interceptor = interceptors.get(index);
        returnInterceptor.interceptor(next); }}Copy the code

5. Call it

public static void main(String[] args) {

       List<Interceptor> interceptors = new ArrayList<>();
       interceptors.add(new RetryAndFollowInterceptor());
       interceptors.add(new BridgeInterceptor());
       interceptors.add(new CallServerInterceptor());

       RealInterceptorChain chain = new RealInterceptorChain(interceptors, 0."");
       String result = chain.proceed("xiaoming, 123");
       System.out.println("---------- server returns:"+result);
}
Copy the code

Results 6.

Does that make sense? If you don’t, this diagram should make sense.

Does okHTTP feel easy? I haven’t introduced the interceptor yet, so let’s finally introduce the interceptor.

8. Introduction to interceptors

1. User-defined interceptors are used to intercept the server before establishing a link

interceptors.addAll(client.interceptors());
Copy the code

2, RetryAndFollowUpInterceptor retry or failure to redirect the interceptor

 interceptors.add(retryAndFollowUpInterceptor);
Copy the code

3. The BridgeInterceptor interceptor calibrates and ADAPTS interceptors that complement the content-type header of user-created requests

interceptors.add(new BridgeInterceptor(client.cookieJar()));
Copy the code

4. CacheInterceptor deals primarily with caching

interceptors.add(new CacheInterceptor(client.internalCache()));
Copy the code

5. The ConnectInterceptor creates a link with the server to create a usable RealConnection(encapsulating java.io and java.nio).

interceptors.add(new ConnectInterceptor(client));
Copy the code

6. User-defined interceptors are used to intercept after a link is established with the server. Only non-sockets can be set

if(! forWebSocket) { interceptors.addAll(client.networkInterceptors()); }Copy the code

7. The CallServerInterceptor sends requests and receives data from the server. The request is written to the IO stream and the response data is read from the IO stream

interceptors.add(new CallServerInterceptor(forWebSocket));
Copy the code

Here is just the interview shorthand, if you need details, please leave a message below.