The outline

  • preface
  • An overview of the
  • Basic usage
  • Summary of the foundation
  • Source article
  • Source code summary
  • thinking
  • The resources

preface

The article was read with the OkHttp source code, combined with other summary materials on the Internet, and is actually longer than Retrofit… First say my source code reading process, or according to the call to start reading, from build to call, start from the way into the synchronous call, to the responsibility chain, and then back to the end. The difference between synchronous and asynchronous is that the method entry is different before entering the responsibility chain. The process is basically the same when reaching the link of the responsibility chain. However, in the case of asynchronous invocation, the return of the responsibility chain is only through callback. Therefore, the layout order of the source code has been adjusted (originally synchronous call -> end, asynchronous call -> end as I read), putting the two call methods first, and then dividing the chain of responsibility into smaller points according to each interceptor for easy reading.

An overview of the

HTTP is the way modern application network requests are used to exchange data and media resources. Effective implementation of HTTP can make content providers faster and save bandwidth. OkHttp is an efficient HTTP client that has the following characteristics

  • HTTP/2 support allows all requests from the same host to share a socket
  • Connection pooling reduces request latency (if HTTP/2 is unavailable)
  • Transparent GZIP reduces download size
  • Response caching completely eliminates network duplicate requests

OkHttp quietly recovers from common connection problems in the event of a network problem. If the service has multiple IP addresses, OkHttp tries alternate addresses if the first connection fails. This is required for IPv4+IPv6 and services hosted in redundant data centers. OkHttp supports TLS functionality (TLS 1.3, ALPN, certificate locking). Can be configured to fall back for extensive connections. OkHttp’s request/response API is designed with a smooth builder and immutability. Synchronous blocking calls and asynchronous calls with callbacks are supported. (relying on the Okio and Kotlin libraries) just translated the official document… Now that you know the basics of OkHttp, let’s see how it works

Basic usage

Depend on the introduction of implementation (” com. Squareup. Okhttp3: okhttp: 4.9.0 “) to obtain a URL

