1. Write in front

Since working, most of the time is busy with various business. Business fulfillment is important, but I still believe that focusing on the basics is the way to go. Think of the use of so long OkHttp has not carefully read his source code, deeply ashamed. It’s time to look into it.

2. About OkHttp

2.1 introduction

An HTTP & HTTP/2 client for Android and Java applications OkHttp is a high-performance framework for handling web requests, contributed by Square (which also contributed Picasso)

2.2 advantage

  • Share sockets to reduce the number of requests to the server
  • Through connection pooling, the request latency is reduced;
  • Support GZIP to reduce data traffic;
  • Cache response data to reduce completely repetitive network requests;
  • There is no need to rewrite the network code in your program to use OkHttp. To achieve a OkHttp almost as well as the java.net.HttpURLConnection API;
  • OkHttp automatically recovers from many common connection problems. If your server is configured with multiple IP addresses, when the first IP connection fails, the next IP address is automatically attempted.

3. Start eating the source code

Note: This article code combing based on v3.10.0 version, recommended fromOn the lotClone a copy of the code and go through it with this article.

3.1 Describe the general analysis procedure first

This article will take the request process as the main line to comb OKHttp source code, the reason why not deep into the code details because too deep into the code implementation itself does not have much guiding significance, and easy to let beginners into the embarrassment of seeing the forest for the trees. Since OkHttp initiates a request when can be divided into synchronous or asynchronous from the point of view of synchronization. All the following source code combing is also analyzed from these two aspects. Let’s start with the flow chart:

The blog Piasy

3.2 Synchronizing Requests

3.2.1 How Do I Initiate a Synchronization Request?

The complete code is as follows:

OkHttpClient client = new OkHttpClient
                        .Builder().build();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}
Copy the code

3.2.2 Synchronizing the classes involved in the request

Following the demo in Section 3.2.1, we first analyze the roles played by each of the classes used, and then string all the knowledge together according to the requested flow.

3.2.2.1 Request class

An HTTP request. Instances of this class are immutable if their is null or itself immutable. Obviously, this class represents an HTTP request. Take a look at the source code:

