1. Introduction
The general request flow is as follows
Requests are roughly divided into three phases: preparation, queuing, and preprocessing.
- This preparation includes instantiations of OkHttpClient, Request, and so on;
- A network request that is executed synchronously in the queue phase does not have a queue phase because it calls execute() directly and blocks the current thread due to network I/O. The enqueue() method, on the other hand, is asynchronous, implemented through the Dispatcher using the ExecutorService, and ultimately performs network requests the same as the synchronous execute() interface.
- Pretreatment: Preprocessing is the preprocessing of network requests. The implementation of OkHttp adopts an Interceptor Chain. This series of interceptors will add some header information for our network requests. Or preempting the retry mode for failed requests, getting available instances from the connection pool for reuse, and so on. The next step is to read the source code in these three stages.
2. Preparation
OkHttpClient is a requestor. We can create an OkHttpClient using new:
OkHttpClient httpClient = new OkHttpClient();
Copy the code
Point into the method to find the constructor of the empty parameter:
//OkHttpClient.decompiled.Java
public OkHttpClient() {
this(new OkHttpClient.Builder());
}
Copy the code
We actually passed in a Builder object by default, which is one of our configuration objects. We can configure OkHttpClient using the Builder. The Builder constructor is as follows:
public Builder() {
this.dispatcher = new Dispatcher();
this.connectionPool = new ConnectionPool();
boolean var1 = false;
this.interceptors = (List)(new ArrayList());
var1 = false;
this.networkInterceptors = (List)(new ArrayList());
this.eventListenerFactory = Util.asFactory(EventListener.NONE);
this.retryOnConnectionFailure = true;
this.authenticator = Authenticator.NONE;
this.followRedirects = true;
this.followSslRedirects = true;
this.cookieJar = CookieJar.NO_COOKIES;
this.dns = Dns.SYSTEM;
this.proxyAuthenticator = Authenticator.NONE;
SocketFactory var10001 = SocketFactory.getDefault();
Intrinsics.checkNotNullExpressionValue(var10001, "SocketFactory.getDefault()");
this.socketFactory = var10001;
this.connectionSpecs = OkHttpClient.Companion.getDEFAULT_CONNECTION_SPECS$okhttp();
this.protocols = OkHttpClient.Companion.getDEFAULT_PROTOCOLS$okhttp();
this.hostnameVerifier = (HostnameVerifier)OkHostnameVerifier.INSTANCE;
this.certificatePinner = CertificatePinner.DEFAULT;
this.connectTimeout = 10000;
this.readTimeout = 10000;
this.writeTimeout = 10000;
this.minWebSocketMessageToCompress = 1024L;
}
Copy the code
This provides a long list of Build parameters, the meaning of which will come later, but we only need to know the Build process of OkHttpClient.
We can think of OkHttpClient as a requestor that sends out a Request. A Request is a Request. After we declare OkHttpClient, we need to declare the Request object:
Request request = new Request.Builder()
.url(url).post(requestBody).build();
Copy the code
The URL is naturally the address of the request, and the RequestBody is the body of the request, itself an abstract class where you can set the request’s MediaType, Content, ContentLength, and so on. The following code sets the content-type to “text/ HTML”. Charset = UTF-8 “), the data is the form data and will be stored in the body of the request in the format var1=x&var2=y.
RequestBody requestBody = RequestBody.create(MediaType.parse("text/html; charset=utf-8"), data);Copy the code
Next, we generate a Call object from the okHttpClient.newCall Call:
Call x = httpClient.newCall(request); // Inside the newCall method: returns a RealCall object; override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)Copy the code
And the RealCall constructor:
class RealCall(
val client: OkHttpClient,
/** The application's original request unadulterated by redirects or auth headers. */
val originalRequest: Request,
val forWebSocket: Boolean
) : Call {
//Omit
}
Copy the code
In the newCall method, we fill in OkHttpClient, the base request, and whether it is a WebSocket request. After this step, you end up with a Call object.
3. Synchronize requests
If we use the realCall.execute () method, then a synchronous request will be made, that is, the request will be executed on the current thread, with the execute method as follows:
// Override Fun Execute (): Response { check(executed.compareAndSet(false, true)) { "Already Executed"} timeout.enter() callStart() try { client.dispatcher.executed(this) return Finally getResponseWithInterceptorChain ()} {client. The dispatcher. Finished (this)}} / / Java version @ Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } try { client.dispatcher().executed(this); Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } finally { client.dispatcher().finished(this); }}Copy the code
Since OkHttp was later rewritten using Kotlin, I’ll stick to the Java version for now: Executed in Java is a Boolean variable used to check whether the RealCall has been executed. With sychronized locking, this means that the ReadCall will only be executed once.
The dispatcher is one of the members of the client, can be seen as the dispenser of events, call executed (this) perform network request, and getResponseWithInterceptorChain (); Is the third step: pretreatment; Make the whole request through the Interceptor Chain and finally get Response. If the Response obtained is empty, it means that it is cancelled by the Interceptor Chain.
In the Kotlin version, executed is declared as “a Boolean value that can be updated atomically”, which means that only one thread can access it under high concurrency. In fact, it is equivalent to a piece of code encapsulated in Sychronized(){}. CompareAndSet (the original value, the value to change), refer to the CAS implementation, and the rest of the code is roughly the same.
4. Queue (asynchronous request)
Now going back to getting the RealCall object, we usually don’t want the network request to be executed directly on the main thread, which can cause frames to not be rendered in time, which can affect the user experience, and even cause ANR in Android. We passed:
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
}
});
Copy the code
To insert a request into the request queue, where we override two callbacks, one for failure and one for success.
The body of the enqueue method is as follows:
override fun enqueue(responseCallback: Callback) {
check(executed.compareAndSet(false, true)) { "Already Executed"}
callStart()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
Copy the code
Similarly, a RealCall can only be executed once, CallStart() is a callback to start execution, and an event is finally dispatched via Dispatch, adding an asynchronous AsyncCall to the send queue:
Internal fun enqueue(Call: AsyncCall) {synchronized(this) {readyAsyncCalls.add(call) call.call.forWebSocket) { val existingCall = findExistingCallWithHost(call.host) if (existingCall ! = null) call. ReuseCallsPerHostFrom (existingCall) / / reuse an existing call}} promoteAndExecute () / / notify the thread pool to take out a call to deal with;Copy the code
In the PromoteAndExecute method:
private fun promoteAndExecute(): Boolean { this.assertThreadDoesntHoldLock() val executableCalls = mutableListOf<AsyncCall>() val isRunning: Boolean synchronized(this) { val i = readyAsyncCalls.iterator() while (i.hasNext()) { val asyncCall = i.next() if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity. if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity. i.remove() asyncCall.callsPerHost.incrementAndGet() executableCalls.add(asyncCall) runningAsyncCalls.add(asyncCall) } isRunning = runningCallsCount() > 0 } // Execute executableCalls for (I in 0 until executablecalls.size) {val asyncCall = executableCalls[I] asyncCall.executeOn(executorService) } return isRunning }Copy the code
First, iterate over readyAsyncCalls, with two special cases:
- When runningAsyncCalls exceed maxRequests, the thread is full and exits the loop.
- When asynccall.callsperHost ().get() has received enough Call requests from the host of the current Call, the loop continues;
If neither is satisfied, the following is performed :(mainly to add requests to execute to executableCalls and to add requests to execute to runningAsyncCalls)
Then, you walk through the executableCalls you just got and execute the tasks using the call thread pool. This is done by calling asyncCall’s executeOn(executorService()) method.
The executeOn method works as follows:
fun executeOn(executorService: ExecutorService) { client.dispatcher.assertThreadDoesntHoldLock() var success = false try { executorService.execute(this) success = true } catch (e: RejectedExecutionException) { val ioException = InterruptedIOException("executor rejected") ioException.initCause(e) noMoreExchanges(ioException) responseCallback.onFailure(this@RealCall, ioException) } finally { if (! success) { client.dispatcher.finished(this) // This call is no longer running! }}}Copy the code
The AsyncCall class looks like this:
internal inner class AsyncCall( private val responseCallback: Callback ) : Runnable { @Volatile var callsPerHost = AtomicInteger(0) private set fun reuseCallsPerHostFrom(other: AsyncCall) { this.callsPerHost = other.callsPerHost } val host: String get() = originalRequest.url.host val request: Request get() = originalRequest val call: RealCall get() = this@RealCall fun executeOn(executorService: ExecutorService) { client.dispatcher.assertThreadDoesntHoldLock() var success = false try { executorService.execute(this) success = true } catch (e: RejectedExecutionException) { val ioException = InterruptedIOException("executor rejected") ioException.initCause(e) noMoreExchanges(ioException) responseCallback.onFailure(this@RealCall, ioException) } finally { if (! success) { client.dispatcher.finished(this) // This call is no longer running! } } } override fun run() { threadName("OkHttp ${redactedUrl()}") { var signalledCallback = false timeout.enter() try { val response = getResponseWithInterceptorChain() signalledCallback = true responseCallback.onResponse(this@RealCall, response) } catch (e: IOException) { if (signalledCallback) { // Do not signal the callback twice! Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e) } else { responseCallback.onFailure(this@RealCall, e) } } catch (t: Throwable) { cancel() if (! signalledCallback) { val canceledException = IOException("canceled due to $t") canceledException.addSuppressed(t) responseCallback.onFailure(this@RealCall, canceledException) } throw t } finally { client.dispatcher.finished(this) } } } }Copy the code
Finally, we rewrite the run () method, saw and direct call RealCall. Execute similar code, by getResponseWithInterceptorChain will a request by the third step of the interceptor chain processing, send out again, Finally, the Response is successfully obtained or failed, and an error is reported.
try {
val response = getResponseWithInterceptorChain()
signalledCallback = true
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {//omit }
Copy the code
The responseCallBack is the constructor passed to the AsyncCall when it is constructed, and it is called in the enqueue method:
override fun enqueue(responseCallback: Callback) {
check(executed.compareAndSet(false, true)) { "Already Executed" }
callStart()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
Copy the code
This responseCallback is where we are
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {}
});
Copy the code
Callback() from new.
5. Preprocessing (Interceptor Chain)
For both synchronous and asynchronous requests, we end up with this method:
getResponseWithInterceptorChain()
Copy the code
Chain of responsibility interceptors, as a whole, can be wrapped before the request is sent, including the request header (Cookie or Token), interception of incorrect requests, etc. There are five layers of interceptors built in, not including (newWork interceptor). The body of this method is as follows:
@Throws(IOException::class) internal fun getResponseWithInterceptorChain(): Response { // Build a full stack of interceptors. val interceptors = mutableListOf<Interceptor>() interceptors += Client.interceptors //0(user defined App interceptors, Not netWorkInterceptor) interceptors + = RetryAndFollowUpInterceptor (client) / / interceptors + = 1 BridgeInterceptor(client.cookieJar)//2 interceptors += CacheInterceptor(client.cache)//3 interceptors += ConnectInterceptor//4 // If not WebSocket, add user's own netWork interceptor if (! forWebSocket) { interceptors += client.networkInterceptors } interceptors += CallServerInterceptor(forWebSocket)//5 val chain = RealInterceptorChain( call = this, interceptors = interceptors, index = 0, exchange = null, request = originalRequest, connectTimeoutMillis = client.connectTimeoutMillis, readTimeoutMillis = client.readTimeoutMillis, writeTimeoutMillis = client.writeTimeoutMillis ) var calledNoMoreExchanges = false try { //focus val response = chain.proceed(originalRequest) if (isCanceled()) { response.closeQuietly() throw IOException("Canceled") } return response } catch (e: IOException) { calledNoMoreExchanges = true throw noMoreExchanges(e) as Throwable } finally { if (! calledNoMoreExchanges) { noMoreExchanges(null) } } }Copy the code
Finally, it came to this:
val response = chain.proceed(originalRequest)
Copy the code
Proceed method:
override fun proceed(request: Request): Response { check(index < interceptors.size) calls++ if (exchange ! = null) { check(exchange.finder.sameHostAndPort(request.url)) { "network interceptor ${interceptors[index - 1]} must retain the same host and port" } check(calls == 1) { "network interceptor ${interceptors[index - 1]} must call proceed() Val next = copy(index = index + 1); request = request) val interceptor = interceptors[index] @Suppress("USELESS_ELVIS") val response = interceptor.intercept(next) ?: throw NullPointerException( "interceptor $interceptor returned null") if (exchange ! = null) { check(index + 1 >= interceptors.size || next.calls == 1) { "network interceptor $interceptor must call proceed() exactly once" } } check(response.body ! = null) { "interceptor $interceptor returned a response with no body" } return response }Copy the code
An Interceptor is an abstract interface definition for an Interceptor. In the intercept method, it receives a Chain. In this way, when an interceptor implementation calls the intercept method, it can intercept the Chain with Chain, call the request method to fetch the client request, and then call the process method to create the Chain of the next node. In this case, The next Interceptor will be located in the Chain of the next node. The process method of Chain is no longer executed until the last Interceptor. Because by the time the last interceptor is executed, there are no more interceptors, the final response needs to be sent back to the client after the last interceptor call. The responsibilities of each interceptor are as follows:
In getResponseWithInterceptorChain () method, we passed the most primitive: originalRequest
val response = chain.proceed(originalRequest)
Copy the code
In the RetryAndFollowUp interceptor, we find:
//RetryAndFollowUpInterceptor.kt class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response {// omit var request = chain-.request // omit ····· Response = realchain-.proceed (request) // omit ·····}Copy the code
In this interceptor, we once call chchain.proceed (request), and the same is true for BridgeInterceptor:
//BridgeInterceptor.kt
val networkResponse = chain.proceed(requestBuilder.build())
Copy the code
There is even a new request that is passed in using the RequestBuilder.build(), which ends up in the final interceptor: CallServerInterceptor, instead of chain-.proceed:
var response = responseBuilder
.request(request)
.handshake(exchange.connection.handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
Copy the code
Not surprisingly, we’ll get the result of this request at this point, and then go back, layer by layer, to the CallBack CallBack.
Reference source
- Chain of responsibility pattern for OKHttp interceptors
- The chain of responsibility pattern of interceptor design essence in OkHttp framework
- OkHttp parsing
- Some simple interceptor