Public class GetExample {// create OkHttpClient OkHttpClient client = new OkHttpClient(); String run(String URL) throws IOException {// Create Request Request Request = new request.builder ().url(url).build(); // client.newCall initiates a request. Try (Response Response = client.newCall(request).execute()) {// Return the result, string() after one Call, Return response.body().string(); } } public static void main(String[] args) throws IOException { GetExample example = new GetExample(); String response = example.run("https://raw.github.com/square/okhttp/master/README.md"); System.out.println(response); }}Copy the code

Push data to the server

Public static final MediaType JSON = MediaType. Get ("application/ JSON; charset=utf-8"); // Construct client OkHttpClient client = new OkHttpClient(); String post(String url, String json) throws IOException {// Construct RequestBody (parameter) RequestBody body = requestBody.create (json, json); Request Request = new request.builder ().url(url).post(body).build(); Try (Response Response = client.newCall(request).execute()) {return response.body().string(); }}Copy the code

The above two examples are synchronous requests that block and wait for direct results to return. Let’s look at an asynchronous request

//1 Request Client val okHttpClient = okHttpClient () //2 construct a Request object val Request = request.builder () //API interface by Wanandroid.com. Url (" https://wanandroid.com/wxarticle/chapters/json "). The get (). The build () / / 3 create a Call object of execution Call = val Call. Enqueue (object: Callback {override fun onFailure(call: call, e: Override fun onResponse(call: call, response: override fun onResponse(call: call, response: Response.code = ${response.code}".log()}})Copy the code

Summary of the foundation

Create a RequestBody (to specify MediaType, the basis of a network Request) if there is a Request body (to specify MediaType) Create a Call instance (client.newCall) from OkHttpClient, execute() synchronously, enqueue() asynchronously, and Response is completed. Only from the basic usage, can not see the details of the network request, is not there a distinction between request methods? Isn’t there a timeout mechanism? How to configure it? Synchronous asynchronous switching is how to do… Why don’t we see how he pulls it off

Source article

OkHttpClient

By convention, you start at the place of the invocation

OkHttpClient client = new OkHttpClient(); // OkHttpClient constructor() : this(Builder())Copy the code

An instance of OkHttpClient is also created using the Builder pattern, which contains a set of configurations

If you want to configure parameters, specify the line class Builder () {// The policy for when to execute the requested internal var dispatcher: ConnectionPool = Dispatcher(); // HTTP/2 connectionPool = Dispatcher(); ConnectionPool = ConnectionPool() // List of interceptors for observing, modifying, and short-linking requests and corresponding returned responses MutableList<Interceptor> = mutableListOf() internal val networkInterceptors: MutableList<Interceptor> = mutableListOf(); Monitor the number, size, and duration of HTTP calls to your application (factory mode) EventListener. Factory = EventListener. NONE. AsFactory () / / connection whether after a failed attempt reconnection internal var retryOnConnectionFailure = true / / Server authentication Internal Var Authenticator: Authenticator = authenticator. NONE // Whether redirects internal var followRedirects = true // Whether SSL redirects internal var followSslRedirects = true // HTTP Cookie internal var cookieJar: CookieJar = cookiejar.no_cookies // HTTP cache (stored in a file for reuse to save time and bandwidth) = null // DNS management SYSTEM internal var DNS: DNS = dns. SYSTEM // Proxy Settings (usually HTTP or SOCKS and a socket address) internal var proxy: proxy? = null // internal proxySelector proxySelector: proxySelector? = null // Proxy server authentication internal var proxyAuthenticator: Authenticator = authenticator. NONE // Socket Factory internal var socketFactory: SocketFactory = socketFactory.getDefault () // SSLSocket factory internal var sslSocketFactoryOrNull: SSLSocketFactory? = null // SSL handshake exception management internal var x509TrustManagerOrNull: X509TrustManager? = null // Transport layer version and connection protocol (HTTP and HTTPS internal var connectionSpecs are supported by default: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS // HTTP (default HTTP_2, HTTP_1_1) internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS // hostname HostnameVerifier = OkHostnameVerifier // Internal var certificatePinner CertificatePinner = CertificatePinner. DEFAULT/clear/certificate chain internal var certificateChainCleaner: certificateChainCleaner? = null // Default call timeout (ms), Internal var callTimeout = 0 // Connection timeout (default: 10 seconds) The timeout period is specified when the internal var is created ConnectTimeout = 10_000 // readTimeout (default: 10 seconds) internal var readTimeout = 10_000 // writeTimeout (default: 10 seconds) internal var writeTimeout = 10_000 // Interval between Web sockt and HTTP/2 ping operations (unit: Ms) / / by default, do not send the ping an internal var pingInterval = 0 / / web sockt minimum value internal var minWebSocketMessageToCompress = should be compressed Realwebsocket. DEFAULT_MINIMUM_DEFLATE_SIZE If an error occurs when attempting to connect to a specific IP or proxy server, the route information is logged as an alternate route. Internal var routeDatabase: routeDatabase? = null // ... }Copy the code

To be honest, there are so many properties in The Builder that it took less than half an hour to click through each comment, but to get to know the framework, I should first look at the main process and clear away the confusion at the beginning of the library. Other details can be seen slowly… Now that we’re done creating OkHttpClient, let’s look at Request (step 2, after all).

Request

/** * This class is simpler by comparison * the URL for the url request * method the default method for the request is GET (method specified here) * headers header information (key/value pair), its key/value pair is accessed by List, something... Class Request Internal constructor(@get:JvmName("url")) val url: HttpUrl, @get:JvmName("method") val method: String, @get:JvmName("headers") val headers: Headers, @get:JvmName("body") val body: RequestBody? , internal val tags: Map<Class<*>, Any> ) { // ... }Copy the code

It is worth noting that although the Header is usually passed in as a key-value pair, it has internal logic to convert the key-value pair into a string List, so it can write a string if the Request is a Header. Following the newCall operation seen in Retrofit, OkHttpClient implements the Call.factory interface and overwrites it to return an instance of RealCall as follows

Okhttpclient. kt // the logic of execute and enqueue is in RealCall. ** Crews the [request] to be executed at some point in the future. */ Override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)Copy the code

All that’s left is to make synchronous and asynchronous requests. Let’s see what happens in the RealCall class. Execute (), which uses the Kotlin contract and AtomicBoolean (CAS). Enqueue (), ThreadPoolExecutor

A synchronous request

Realcall.kt /** * originalRequest client * originalRequest parameter * forWebScoket Default is not a Web socket request * newCall to get the Call instance, Execute (); enqueue() */ class RealCall(val client: OkHttpClient, /** The application's original request unadulterated by redirects or auth headers. */ val originalRequest: Request, val forWebSocket: Boolean ) : Call { // ... /** * Synchronous request method * This method has no input and returns Response * This method actually executes the request through the Dispatcher after doing some front-end logic. */ Override fun execute(): Response {// Check whether the executed variable can be changed from false to true, Check (executed.compareAndSet(false, true)) {"Already Executed"} Execute exactly the timeout operation timeout.enter() in the background thread // event callback, Request start callStart () try {/ / call dispatcher# client executed RealCall to pass box. The dispatcher, executed (this) / / get the Response from interception chain And return () is returned by the server response return getResponseWithInterceptorChain ()} finally {/ / end request the client. The dispatcher. Finished (this)}}} /** * () {//... /** * Each new element is added to the end of the array. The head pointer remains the same. The tail pointer is added to the end of the array. * (this.tail = this.tail + 1 & this.elements. Length - 1) == this.head * This queue uses to record synchronous calls that are Running (both canceled and incomplete) */ /** Running synchronous calls. Includes canceled calls that haven't finished yet. */ private val runningSyncCalls = ArrayDeque<RealCall>() // ... The "executed" method places a RealCall instance on a two-way queue. * @synchronized acts like the Synchronized keyword in Java Concurrency related keywords are removed from the Use annotations to replace * but executed () method is to call into the queue only * so still have to look at how to get the real Response * have to return to the RealCall getResponseWithInterceptorChain * / /** Used by [Call.execute] to signal it is in-flight. */ @Synchronized internal fun executed(call: RealCall) { runningSyncCalls.add(call) } }Copy the code

An asynchronous request

Let’s look at the asynchronous call to enqueue()

RealCall.kt override fun enqueue(responseCallback: Callback) { check(executed.compareAndSet(false, "Already Executed"} // call dispatcher#enqueue, Into the AsyncCall and took a responseCallback client. The dispatcher. The enqueue (AsyncCall (responseCallback)} the dispatcher. Kt internal fun  enqueue(call: AsyncCall) {synchronized(this) {// Add AsyncCall to readyAsyncCalls.add(call) // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to // the same host. if (! call.call.forWebSocket) { val existingCall = findExistingCallWithHost(call.host) if (existingCall ! = null) call. ReuseCallsPerHostFrom (existingCall)}} / / execution promoteAndExecute ()} private fun promoteAndExecute () : Boolean {enclosing assertThreadDoesntHoldLock () / / declaration AsyncCall collection requests can be executed (said) val executableCalls = mutableListOf<AsyncCall>() val isRunning: Boolean synchronized(this) {val I = readyAsyncCalls.iterator() while (i.wasnext ()) {val asyncCall = i.next() if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity. if (asyncCall. CallsPerHost. The get () > = this. MaxRequestsPerHost) continue / / Host Max capacity. The i.r emove () / / will get asyncCall endures ExecutableCalls collection asyncCall. CallsPerHost. IncrementAndGet () executableCalls. Add (asyncCall) Runningasynccalls.add (asyncCall)} isRunning = runningCallsCount() > 0} // Traverse the executableCalls collection and call each AsyncCall# executeOn() // executorService is an executorservice-type thread pool that can be used to perform background tasks. ThreadPoolExecutor for (I in 0 until executableCalls. Size) {val asyncCall = executableCalls[I] ExecutorService (executorService)} return isRunning} RealCall.kt (asyncCall is the inner class of RealCall) /** * Attempt to enqueue this async call on [executorService]. This will attempt to clean up * if the executor has been shut down by reporting the call as failed. */ fun executeOn(executorService: ExecutorService) {client. The dispatcher. AssertThreadDoesntHoldLock (var) success = false try {/ / execution AsyncCall (a Runnable)  executorService.execute(this) success = true } catch (e: RejectedExecutionException) { val ioException = InterruptedIOException("executor rejected") ioException.initCause(e) NoMoreExchanges (ioException) // Thread pool task full, refused to execute, Throw exceptions direct callback failed 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 { / / is through the chain of responsibility getResponseWithInterceptorChain val response = getResponseWithInterceptorChain () signalledCallback = true / / Through responseCallback will return to responseCallback response. OnResponse (this @ RealCall, response)} the catch (e: IOException) { if (signalledCallback) { // Do not signal the callback twice! Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, E)} else {/ / exception callback responseCallback onFailure (this @ RealCall, e)}} the catch (t: Throwable) {the cancel () if (! signalledCallback) { val canceledException = IOException("canceled due to $t") canceledException.addSuppressed(t) // Abnormal callback responseCallback. OnFailure (this @ RealCall, canceledException) } throw t } finally { client.dispatcher.finished(this) } } }Copy the code

As you can see, the AsyncCall Runnable starts the chain of responsiblity by performing a thread pool AsyncCall Runnable, and then returns the result via responseCallback as a callback. Synchronous calls do not have a thread open to request, if used on Android, you need to manually open a thread to perform.

Chain of responsibility

Synchronous and asynchronous request will eventually call getResponseWithInterceptorChain into the responsibility chain link connection is established and initiate the request

RealCall.kt // ... /** * This method uses an incoming interceptor and some default interceptors * and then constructs a chain of responsibility instance for processing (using chain of responsibility mode) * each interceptor is responsible for the corresponding function, the entire request process is completed through one interceptor after another * and when the server returns, After dealing with the one interceptor return Response * / @ Throws (IOException: : class) internal fun getResponseWithInterceptorChain () : Response {// Build a full stack of interceptors. // Build a full stack of interceptors Add custom interceptors (which can be passed in when creating OkHttpClient) // You can configure public parameters, Interceptors += client. Interceptors // Add retry and redirection interceptors // Network request error or server return 301, 302, Automatically redirected interceptors + = RetryAndFollowUpInterceptor (client) interceptors / / / / add a bridge joining together into a standard Http request, the request line, the Header, Body += BridgeInterceptor(client.cookiejar) // Add a cache interceptor // Make a network cache based on the Header CacheInterceptor(client.cache) // Add a connection interceptor // open a connection to the target server. ForWebSocket) {/ / add a custom web blocker / / result in the request returns, the interface data processing, such as log interceptors + = client.net workInterceptors} / / add server request interceptor, Request server interceptors += CallServerInterceptor(forWebSocket) // build responsibility chain // call current RealCall instance // Interceptors current collection // // Request Original request parameters (not processed by interceptor) // The remaining three are timeout times (all have default values) 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 {/ / processing interceptor in the chain of responsibility val response = Proceed (originalRequest) // If (isCanceled()) {response. Canceled() throw IOException("Canceled")} return response } catch (e: IOException) { calledNoMoreExchanges = true throw noMoreExchanges(e) as Throwable } finally { if (! calledNoMoreExchanges) { noMoreExchanges(null) } } } //...Copy the code

RealInterceptorChain

A connection to a remote Web server that can host one or more concurrent streams.

** ** RealInterceptorChain(internal Val Call: RealCall, private val interceptors: List<Interceptor>, private val index: Int, internal val exchange: Exchange? , internal val request: Request, internal val connectTimeoutMillis: Int, internal val readTimeoutMillis: Int, internal val writeTimeoutMillis: Int ) : Interceptor.Chain { // ... @Throws(IOException::class) 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() } exactly once"} exactly once"} exactly once"} exactly once"} exactly once"} exactly once"} exactly once" chain. 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 } // ... internal fun copy( index: Int = this.index, exchange: Exchange? = this.exchange, request: Request = this.request, connectTimeoutMillis: Int = this.connectTimeoutMillis, readTimeoutMillis: Int = this.readTimeoutMillis, writeTimeoutMillis: Int = this.writeTimeoutMillis ) = RealInterceptorChain(call, interceptors, index, exchange, request, connectTimeoutMillis, readTimeoutMillis, writeTimeoutMillis) }Copy the code

Copy () creates a new chain of responsibilities for the next interceptor. Copy () takes index + 1 to create a new chain of responsibilities for the next interceptor. The current index has not been changed. The current interceptor’s intercepting method is then called with the new chain of responsibility as an argument, and a Response is returned if the intercepting method completes successfully, otherwise the chain of responsibility continues in the intercepting method (also Chain#proceed()). That rule out the custom interceptors, natural default interceptor is RetryAndFollowUpInterceptor first, to take a look at the intercept () to do something.

RetryAndFollowUpInterceptor

Retry with redirection interceptor.

RetryAndFollowUpInterceptor.kt @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val realChain = chain as RealInterceptorChain var request = chain.request val call = realChain.call var followUpCount = 0 var priorResponse: Response? = null var newExchangeFinder = true var recoveredFailures = listOf<IOException>() while (true) { This method in RealCall, purpose is to create a ExchangeFinder call. EnterNetworkInterceptorExchange (request, newExchangeFinder) var response: Response var closeActiveExchange = true try { if (call.isCanceled()) { throw IOException("Canceled") } try { // Response = realchain.proceed (request) newExchangeFinder = true} Catch (e: RouteException) {// A Route exception occurs, The request will not have been sent. If (! recover(e.lastConnectException, call, request, requestSendStarted = false)) { throw e.firstConnectException.withSuppressed(recoveredFailures) } else { recoveredFailures += e.firstConnectException } newExchangeFinder = false continue } catch (e: IOException) {// An I/O exception occurs, // An attempt to communicate with a server failed. The request may have been sent. recover(e, call, request, requestSendStarted = e ! is ConnectionShutdownException)) { throw e.withSuppressed(recoveredFailures) } else { recoveredFailures += e } NewExchangeFinder = false continue} // Attach the prior response if it exists. Such responses never have a body. if (priorResponse ! = null) { response = response.newBuilder() .priorResponse(priorResponse.newBuilder() .body(null) .build()) .build() } // Call. InterceptorScopedExchange at the end of the data flow will return null val exchange. = call interceptorScopedExchange / / check whether need to redirect, Val followup = followUpRequest(Response, exchange) if (followup == null) {if (exchange! = null && exchange.isDuplex) {call.timeoutearlyexit ()} closeActiveExchange = false Val followUpBody = followup. body if (followUpBody! = null && followupBody.isoneshot ()) {closeActiveExchange = false writeTo is required at most once and only once, Return response} // Close the resource response.body? .closequietly () // The number of redirection times is greater than the maximum, If (++followUpCount > MAX_FOLLOW_UPS) {throw ProtocolException("Too many follow-up requests: $followUpCount")} request = followUp priorResponse = Response} finally {// Exit network interceptor processing Call. ExitNetworkInterceptorExchange (closeActiveExchange)}}} RealCall. Kt / * * * * / fun creating ExchangeFinder instance enterNetworkInterceptorExchange(request: Request, newExchangeFinder: Boolean) { check(interceptorScopedExchange == null) synchronized(this) { check(! responseBodyOpen) { "cannot make a new request because the previous response is still open: " + "please call response.close()" } check(! requestBodyOpen) } if (newExchangeFinder) { this.exchangeFinder = ExchangeFinder( connectionPool, createAddress(request.url), this, eventListener ) } }Copy the code

The code above, the interceptor RetryAndFollowUpInterceptor first going to create ExchangeFinder instance, and then to perform, actually got to the next interceptor, the logic is behind the next interceptor processing, after that, in the default interceptor to add order It is the BridgeInterceptor, because in the realchain.proceed (request) line, it still calls the RealInterceptorChain#proceed logic. As an entry parameter to the current interceptor interceptor method, and therefore directly locates BridgeInterceptor#intercept

BridgeInterceptor

Bridge interceptors, Bridges from application code to network code, first build network requests based on user requests, then continue to call the network, and finally build user responses based on network responses.

BridgeInterceptor.kt @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Val userRequest = chain.request() val requestBuilder = userRequest.newBuilder() Val body = userRequest. Body if (body! = null) { val contentType = body.contentType() if (contentType ! = null) { requestBuilder.header("Content-Type", contentType.toString()) } val contentLength = body.contentLength() if (contentLength ! = -1L) { requestBuilder.header("Content-Length", contentLength.toString()) requestBuilder.removeHeader("Transfer-Encoding") } else { requestBuilder.header("Transfer-Encoding", "Chunked") requestBuilder. RemoveHeader (" the Content - Length ")}} / / stitching request header if (userRequest. The header (" Host ") = = null) { requestBuilder.header("Host", userRequest.url.toHostHeader()) } if (userRequest.header("Connection") == null) { requestBuilder.header("Connection", "Keep-Alive") } // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing // the transfer stream. var transparentGzip = false if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) { transparentGzip = true requestBuilder.header("Accept-Encoding", "gzip") } val cookies = cookieJar.loadForRequest(userRequest.url) if (cookies.isNotEmpty()) { requestBuilder.header("Cookie", cookieHeader(cookies)) } if (userRequest.header("User-Agent") == null) { requestBuilder.header("User-Agent", UserAgent)} // This goes to the next interceptor, taking the assembled request body as an input parameter, Val networkResponse = chain.proceed(requestBuilder.build()); Cookiejar.receiveheaders (userRequest.url, networkResponse.headers) Val converts it to the user of the available response responseBuilder. = networkResponse newBuilder (.) the request (userRequest) if (transparentGzip && "gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) && networkResponse.promisesBody()) { val responseBody = networkResponse.body if (responseBody ! = null) { val gzipSource = GzipSource(responseBody.source()) val strippedHeaders = networkResponse.headers.newBuilder() .removeAll("Content-Encoding") .removeAll("Content-Length") .build() responseBuilder.headers(strippedHeaders) val contentType = networkResponse.header("Content-Type") responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer())) } } return responseBuilder.build() }Copy the code

The BridgeInterceptor’s main job is to convert the web request information from the developer into an actual HTTP request and the HTTP response into something the developer can use. CacheInterceptor is next. Check out the Intercept

CacheInterceptor

Cache interceptor, which processes requests from the cache and writes responses to the cache (using policy mode).

CacheInterceptor.kt @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Val call = chain.call() // The cache is DiskLruCache (least recently used), the key is the requested URL, Return Response instance val cacheCandidate = cache? .get(chain-.request ()) val now = system.currentTimemillis () Or val strategy = cacheStrategy.factory (now, chain-.request (), cacheCandidate).compute() val networkRequest = strategy.networkRequest val cacheResponse = strategy.cacheResponse cache?.trackResponse(strategy) val listener = (call as? RealCall)?.eventListener ?: Eventlistener. NONE // The cache exists but is not used in the policy. If (cacheCandidate! = null && cacheResponse == null) { // The cache candidate wasn't applicable. Close it. cacheCandidate.body? .closequietly ()} // No network request, no cache, If we're forbidden from using the network and the cache is insufficient, fail. if (networkRequest == null && cacheResponse == null) { return Response.Builder() .request(chain.request()) .protocol(Protocol.HTTP_1_1) .code(HTTP_GATEWAY_TIMEOUT) .message("Unsatisfiable Request (only-if-cached)") .body(EMPTY_RESPONSE) .sentRequestAtMillis(-1L) .receivedResponseAtMillis(System.currentTimeMillis()) .build().also { Listener. SatisfactionFailure (call it)}} / / strategy does not use the network request, then use the cache, // If we don't need the network, we're done. If (networkRequest == null) {return cacheResponse!! .newBuilder() .cacheResponse(stripBody(cacheResponse)) .build().also { listener.cacheHit(call, It)}} // Listen for the callback cache if (cacheResponse! = null) { listener.cacheConditionalHit(call, cacheResponse) } else if (cache ! = null) { listener.cacheMiss(call) } var networkResponse: Response? = null try {// Execute the next ConnectInterceptor for a network request, NetworkResponse = chain.proceed(networkRequest)} finally {// If I/O or other exceptions occur, in order not to leak the cache body, If we're crashing on I/O or otherwise, don't leak the cache body. if (networkResponse == null && cacheCandidate ! = null) { cacheCandidate.body? .closequietly ()}} // If the policy uses the cache and the response code is 304 (no change, no content is transmitted), If we have a cache response too, then we're doing a conditional get. If (cacheResponse! = null) { if (networkResponse? .code == HTTP_NOT_MODIFIED) { val response = cacheResponse.newBuilder() .headers(combine(cacheResponse.headers, networkResponse.headers)) .sentRequestAtMillis(networkResponse.sentRequestAtMillis) .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis) .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build() networkResponse.body!! .close() // Update the cache after combining headers but before stripping the // Content-Encoding header (as performed by initContentStream()). cache!! TrackConditionalCacheHit () / / cache cache update. The update (cacheResponse, response) return response.also { listener.cacheHit(call, it) } } else { cacheResponse.body? . CloseQuietly ()}} val response = networkResponse!! .newBuilder() .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build() if (cache ! = null) { if (response.promisesBody() && CacheStrategy.isCacheable(response, NetworkRequest)) {// Offer this request to the cache. Val cacheRequest = cache.put(response) return cacheWritingResponse(cacheRequest, response).also { if (cacheResponse ! = null) { // This will log a conditional cache miss only. listener.cacheMiss(call) } } } if (HttpMethod.invalidatesCache(networkRequest.method)) { try { cache.remove(networkRequest) } catch (_: IOException) { // The cache cannot be written. } } } return response }Copy the code

When executing a CacheInterceptor on a network request, it determines whether a cache is needed and whether network request data is used. If a cache is used and available, it returns the cache directly. If not, a ConnectInterceptor will be executed to continue requesting the network, and the requested data will be cached in return.

ConnectInterceptor

Connect to the interceptor, open the connection to the target server and proceed to the next interceptor, the network may be available for the returned response or for the cached response validated with conditional GET.

ConnectInterceptor.kt

@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
  val realChain = chain as RealInterceptorChain
  // 调用 RealCall#initExchange,就看看这个方法的逻辑,怎么建立的连接
  val exchange = realChain.call.initExchange(chain)
  val connectedChain = realChain.copy(exchange = exchange)
  // 执行下一个拦截器(不考虑 forWebSocket,是 CallServerInterceptor)
  return connectedChain.proceed(realChain.request)
}

RealCall.kt

internal fun initExchange(chain: RealInterceptorChain): Exchange {
  synchronized(this) {
    check(expectMoreExchanges) { "released" }
    check(!responseBodyOpen)
    check(!requestBodyOpen)
  }

  // 这个取的就是在 RetryAndFollowUpInterceptor 中创建的 ExchangeFinder
  val exchangeFinder = this.exchangeFinder!!
  // 调用 find 方法
  val codec = exchangeFinder.find(client, chain)
  val result = Exchange(this, eventListener, exchangeFinder, codec)
  this.interceptorScopedExchange = result
  this.exchange = result
  synchronized(this) {
    this.requestBodyOpen = true
    this.responseBodyOpen = true
  }

  if (canceled) throw IOException("Canceled")
  return result
}

ExchangeFinder.kt

fun find(
  client: OkHttpClient,
  chain: RealInterceptorChain
): ExchangeCodec {
  try {
    // 调用本身的 findHealthyConnection
    val resultConnection = findHealthyConnection(
        connectTimeout = chain.connectTimeoutMillis,
        readTimeout = chain.readTimeoutMillis,
        writeTimeout = chain.writeTimeoutMillis,
        pingIntervalMillis = client.pingIntervalMillis,
        connectionRetryEnabled = client.retryOnConnectionFailure,
        doExtensiveHealthChecks = chain.request.method != "GET"
    )
    return resultConnection.newCodec(client, chain)
  } catch (e: RouteException) {
    trackFailure(e.lastConnectException)
    throw e
  } catch (e: IOException) {
    trackFailure(e)
    throw RouteException(e)
  }
}

@Throws(IOException::class)
private fun findHealthyConnection(
  connectTimeout: Int,
  readTimeout: Int,
  writeTimeout: Int,
  pingIntervalMillis: Int,
  connectionRetryEnabled: Boolean,
  doExtensiveHealthChecks: Boolean
): RealConnection {
  while (true) {
    // 调用本身的 findConnection
    val candidate = findConnection(
        connectTimeout = connectTimeout,
        readTimeout = readTimeout,
        writeTimeout = writeTimeout,
        pingIntervalMillis = pingIntervalMillis,
        connectionRetryEnabled = connectionRetryEnabled
    )

    // Confirm that the connection is good.
    if (candidate.isHealthy(doExtensiveHealthChecks)) {
      return candidate
    }

    // If it isn't, take it out of the pool.
    candidate.noNewExchanges()

    // Make sure we have some routes left to try. One example where we may exhaust all the routes
    // would happen if we made a new connection and it immediately is detected as unhealthy.
    if (nextRouteToTry != null) continue

    val routesLeft = routeSelection?.hasNext() ?: true
    if (routesLeft) continue

    val routesSelectionLeft = routeSelector?.hasNext() ?: true
    if (routesSelectionLeft) continue

    throw IOException("exhausted all routes")
  }
}

@Throws(IOException::class)
private fun findConnection(
  connectTimeout: Int,
  readTimeout: Int,
  writeTimeout: Int,
  pingIntervalMillis: Int,
  connectionRetryEnabled: Boolean
): RealConnection {
  if (call.isCanceled()) throw IOException("Canceled")

  // 获取 RealCall 里的连接并尝试重用
  // Attempt to reuse the connection from the call.
  val callConnection = call.connection // This may be mutated by releaseConnectionNoEvents()!
  if (callConnection != null) {
    var toClose: Socket? = null
    synchronized(callConnection) {
      if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
        toClose = call.releaseConnectionNoEvents()
      }
    }

    // 连接未被释放,则重用
    // If the call's connection wasn't released, reuse it. We don't call connectionAcquired() here
    // because we already acquired it.
    if (call.connection != null) {
      check(toClose == null)
      return callConnection
    }

    // 连接已被释放则关闭 socket 并回调事件
    // The call's connection was released.
    toClose?.closeQuietly()
    eventListener.connectionReleased(call, callConnection)
  }

  // 创建新的连接需要刷新计数字段
  // We need a new connection. Give it fresh stats.
  refusedStreamCount = 0
  connectionShutdownCount = 0
  otherFailureCount = 0

  // 尝试从连接池(RealConnectionPool)中获取连接
  // 这里会判断连接是否可以被分配传送到指定地址
  // 判断后最终会执行 RealCall#acquireConnectionNoEvents
  // 这个方法判断拿到的 connection 会判断是否持有锁和判空
  // 通过判断最后会赋值到 RealCall#connection 中
  // RealCall 会以弱引用的形式被添加到 RealConnection#calls 中
  // RealConnection 会记录当前连接的请求
  // 此时没有路由
  // Attempt to get a connection from the pool.
  if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
    val result = call.connection!!
    // 成功后会回调,获取连接成功
    eventListener.connectionAcquired(call, result)
    return result
  }

  // 找合适的路由地址,先判断有没有已标记的,没有就尝试拿到一个新的路由
  // Nothing in the pool. Figure out what route we'll try next.
  val routes: List<Route>?
  val route: Route
  if (nextRouteToTry != null) {
    // Use a route from a preceding coalesced connection.
    routes = null
    route = nextRouteToTry!!
    nextRouteToTry = null
  } else if (routeSelection != null && routeSelection!!.hasNext()) {
    // Use a route from an existing route selection.
    routes = null
    route = routeSelection!!.next()
  } else {
    // Compute a new route selection. This is a blocking operation!
    var localRouteSelector = routeSelector
    if (localRouteSelector == null) {
      localRouteSelector = RouteSelector(address, call.client.routeDatabase, call, eventListener)
      this.routeSelector = localRouteSelector
    }
    val localRouteSelection = localRouteSelector.next()
    routeSelection = localRouteSelection
    routes = localRouteSelection.routes

    if (call.isCanceled()) throw IOException("Canceled")

    // 拿到路由地址列表后,再尝试找连接,如果找到直接返回
    // Now that we have a set of IP addresses, make another attempt at getting a connection from
    // the pool. We have a better chance of matching thanks to connection coalescing.
    if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
      val result = call.connection!!
      eventListener.connectionAcquired(call, result)
      return result
    }

    route = localRouteSelection.next()
  }

  // 找不到连接,则会根据路由新建一个 RealConnection
  // Connect. Tell the call about the connecting call so async cancels work.
  val newConnection = RealConnection(connectionPool, route)
  call.connectionToCancel = newConnection
  try {
    // 执行连接,是 RealConnection#connect
    newConnection.connect(
        connectTimeout,
        readTimeout,
        writeTimeout,
        pingIntervalMillis,
        connectionRetryEnabled,
        call,
        eventListener
    )
  } finally {
    call.connectionToCancel = null
  }
  // 记录路由
  call.client.routeDatabase.connected(newConnection.route())

  // If we raced another call connecting to this host, coalesce the connections. This makes for 3
  // different lookups in the connection pool!
  if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
    val result = call.connection!!
    nextRouteToTry = route
    newConnection.socket().closeQuietly()
    eventListener.connectionAcquired(call, result)
    return result
  }

  synchronized(newConnection) {
    connectionPool.put(newConnection)
    call.acquireConnectionNoEvents(newConnection)
  }

  eventListener.connectionAcquired(call, newConnection)
  return newConnection
}

RealConnection.kt

fun connect(
  connectTimeout: Int,
  readTimeout: Int,
  writeTimeout: Int,
  pingIntervalMillis: Int,
  connectionRetryEnabled: Boolean,
  call: Call,
  eventListener: EventListener
) {
  check(protocol == null) { "already connected" }

  var routeException: RouteException? = null
  val connectionSpecs = route.address.connectionSpecs
  val connectionSpecSelector = ConnectionSpecSelector(connectionSpecs)

  // HTTP 的请求判断
  if (route.address.sslSocketFactory == null) {
    if (ConnectionSpec.CLEARTEXT !in connectionSpecs) {
      throw RouteException(UnknownServiceException(
          "CLEARTEXT communication not enabled for client"))
    }
    val host = route.address.url.host
    if (!Platform.get().isCleartextTrafficPermitted(host)) {
      throw RouteException(UnknownServiceException(
          "CLEARTEXT communication to $host not permitted by network security policy"))
    }
  } else {
    if (Protocol.H2_PRIOR_KNOWLEDGE in route.address.protocols) {
      throw RouteException(UnknownServiceException(
          "H2_PRIOR_KNOWLEDGE cannot be used with HTTPS"))
    }
  }

  while (true) {
    try {
      if (route.requiresTunnel()) {
        // 返回通过 HTTP 代理创建 TLS 隧道的请求(未加密地发送到代理服务器)
        // 默认使用 HTTP_1_1 
        // 先通过建立连接通道(Proxy-Connection: Keep-Alive),保持长连接
        // 调用链: createTunnelRequest -> connectSocket -> Platform.get().connectSocket -> socket.connect() -> SocketImpl.connect()
        // 最终是通过 Socket 进行连接,具体代码就不拷贝了,可以自行看看
        connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener)
        if (rawSocket == null) {
          // We were unable to connect the tunnel but properly closed down our resources.
          break
        }
      } else {
        // 直接连接 socket 处理 HTTP 的请求连接
        connectSocket(connectTimeout, readTimeout, call, eventListener)
      }
      // 建立协议
      // 会先判断 sslSocketFactory 是否为空,为空的话就是普通的 HTTP 请求
      // 再判断 HTTP 版本号
      // 是否使用的是 HTTP_2 协议如果是会通过 startHttp2 执行请求
      // 否则默认还是使用 HTTP_1_1 协议
      // 最终通过 connectTls 建立 TLS 连接
      establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener)
      // 回调连接结束
      eventListener.connectEnd(call, route.socketAddress, route.proxy, protocol)
      break
    } catch (e: IOException) {
      socket?.closeQuietly()
      rawSocket?.closeQuietly()
      socket = null
      rawSocket = null
      source = null
      sink = null
      handshake = null
      protocol = null
      http2Connection = null
      allocationLimit = 1

      // 回调连接失败
      eventListener.connectFailed(call, route.socketAddress, route.proxy, null, e)

      // 异常分发
      if (routeException == null) {
        routeException = RouteException(e)
      } else {
        routeException.addConnectException(e)
      }

      if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
        throw routeException
      }
    }
  }

  if (route.requiresTunnel() && rawSocket == null) {
    throw RouteException(ProtocolException(
        "Too many tunnel connections attempted: $MAX_TUNNEL_ATTEMPTS"))
  }

  idleAtNs = System.nanoTime()
}

@Throws(IOException::class)
private fun connectTls(connectionSpecSelector: ConnectionSpecSelector) {
  val address = route.address
  val sslSocketFactory = address.sslSocketFactory
  var success = false
  var sslSocket: SSLSocket? = null
  try {
    // 在原始 socket(前面创建的)上通过 sslSocketFactory 包一层
    // Create the wrapper over the connected socket.
    sslSocket = sslSocketFactory!!.createSocket(
        rawSocket, address.url.host, address.url.port, true /* autoClose */) as SSLSocket

    // 配置 socket 密码、TLS 版本和扩展
    // Configure the socket's ciphers, TLS versions, and extensions.
    val connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket)
    if (connectionSpec.supportsTlsExtensions) {
      Platform.get().configureTlsExtensions(sslSocket, address.url.host, address.protocols)
    }

    // 开始握手
    // Force handshake. This can throw!
    sslSocket.startHandshake()
    // block for session establishment
    val sslSocketSession = sslSocket.session
    val unverifiedHandshake = sslSocketSession.handshake()

    // 验证目标主机是否可以接受套接字的证书
    // Verify that the socket's certificates are acceptable for the target host.
    if (!address.hostnameVerifier!!.verify(address.url.host, sslSocketSession)) {
      val peerCertificates = unverifiedHandshake.peerCertificates
      if (peerCertificates.isNotEmpty()) {
        val cert = peerCertificates[0] as X509Certificate
        throw SSLPeerUnverifiedException("""
            |Hostname ${address.url.host} not verified:
            |    certificate: ${CertificatePinner.pin(cert)}
            |    DN: ${cert.subjectDN.name}
            |    subjectAltNames: ${OkHostnameVerifier.allSubjectAltNames(cert)}
            """.trimMargin())
      } else {
        throw SSLPeerUnverifiedException(
            "Hostname ${address.url.host} not verified (no certificates)")
      }
    }

    // 返回地址的证书 pinner,如果不是 HTTPS 地址,则返回 null
    val certificatePinner = address.certificatePinner!!

    // 根据未验证的 TLS 握手记录新建一个 TLS 握手记录
    handshake = Handshake(unverifiedHandshake.tlsVersion, unverifiedHandshake.cipherSuite,
        unverifiedHandshake.localCertificates) {
      certificatePinner.certificateChainCleaner!!.clean(unverifiedHandshake.peerCertificates,
          address.url.host)
    }

    // 检查证书
    // Check that the certificate pinner is satisfied by the certificates presented.
    certificatePinner.check(address.url.host) {
      handshake!!.peerCertificates.map { it as X509Certificate }
    }

    // 成功,根据平台选择对应的应用层协议
    // Success! Save the handshake and the ALPN protocol.
    val maybeProtocol = if (connectionSpec.supportsTlsExtensions) {
      Platform.get().getSelectedProtocol(sslSocket)
    } else {
      null
    }
    socket = sslSocket
    source = sslSocket.source().buffer()
    sink = sslSocket.sink().buffer()
    // 找不到默认就是 HTTP1.1
    protocol = if (maybeProtocol != null) Protocol.get(maybeProtocol) else Protocol.HTTP_1_1
    success = true
  } finally {
    // 释放资源
    if (sslSocket != null) {
      Platform.get().afterHandshake(sslSocket)
    }
    if (!success) {
      sslSocket?.closeQuietly()
    }
  }
}
Copy the code