public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final @Nullable RequestBody body;
  final Object tag;

  private volatile CacheControl cacheControl; // Lazily initialized.

  Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tag = builder.tag != null ? builder.tag : this;
  }
  ...
  public static class Builder {
    HttpUrl url;
    String method;
    Headers.Builder headers;
    RequestBody body;
    Object tag;

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

    Builder(Request request) {
      this.url = request.url;
      this.method = request.method;
      this.body = request.body;
      this.tag = request.tag;
      this.headers = request.headers.newBuilder();
    }

    public Builder url(HttpUrl url) {
      if (url == null) throw new NullPointerException("url == null");
      this.url = url;
      returnthis; }...Copy the code

The above code has been filtered so that we only look at the key parts. Request is used to describe an HTTP Request. This class contains the address of the Request, the method of the Request, the body of the Request, and other types of data. One important point: Building the use of the Request class requires the use of the inner class Builder, obviously, the typical Builder pattern.

3.2.2.2 OkHttpClient class

As the name suggests, this class is a “client role” for OKHttp, which is officially a “Call” factory. What is “Call”? It’s actually a request process, which I’ll skip here and introduce later. Let’s take a look at the source of OkHttpClient.

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {

   public OkHttpClient() { this(new Builder()); } OkHttpClient(Builder builder) { this.dispatcher = builder.dispatcher; . }... public static final class Builder { Dispatcher dispatcher; @Nullable Proxy proxy; List<Protocol> protocols; List<ConnectionSpec> connectionSpecs; final List<Interceptor> interceptors = new ArrayList<>(); final List<Interceptor> networkInterceptors = new ArrayList<>(); EventListener.Factory eventListenerFactory; ProxySelector proxySelector; CookieJar cookieJar; @Nullable Cache cache; @Nullable InternalCache internalCache; SocketFactory socketFactory; @Nullable SSLSocketFactory sslSocketFactory; @Nullable CertificateChainCleaner certificateChainCleaner; HostnameVerifier hostnameVerifier; CertificatePinner certificatePinner; Authenticator proxyAuthenticator; Authenticator authenticator; ConnectionPool connectionPool; Dns dns; boolean followSslRedirects; boolean followRedirects; boolean retryOnConnectionFailure; int connectTimeout; intreadTimeout;
    int writeTimeout;
    int pingInterval;

    public Builder() { dispatcher = new Dispatcher(); . }}Copy the code

The above code is simplified, and again, let’s just focus on the important points. OkHttpClient also provides a builder pattern to create an instance that configures the interceptor, read/write timeout, and other properties. Note that the OkHttpClient Builder already provides us with the default configuration, and we just need to override the custom properties as needed.

3.2.2.3 Call class

Frankly speaking, a Call represents the process of a request. Officially, this class represents a request that is ready to be executed. And the same Call instance cannot be executed twice. One thing to note here is that Call can copy an object with the same content as itself through the prototype pattern.

/**
 * A call is a request that has been prepared for execution. A call can be canceled. As this object
 * represents a single request/response pair (stream), it cannot be executed twice.
 */
public interface Call extends Cloneable {
  Request request();

  Response execute() throws IOException;

  void cancel();

  boolean isExecuted();

  boolean isCanceled();

  Call clone();

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

3.2.3 Synchronization Request Process

According to the demo in section 3.2.1, let’s sort out the process of synchronous request and connect the knowledge points mentioned in section 3.2.2. To initiate a request, the typical application code is: Response Response = client.newCall(request).execute(); So let’s look at what the newCall method of OkHttpClient does:

The newCall method content of OkHttpClient:

 /**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false/ *for web socket */);
  }

Copy the code

As you can see from the above code, the newCall method directly calls the realCall.newRealCall method. This RealCall class implements the Call interface. Take a look at the contents of realCall.newRealCall.

Realcall. newRealCall method body:

  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }
Copy the code

The above code is clear. The newRealCall method simply new a RealCall object and returns it.

Response Response = client.newCall(request).execute(); Based on the above code analysis, this code can be replaced with pseudocode Response Response = realCall.execute (); . So let’s take a look at the Execute method in RealCall.

RealCall. The execute method:

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

Focus on the above code in two places:

  • client.dispatcher().executed(this);This code flags the current Call once with the client’s Dispatcher. This method internally uses a name calledrunningSyncCallsThe Deque stored the current Call. Since the Dispatcher is not the main player in the process of synchronous calls, this will be a bit of a pit in the background when analyzing asynchronous calls.
  • Response result = getResponseWithInterceptorChain();Personally, I think this call is the soul of synchronous calls and OkHttp as a whole. Let’s focus on the analysis.

Look at getResponseWithInterceptorChain () the method of the body:

Response getResponseWithInterceptorChain() 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));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }
Copy the code

Even if you haven’t read the source code of OkHttp, you must have heard that the essence of OkHttp is the use of interceptors. Yes getResponseWithInterceptorChain this method is responsible for each of the interceptor in the chain of responsibility in series method. The logic of this method is easy to understand. It encapsulates the interceptors defined by the developer and the interceptors built in OkHttp into a List and then gives it to the RealInterceptorChain. Finally, it executes the proceed method of the RealInterceptorChain. So what is this RealInterceptorChain? In general terms, this class is a concrete chain of interceptors, and yes this is the typical chain of responsibility pattern.

RealInterceptorChain class code:

/**
 * A concrete interceptor chain that carries the entire interceptor chain: all application
 * interceptors, the OkHttp core, all network interceptors, and finally the network caller.
 */
