Hello, I’m N0tExpectErr0r, an Android developer who loves technology

My personal blog: blog.n0tExpecterr0r.cn

OkHttp source code analysis series

OkHttp source code Analysis series (1) – request initiation and interceptor mechanism overview

OkHttp source code analysis series (two) – interceptor overall flow analysis

OkHttp source code analysis series (three) – caching mechanism

OkHttp source code analysis series (4) – connection establishment overview

OkHttp source code analysis series (five) – proxy routing

OkHttp source code analysis series (six) – connection reuse mechanism and connection establishment

OkHttp source code analysis series (seven) – request initiation and response read

OkHttp is a network request library that I’ve been in touch with since I started Android, and now I’ve been with it for almost two years, without a systematic source code analysis of it. So we are going to create a series that will parse the source code for OkHttp.

This source code analysis is based on OkHttp 3.14

OkHttpClient

OkHttpClient is one of the most important classes in OkHttp.

Factory for {@linkplain Call calls}, which can be used to send HTTP requests and read their responses.

OkHttpClients should be shared OkHttp performs best when you create a single {@code OkHttpClient} instance and reuse it for all of your HTTP calls. This is because each client holds its own connection pool and thread pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a client for each request wastes resources on idle pools.

According to its official introduction, it is a Call factory class, which can be used to produce calls, so as to initiate HTTP Request and obtain Response through Call.

At the same time, the official recommendation is to use a global OkHttpClient shared between multiple classes. Because each Client has its own connection pool and thread pool, reusing clients reduces resource waste.

It is built in Builder mode and provides a number of parameters that we can configure:

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 callTimeout;
    int connectTimeout;
    int readTimeout;
    int writeTimeout;
    int pingInterval;
    // ...
}
Copy the code

As you can see, it has a lot of configurable parameters.

With OkHttpClient built, we can use the okHttpClient. newCall method to create a Call based on our incoming Request.

/**
 * 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

Request

Request corresponds to the Request in our HTTP Request, and its URL, method, header and so on can be configured in Builder.

Request construction also adopts the Builder mode to construct:

public static class Builder {
    @Nullable
    HttpUrl url;
    String method;
    Headers.Builder headers;
    @Nullable
    RequestBody body;
    // ...
}
Copy the code

Once the Request is built, you can Call the okHttpClient. newCall method to create the corresponding Call

Call

build

NewRealCall (this, Request, false /* for Web socket */); , where the third parameter indicates whether to use the Web socket.

Let’s look at the realCall. newRealCall method:

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.transmitter = new Transmitter(client, call);
    return call;
}
Copy the code

Here we build a RealCall object based on the parameters we passed in, and build its TRANSMITTER based on the client.

RealCall’s constructor consists mainly of assignments:

private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
}
Copy the code

And there are also some assigning operations in Transmitter:

public Transmitter(OkHttpClient client, Call call) {
    this.client = client;
    this.connectionPool = Internal.instance.realConnectionPool(client.connectionPool());
    this.call = call;
    this.eventListener = client.eventListenerFactory().create(call);
    this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
} 
Copy the code

The first call the Internal. The instance. RealConnectionPool method, by the client. Capturing the connectionPool realConnectionPool object, After the call to the client. EventListenerFactory (). The create method (call) structure created the eventListener.

Initiation of a request

OkHttp can be executed in two ways, enqueue and execute, which represent asynchronous and synchronous requests respectively:

  • Enqueue: Represents an asynchronous request that does not block the calling thread. We need to pass in a Callback that will Callback its onResponse method when the request succeeds and its onFailure method when the request fails.

  • Execute: indicates a synchronous request. It blocks the calling thread and returns the request result after the request is complete.

Let’s break them down separately:

An asynchronous request

Let’s examine the enqueue method:

@Override
public void enqueue(Callback responseCallback) {
    synchronized (this) {
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true; } // Notify eventListener transmitter. CallStart (); // Build AsyncCall and assign the task client.dispatcher().enqueue(new AsyncCall(responseCallback)); }Copy the code

It first calls transmitter. CallStart and finally the callStart method of eventListener constructed earlier

It then calls the client.dispatcher().enqueue method, builds an AsyncCall object and gives it to the client.Dispatcher for task dispatch.

executeOn

The AsyncCall class exposes the executeOn method, which the Dispatcher can call and pass to the ExecutorService to make HTTP requests in the threads provided by the thread pool. Get Response and Callback the corresponding method of Callback to achieve task scheduling.

void executeOn(ExecutorService executorService) { assert (! Thread.holdsLock(client.dispatcher())); boolean success =false; Executorservice.execute (this); // Execute AsyncCall executorService.execute(this) in the corresponding ExecutorService; success =true; } the catch (RejectedExecutionException e) {/ / there is a problem, Callback ioException = new InterruptedIOException("executor rejected"); ioException.initCause(e); transmitter.noMoreExchanges(ioException); responseCallback.onFailure(RealCall.this, ioException); } finally {// Whether successful or not, notify Dispatcher that the request is completeif(! success) { client.dispatcher().finished(this); // This call is no longer running! }}}Copy the code

AsyncCall is a Runnable. Let’s see how it implements the execute method:

@Override
protected void execute() {
    boolean signalledCallback = false; TimeoutEnter (); timeoutEnter(); Try {/ / get the Response the Response the Response = getResponseWithInterceptorChain (); signalledCallback =true; / / request is successful, the notification Callback 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{/ / request fails, notify the Callback responseCallback. OnFailure (RealCall. This, e); }} finally {// Notify Dispatcher of request completion client.dispatcher().finished(this); }}Copy the code

TimeoutEnter () is called to start the Timeout.

After if the request is successful, then will pass getResponseWithInterceptorChain method to get the Response, called after the Callback. The notification request successful onResponse method.

If the request fails, the callback. onFailure method is called to notify that the request failed.

It seems that the core implementation of network request getResponseWithInterceptorChain method is implemented, and OkHttp timeout mechanism and transmitter. TimeoutEnter related, we temporarily don’t pay attention to these details first.

Asynchronous thread pool

Let’s take a look at what thread pool OkHttp uses for asynchronous requests. The caller in AsyncCall. ExecuteOn method was introduced into the Dispatcher. ExecutorService method return values, we came to this method:

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

We know that the thread pool can be specified by creating the Dispatcher. If not, we will create a thread pool as shown in the above code. Let’s examine its parameters.

  • Core threadscorePoolSize: The number of threads that remain in the thread pool, even when idle. Is 0, so any threads that are idle are not reserved.
  • Maximum number of threadsmaximumPoolSize: Specifies the maximum number of threads that can be created in the thread poolInteger.MAX_VALUE.
  • Thread lifetimekeepAliveTime: The time that a thread can live after it is idle. The time specified here is 60 time units (60s), which means that threads are recycled after more than 60 seconds.
  • Unit of timeunit: Unit of the thread lifetime of the signature, where isTimeUnit.SECONDSIn other words, seconds
  • Thread wait queueworkQueue: a queue of threads in which elements are queued and executed in sequence, where and are specifiedSynchronousQueue.
  • Thread factorythreadFactoryThe thread creation factory is passed in hereUtil.threadFactoryMethod to create a thread factory.

For the above parameters, we have a few details to consider:

Why SynchronousQueue

SynchronousQueue is a queue with no containers. It uses the classical producer-consumer model. When a production thread performs a production operation (PUT), If there is no consumer thread to consume (take), the thread blocks until a consumer consumes. That is to say, it only realizes a transfer operation. Since there is no intermediate process of putting and taking elements out of the container, this transfer function is a fast way to transfer elements, which is very suitable for high-frequency requests like network requests. For details on SynchronousQueue, see this article: Java Concurrency SynchronousQueue implementation principles

Why does a thread pool have an unlimited number of threads and live only a short time when each thread is idle

In OkHttp, the number of threads is not maintained by the thread pool, but by the Dispatcher. MaxRequests and maxRequestsPerHost are set externally to adjust the wait queue and the execution queue. To achieve the maximum number of threads control. The implementation of the Dispatcher is described later in this article.

A synchronous request

execute

We then look at the execute method, which executes the synchronous request:

@Override
public Response execute() throws IOException {
    synchronized (this) {
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true; } transmitter.timeoutEnter(); transmitter.callStart(); Try {// Notify Dispatcher client.dispatcher().executed(this); / / get the ResponsereturngetResponseWithInterceptorChain(); } finally {// Notify Dispatcher of request completion client.dispatcher().finished(this); }}Copy the code

It first calls the Dispatcher. Executed method, inform the Dispatcher the Call is executed, then Call the getResponseWithInterceptorChain method to obtain the Response, Finished is called to notify the Dispatcher that the Call has been completed.

Dispatcher task scheduling

enqueue

To see how Dispatcher dispatches asynchronous requests, go to dispatcher. enqueue:

Void enqueue(AsyncCall call) {synchronized (this) {readyAsynccalls.add (call); // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to // the same host.if(! Call.get ().forwebSocket) {// Find a call with the same host AsyncCall existingCall = findExistingCallWithHost(call.host()); // Reuse callsPerHost for Callif(existingCall ! = null) call.reuseCallsPerHostFrom(existingCall); } // Try to execute the task in the wait queue promoteAndExecute(); }Copy the code

I’m going to add it to the readAsyncCalls wait queue.

The findExistingCallWithHost method is then called to try to find a Call with the same host. It iterates through the readyAsyncCalls and runningAsyncCalls queues to find a Call with the same host.

If found out the corresponding Call, will Call Call. ReuseCallsPerHostFrom reuse this Call callsPerHost, facilitates statistics and a host of corresponding Call number, it is an AtomicInteger.

Finally, the promoteAndExecute method is called, which attempts to execute the task in the waiting queue.

executed

Continuing our look at how Dispatcher dispatches synchronous requests, we go to the Dispatcher. Executed method:

synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
}
Copy the code

Here it is simple to add the synchronization task directly to the execution queue runningSyncCalls.

promoteAndExecute

We see the promoteAndExecute method:

private boolean promoteAndExecute() { assert (! Thread.holdsLock(this)); List<AsyncCall> executableCalls = new ArrayList<>(); boolean isRunning; synchronized (this) {for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall asyncCall = i.next();
            if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
            if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity. i.remove(); Asynccall.callsperhost ().incrementandGet (); executableCalls.add(asyncCall); runningAsyncCalls.add(asyncCall); } isRunning = runningCallsCount() > 0; }for (int i = 0, size = executableCalls.size(); i < size; i++) {
        AsyncCall asyncCall = executableCalls.get(i);
        asyncCall.executeOn(executorService());
    }
    return isRunning;
}
Copy the code

This method iterates through the readyAsyncCalls queue, constantly looking for an AsynCall that can be executed, and then calls the AsynccAll. executeOn method to execute the Call from its executorService thread pool. Where, tasks in progress cannot exceed maxRequests.

finished

After each AsyncCall request is completed, the Finished method is called to notify the Dispatcher of the completion of the request, whether it succeeds or fails:

Void finished(AsyncCall call) {// decrementAndGet(); void finished(AsyncCall call) {call.callsperhost (). finished(runningAsyncCalls, call); }Copy the code

It calls another overload of finished:

private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
        if(! calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); idleCallback = this.idleCallback; } // Try to execute tasks in the wait queue Boolean isRunning = promoteAndExecute();if(! isRunning && idleCallback ! = null) { idleCallback.run(); }}Copy the code

As you can see, the promoteAndExecute method is called again to try to execute the tasks in the wait queue. If there are no tasks in the wait queue that need to be executed, the maxRequests set have not been reached. Idlecallback. run is called to perform some idleCallback

(This design is somewhat similar to Handler’s IdleHandler mechanism, making full use of some idle resources, worth learning).

summary

As you can see, the design of OkHttp’s task scheduler divides requests into two queues, namely wait queue and execution queue.

Each time a new asynchronous request is added, it is first added to the wait queue and then iterated through the wait queue to try to execute the wait task.

Each time a new synchronization request is added, it is directly added to the execution queue.

Whenever a request is completed, it will notify the Dispatcher and the Dispatcher will traverse the ready queue and try to execute the task. If there is no execution, it means that the waiting queue is empty and idlecallback. run will be called to execute some idle tasks. IdleHandler mechanism similar to Handler.

(The task scheduler in multithreaded downloader uses this Dispatcher design.)

Response retrieval

From the previous in both synchronous and asynchronous requests can be seen that the response of access to the core of the implementation is RealCall getResponseWithInterceptorChain method:

The Response getResponseWithInterceptorChain () throws IOException {/ / Build a full stack of interceptors. / / initializes the interceptor list List<Interceptor> interceptors = new ArrayList<>(); // User-defined Interceptor 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) {/ / user custom network Interceptor interceptors. AddAll (client.net workInterceptors ()); } interceptors.add(new CallServerInterceptor(forWebSocket));
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
            originalRequest, this, client.connectTimeoutMillis(),
            client.readTimeoutMillis(), client.writeTimeoutMillis());
    boolean calledNoMoreExchanges = false;
    try {
        Response response = chain.proceed(originalRequest);
        if (transmitter.isCanceled()) {
            closeQuietly(response);
            throw new IOException("Canceled");
        }
        return response;
    } catch (IOException e) {
        calledNoMoreExchanges = true;
        throw transmitter.noMoreExchanges(e);
    } finally {
        if(! calledNoMoreExchanges) { transmitter.noMoreExchanges(null); }}}Copy the code

This method is so important that it implements all the processing of the request in just a few lines of code, and it embodies an important core design of OkHttp — the interceptor mechanism.

It first adds a user – defined interceptor to interceptors, followed by a series of built-in interceptors.

We then construct a Chain object using the RealInterceptorChain constructor and call its proceed method to get the Response to the request.

So how do we get Response in this process? Let’s first understand OkHttp’s interceptor mechanism.

Overview of interceptor mechanisms

The OkHttp network request process depends on a variety of Interceptor implementation, let’s take a look at the definition of Interceptor:

/**
 * Observes, modifies, and potentially short-circuits requests going out and the corresponding
 * responses coming back in. Typically interceptors add, remove, or transform headers on the request
 * or response.
 */
public interface Interceptor {
    Response intercept(Chain chain) throws IOException;
    
    interface Chain {
        Request request();
        Response proceed(Request request) throws IOException;
        /**
         * Returns the connection the request will be executed on. This is only available in the chains
         * of network interceptors; for application interceptors this is always null.
         */
        @Nullable
        Connection connection();
        Call call();
        int connectTimeoutMillis();
        Chain withConnectTimeout(int timeout, TimeUnit unit);
        int readTimeoutMillis(); Chain withReadTimeout(int timeout, TimeUnit unit); int writeTimeoutMillis(); Chain withWriteTimeout(int timeout, TimeUnit unit); }}Copy the code

Interceptor is actually an interface that contains only one method Intercept and one interface Chain.

Interceptor

Intercept methods are usually structured as follows:

@Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); / / Request stage, the interceptor in the Request phase is responsible for doing RealInterceptorChain. / / calls proceed (), Response = ((RealInterceptorChain) chain). Proceed (Request, streamAllocation, null, null); // Response phase, completes what the interceptor was responsible for in the Response phase, and then returns to the interceptor at the next level up.return response;     
}
Copy the code

Here, the chain. Request method is called to obtain the request object of this request.

The chain.proceed method is then called recursively to the interceptor method of the next interceptor.

The Response returned by the chain.proceed method is finally returned.

The three simple lines of code above break the Intercept process into two stages:

  • Request phase: Performs some of the tasks the interceptor is responsible for during the Request phase
  • Response phase: Completes what the interceptor is responsible for in the Response phase

This is actually a recursive design, similar to the hierarchical model in our computer network. OkHttp requests are divided into several stages, each representing different interceptors. Different interceptors may process the Request twice in this recursive process, once before the Request. Once after the Response, if any error occurs in the middle process, it will inform the upper layer by throwing an exception.

There are several preset interceptors:

  • RetryAndFollowUpInterceptor: is responsible for implementing the redirection function
  • BridgeInterceptor: Converts user-constructed requests into requests sent to the server and responses returned from the server into user-friendly responses
  • CacheInterceptor: Reads the cache and updates the cache
  • ConnectInterceptor: Establishes a connection to the server
  • CallServerInterceptor: Reads the response from the server

It can be seen that the whole process of network request is realized by each interceptor cooperating with each other. Through this mechanism of interceptor, the process and sequence of network request can be easily adjusted, and users can easily extend it.

The user can insert the Interceptor at two times:

  • Before and after the network request: YesOkHttpClient.addInterceptorMethod to add
  • Before and after reading the response: PassOkHttpClient.addNetworkInterceptorMethod to add

The overall process is shown in the figure:

RealInterceptorChain

Let’s take a look at how the RealInterceptorChain connects the entire interceptor invocation process. Let’s look at its construction process first:

public RealInterceptorChain(List<Interceptor> interceptors, Transmitter transmitter,
                            @Nullable Exchange exchange, int index, Request request, Call call,
                            int connectTimeout, int readTimeout, int writeTimeout) {
    this.interceptors = interceptors;
    this.transmitter = transmitter;
    this.exchange = exchange;
    this.index = index;
    this.request = request;
    this.call = call;
    this.connectTimeout = connectTimeout;
    this.readTimeout = readTimeout;
    this.writeTimeout = writeTimeout;
}
Copy the code

Here are just some of the assignments. We then look at the chain.proceed method to see how it performs:

public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange) throws IOException { // ... RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange) index + 1, request, call, connectTimeout,readTimeout, writeTimeout); Interceptor = interceptors.get(index); // Interceptor = interceptors.get(index); Response response = interceptor.intercept(next); / /...return response;
}
Copy the code

Some exception handling is omitted here. It can be seen that it first constructs the Chain corresponding to the next interceptor, then obtains the current interceptor and calls its Intercept method to obtain its result. The Chain corresponding to the next interceptor is passed in the parameter of the Intercept method.

Through this recursive design, so as to achieve from top to bottom, and then from bottom to top such a recursive and return process, thus very beautiful to achieve the whole process of HTTP request.

This is an implementation similar to the chain of responsibility pattern, which is very common in the process of network requests and is worth learning from.

summary

OkHttp used in the process of reading response of a chain of responsibility pattern, preset the multiple is responsible for the different functions of interceptors, connect them through a chain of responsibility together, adopt the way of a recursive call, so that each layer in front of the request and response can make different treatment on the request, through the interceptor coordination, Finally completed the entire network request process.

The resources

OkHttp 3.x source code parsing Interceptor

Okhttp Journey part II – Request and response flow

Java concurrency SynchronousQueue implementation principles