The ConnectInterceptor mainly checks whether the current connection is available. If it is available, it returns the connection directly. If it is not available, it obtains an available connection from the connection pool. The TLS and TCP handshake is performed to add the newly created connection to the connection pool. Before the connection is created, OkHttp also determines whether the HTTP connection needs a tunnel connection and adds the corresponding property RealConnection#createTunnelRequest if it does. If not, the socket connection is made directly. During protocol establishment, the system checks whether the connection is HTTPS. If the connection is not HTTPS, the system checks whether the connection is normal. If the connection is TLS, the system checks whether the connection is enabled. Finally, there is the CallServerInterceptor, the request server interceptor.

CallServerInterceptor

Access server interceptor, which makes network calls to the server and is the last interceptor in the chain.

CallServerInterceptor.kt @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response {val realChain = chain as RealInterceptorChain // Exchange HTTP1.1 codec corresponds to Http1ExchangeCodec // HTTP2 Val exchange = realchain-exchange!! val request = realChain.request val requestBody = request.body val sentRequestMillis = System.currentTimeMillis() var invokeStartEvent = true var responseBuilder: Response.Builder? = null var sendRequestException: IOException? = null try {/ / write request head exchange. WriteRequestHeaders (request) / / a request body is written (mainly written and refresh the logic), Otherwise according to request if no request body (HttpMethod. PermitsRequestBody (request) method) && requestBody! = null) { // If there's a "Expect: 100-continue" header on the request, Wait for a "HTTP/1.1 100 // Continue" response before transmitting the request body. If we don't get that, return // what we did get (such as a 4xx response) without ever transmitting the request body. if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) { exchange.flushRequest() responseBuilder = exchange.readResponseHeaders(expectContinue = true) exchange.responseHeadersStart() invokeStartEvent = false } if (responseBuilder == null) { if (requestBody.isDuplex()) { // Prepare a duplex body so that the application can send a request body later. exchange.flushRequest() val bufferedRequestBody = exchange.createRequestBody(request, true).buffer() requestBody.writeTo(bufferedRequestBody) } else { // Write the request body if the "Expect: 100-continue" expectation was met. val bufferedRequestBody = exchange.createRequestBody(request, false).buffer() requestBody.writeTo(bufferedRequestBody) bufferedRequestBody.close() } } else { exchange.noRequestBody()  if (! exchange.connection.isMultiplexed) { // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection // from being reused. Otherwise we're still obligated to transmit the request body to // leave the connection in a consistent state. exchange.noNewExchangesOnConnection() } } } else { exchange.noRequestBody() } / / if there is no request body, then request to refresh to the underlying socket and signal that no longer transmission byte if (requestBody = = null | |! requestBody.isDuplex()) { exchange.finishRequest() } } catch (e: IOException) {/ / IO exception handling the if is ConnectionShutdownException (e) {throw e / / No request was sent so there 's No response  to read. } if (! exchange.hasFailure) { throw e // Don't attempt to read the response; We failed to send the request.} sendRequestException = e} try {// Handle response if (responseBuilder == null) {// Parse from HTTP The transmission of the Response header bytes and returns the Response. The Builder responseBuilder = exchange. ReadResponseHeaders (expectContinue = false)!!!!! If (invokeStartEvent) {exchange. ResponseHeadersStart () invokeStartEvent = false}} / / get a response, Record the HTTP response code var response = responseBuilder. Request (request) handshake (exchange. Connection. Handshake ()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build() var code = Response. code if (code == 100) {// Server sent a 100-continue even though we did not request one. Try again to read the // actual response status. responseBuilder = exchange.readResponseHeaders(expectContinue = false)!! if (invokeStartEvent) { exchange.responseHeadersStart() } response = responseBuilder .request(request) .handshake(exchange.connection.handshake()) .sentRequestAtMillis(sentRequestMillis) ReceivedResponseAtMillis (System. CurrentTimeMillis ()). The build () code. = the response code} / / end of the header information Exchange. ResponseHeadersEnd (response) / / response = if get a response body (forWebSocket && code = = 101) {/ / Connection is upgrading,  but we need to ensure interceptors see a non-null response body. response.newBuilder() .body(EMPTY_RESPONSE) .build() } The else {response. NewBuilder (). The body (exchange. OpenResponseBody (response)). The build ()} / / according to the response code to do the if the corresponding treatment ("close".equals(response.request.header("Connection"), ignoreCase = true) || "close".equals(response.header("Connection"), ignoreCase = true)) { exchange.noNewExchangesOnConnection() } if ((code == 204 || code == 205) && response.body? .contentLength() ? : -1L > 0L) { throw ProtocolException( "HTTP $code had non-zero Content-Length: ${response.body? .contentLength()}")} return response} catch (e: IOException) {if (sendRequestException! = null) { sendRequestException.addSuppressed(e) throw sendRequestException } throw e } }Copy the code

The Http1ExchangeCodec and Http2ExchangeCodec mentioned here are used for streaming read and write. BufferedSink (output stream) and BufferedSource (input stream) are tools provided by OKio for writing request headers and body, and reading response headers and body. This section of the code shows that the CallServerInterceptor is the last interceptor to receive data from the server. When it receives data from the server, it returns it directly to the previous interceptor. In the chain of responsibility will eventually return to RetryAndFollowupInterceptor returns, and getResponseWithInterceptorChain this method can get the response, and returns the response in the synchronization method, The whole chain of calls is complete.

Source code summary

API to summarize

OkHttpClient: a factory for sending HTTP requests and getting their responses. Realcall. AsyncCall: is a Runnable for handling asynchronous requests. Policy for executing requests RealInterceptorChain: Specific chain of interceptors, carrying the whole chain of interceptors, the last is the network the caller (for application interceptors, exchange must be empty, for network interceptors, exchange must be not empty) RetryAndFollowUpInterceptor: Retry and Redirection Interceptor ExchangeFinder: Attempts to find the connection of the exchange and any subsequent retry policies (mainly for establishing a connection) A CacheInterceptor, a bridge between application code and network code that first builds network requests based on user requests, then continues to call the network, and finally builds user responses based on network responses: ConnectInterceptor: A connection interceptor that opens a connection to the target server and continues to the next interceptor. The network may be used to return the response or to verify the cached response using the condition GET: A connection to a remote Web server that can host one or more concurrent streams. The life cycle of a connection has two phases. When connecting, the connection is owned by a single call using a single thread. In this phase, the connection is not shared and does not need to be locked. Connection pool, maintain connection queue (ConcurrentLinkedQueue) and Clean queue (TaskQueue) CallServerInterceptor: The last interceptor in the chain Http1ExchangeCodec: a socket connection used to send HTTP/1.1 messages. Strictly enforce the following life cycle. Send request header (writeRequest) 2. Open a receiver to write the request body (newKnownLengthSink or newChunkedSink) 3. Write and then close the receiver ReadResponseHeaders (newFixedLengthSource, newChunkedSource, newUnknownLengthSource) NewFixedLengthSource (0) can be called and the source Http2ExchangeCodec can be skipped. Request and response are encoded using HTTP/2 frames

Calling process

RealCall#execute(); Dispatcher#executed() Place the call on the synchronous call queue (runningSyncCalls, Actual ArrayQueue) 3, through RealCall# getResponseWithInterceptorChain began to enter the chain of responsibility for an asynchronous call 1, by constructing OkHttpClient network Request and the Request is constructed RealCall# enQueue (); responseCallback (Callback); AsyncCall (); Execute AsyncCall#executeOn() via Dispatcher#promoteAndExecute() and pass the created thread pool as an argument. Perform AsyncCall# run () 6, in AsyncCall# run () by RealCall# getResponseWithInterceptorChain () began to request access to the chain of responsibility for network, ResponseCallback: onFailure(); onFailure()

The problem

OkHttp is actually connected to the network through sockets, and will determine whether to enable proxy tunnel according to the configuration (the purpose is to use HTTP proxy request HTTPS). Enable (connectTunnel) if necessary; otherwise, a TCP connection (connectSocket) is directly established. Whether or not a tunnel needs to be enabled, a TCP connection is established (connectSocket is called). Finally a TCP connection is opened with a call to platform.get ().connectsocket () (Socket#connect())

Response.body ().string() is called closeQuietly(), so you can cache a copy or customize the interceptor to process the log

Design pattern Constructor (OkHttpClient, Request object creation) Factory (Get Call interface instance) Singleton (Platform type) Policy (CacheInterceptor, Policy pattern is used in the selection of response data, cache data or network data) Chain of responsibility pattern (chain call of interceptor) share element pattern (shared technology, support reuse) (In Dispatcher thread pool, unlimited thread pool implements object reuse)

Arraydeques (runningAsyncCalls and readyAsyncCalls) runningAsyncCalls are used to store requests that are being executed. ReadyAsyncCalls are used to hold requests that are ready for execution, because the Dispatcher default supports a maximum of 64 maxRequests and a maximum of 5 requests per Host. The Call will be placed in readyAsyncCalls, and when there is an idle thread, the readyAsyncCalls thread will be moved to runningAsyncCalls to execute the request. RunningAsyncCalls are added to Dispatcher#promoteAndExecute() as long as the number of requests in progress < 64 && the number of requests in progress < 5 for the same domain name. Otherwise it will be put in readAsyncCalls.

thinking

I was hoping to use OkHttp’s code to get a sense of the layering of the network, but the library focuses on logic at the application level. It’s important to note that the BridgeInterceptor interceptor is, in my view, a bridge over data logic (not a bridge over network layering). But also realized that even the development of the application layer, which involves a lot of knowledge (such as the establishment of a proxy tunnel to proxy request HTTPS operation is very strong). There is also an abstract HandShake in HTTPS (also used to describe the completed HandShake), thread pool for reuse in asynchronous calls (use of ThreadPoolExecutor), and so on. Finally, because I understand the main process of OkHttp, I do not have a detailed understanding of many details, such as RealConnection and RealConnectionPool, they do not understand the details of….. If there is a wrong understanding hope readers can correct, we progress together ~

The resources

  • The official documentation
  • OkHttp source code
  • OkHttp source code
  • Android mainstream open source framework (three) OkHttp source code analysis
  • Old talk, look at network request from OkHttp principle