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 threads
corePoolSize
: 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 threads
maximumPoolSize
: Specifies the maximum number of threads that can be created in the thread poolInteger.MAX_VALUE
. - Thread lifetime
keepAliveTime
: 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 time
unit
: Unit of the thread lifetime of the signature, where isTimeUnit.SECONDS
In other words, seconds - Thread wait queue
workQueue
: a queue of threads in which elements are queued and executed in sequence, where and are specifiedSynchronousQueue
. - Thread factory
threadFactory
The thread creation factory is passed in hereUtil.threadFactory
Method 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: Yes
OkHttpClient.addInterceptor
Method to add - Before and after reading the response: Pass
OkHttpClient.addNetworkInterceptor
Method 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