public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final StreamAllocation streamAllocation;
  private final HttpCodec httpCodec;
  private final RealConnection connection;
  private final int index;
  private final Request request;
  private final Call call;
  private final EventListener eventListener;
  private final int connectTimeout;
  private final int readTimeout;
  private final int writeTimeout;
  private int calls;

  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
      EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) { this.interceptors = interceptors; this.connection = connection; . } public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if(this.httpCodec ! = null && ! this.connection.supportsUrl(request.url())) { throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if(this.httpCodec ! = null && calls > 1) { throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if(httpCodec ! = null && index + 1 < interceptors.size() && next.calls ! = 1) { throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null. if (response == null) { throw new NullPointerException("interceptor " + interceptor + " returned null"); } if (response.body() == null) { throw new IllegalStateException( "interceptor " + interceptor + " returned a response with no body"); } return response; }}Copy the code

When I first analyzed this, I was a little bit loopy. It took me a while to figure out how this chain of responsibility works. Finally moved out of the breakpoint play, finally see the light. Again complex chain of responsibility (because I compare water, so feel OkHttp chain of responsibility is more complex, welcome to clap brick) grasp two points is enough:

  • How to get an instance of the next link in the chain of responsibility
  • How is the chain of responsibility executed sequentially

With the above problems for breakpoint method, finally found the following core code:

// Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
Copy the code

This code uses a recursive call; if there is an instance of the interceptor next in the chain of responsibility, it is fetched and the intercept method on that instance is called. Previous analysis getResponseWithInterceptorChain this method when the interceptor is introduced. All interceptors in OkHttp need to implement the Interceptor interface. The only method for this interface is Response Intercept (Chain Chain) throws IOException; .

Since it’s a recursive call, when does it end? With this problem, we once again see getResponseWithInterceptorChain method to add the interceptor. In order to facilitate you to view, again posted this method source bar

Response getResponseWithInterceptorChain() 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));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }
Copy the code

As you can see, from the method body OkHttp after adding the developers to customize Interceptor, has added the RetryAndFollowUpInterceptor… CallServerInterceptor and so on. The conclusion here is that the chain of responsibility for interceptors ends in the CallServerInterceptor class. How does this result come about? Look at OkHttp first native Interceptor RetryAndFollowUpInterceptor code: mentioned OkHttp all Interceptor Interceptor interface is realized. So let’s take a look at the core method in Interceptor.

RetryAndFollowUpInterceptor intercept method of the interceptor:

 @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();

    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    this.streamAllocation = streamAllocation;

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false; }... }}Copy the code

Response = realchain.proceed (request, streamAllocation, null, null); Through this code, and began to call RealInterceptorChain proceed method, and RealInterceptorChain method to proceed the specific logic is to take out the responsibility chain up and down a blocker and execute the interceptor intercept method, In this way, a concrete circular logic is formed, and the chain of responsibility begins to execute perfectly. Moving on to the end of the chain of responsibility, since the CallServerInterceptor is the last link in the chain of responsibility, let’s take a look at its code.

The intercept method of CallServerInterceptor

@Override public Response intercept(Chain chain) throws IOException {
  HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
  StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
  Request request = chain.request();

  long sentRequestMillis = System.currentTimeMillis();
  httpCodec.writeRequestHeaders(request);

  if(HttpMethod.permitsRequestBody(request.method()) && request.body() ! = null) { Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength()); BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); request.body().writeTo(bufferedRequestBody); bufferedRequestBody.close(); } httpCodec.finishRequest(); Response response = httpCodec.readResponseHeaders() .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build();if (!forWebSocket || response.code() ! = 101) { response = response.newBuilder() .body(httpCodec.openResponseBody(response)) .build(); }if ("close".equalsIgnoreCase(response.request().header("Connection"))
      || "close".equalsIgnoreCase(response.header("Connection"))) { streamAllocation.noNewStreams(); } // Omit parts of the check codereturn response;
Copy the code

As you can see from the code above, in the CallServerInterceptor, the HttpCodec class is called to complete the final request. There are two implementation classes in OkHttp: Http1Codec and Http2Codec. These two implementation classes are corresponding to http1.x and Http2.x requests. Their internal specific requests depend on Okio, which is also socket-based. Back to the original topic, the intercept method of CallServerInterceptor does not call the process method of RealInterceptorChain again after execution, but directly returns the Response body. So the chain of responsibility of the interceptor terminates here to look for the next link, and the Response is returned in reverse. At this point the code completes the chain of responsibility of the interceptor and finally returns again to the Execute method of the RealCall.

Here’s a diagram to summarize the invocation process of the interceptor chain:

That concludes the OkHttp synchronization process.

3.3 Asynchronous Request

3.3.1 How Do I Initiate An Asynchronous Request

Typical code for asynchronous requests

Request request = new Request.Builder()
                .url(url)
                .build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                //TODO 失败的回调函数
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                //TODO 成功的回调函数
            }
        });
Copy the code

3.3.2 Classes involved in asynchronous requests

3.3.2.1 Dispatcher class

We’ve encountered this class briefly when synchronizing requests, but it’s not the main character in the request, it just stores the synchronized Call in an ArrayDeque called runningSyncCalls. But the Dispatcher class still plays an important role in asynchronous requests.

public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private @Nullable Runnable idleCallback;
  private @Nullable ExecutorService executorService;
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

  public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }
  public Dispatcher() {
  }
  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher".false));
    }
    returnexecutorService; }... synchronized void enqueue(AsyncCall call) {if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

  public synchronized void cancelAll() {
    for (AsyncCall call : readyAsyncCalls) {
      call.get().cancel();
    }

    for (AsyncCall call : runningAsyncCalls) {
      call.get().cancel();
    }

    for(RealCall call : runningSyncCalls) { call.cancel(); }}... }Copy the code

From the source code for Dispatcher above, this class is decorated with the final keyword, indicating that it cannot be inherited. He has a public synchronized ExecutorService ExecutorService () method internally. Obviously, this method sets the ThreadPoolExecutor pool. Asynchronous tasks will be executed in this thread pool. This is significantly different from synchronous calls that go directly through the interceptor chain.

3.3.2.2 AsyncCall class

This class inherits from NamedRunnable, which is a simple wrapper around Runnable and sets the name of the thread currently executing. I won’t expand on NamedRunnable here. Let’s look at the source code of AsyncCall.

  final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

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

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

    Request request() {
      return originalRequest;
    }

    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{ eventListener.callFailed(RealCall.this, e); responseCallback.onFailure(RealCall.this, e); } } finally { client.dispatcher().finished(this); }}}Copy the code

When AsyncCall thread pool calls the execute method will be executed, look at the content of the execute, day lulu, or through the getResponseWithInterceptorChain method by request, it like a synchronous invocation is dripping.

3.3.3 Sorting out the Asynchronous request process

Starting with the demo in section 3.3.1, let’s comb through the asynchronous process. The call starts with a call through client.newCall(request), just like the synchronized process, which obviously goes inside the RealCall. Take a look at the RealCall enqueue method.

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

The enqueue method checks to see if the current Call has been requested and throws an exception if so. Finally, this method will hand the AsyncCall object directly to the Dispatcher for execution, as described in 3.3.2 above. The Dispatcher enqueue method holds the AsyncCall encapsulated Runnable directly from the pool of threads initialized by the DispatorService method. The developer custom Callback is completed inside the AsyncCall. At this point, the asynchronous process is combed out.

Write in the last

This is my first source code analysis blog, so it may be messy. Welcome to slap bricks.

Some time ago, I participated in the JTalk offline salon of Nuggets, and the concept of “jumping out of comfort period” proposed by The zi Cheng big guy of Didi made me feel quite a lot. After all, it is always good to be exposed to new technologies for your own development.

Ring arrogant, ring mania, ring ease. ‘~


About Me

contact way value
mail [email protected]
wechat W2006292
github https://github.com/weixinjie
blog https://juejin.cn/user/3087084378665367