Basic use of OkHttp
Implementation ‘com. Squareup. Okhttp3: okhttp: 3.10.0’
Private void getFuncEnqueue() {String url = "https://www.baidu.com"; OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(url) .get() .build(); Call call = client.newCall(request); Callback callback = new Callback() { @Override public void onFailure(Call call, IOException e) { Log.i(TAG, "getFuncEnqueue onFailure"); } @Override public void onResponse(Call call, Response response) throws IOException { ResponseBody body = response.body(); if (body == null) { Log.i(TAG, "getFuncEnqueue onResponse error"); return; } Log.i(TAG, "getFuncEnqueue onResponse:" + body); }}; call.enqueue(callback); } private void getFuncExecute() {String url = "https://www.baidu.com"; OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(url) .get() .build(); Call call = client.newCall(request); Runnable runnable = new Runnable() { @Override public void run() { try { Response response = call.execute(); ResponseBody body = response.body(); if (body == null) { Log.i(TAG, "getFuncExecute onResponse error"); return; } Log.i(TAG, "getFuncExecute onResponse:" + body); } catch (IOException e) { Log.i(TAG, "getFuncExecute onResponse IOException"); }}}; new Thread(runnable).start(); } / / post request private void postFuncEnqueue () {String url = "https://www.wanandroid.com/navi/json"; OkHttpClient client = new OkHttpClient(); RequestBody requestBody = new FormBody.Builder() .add("key1","value1") .add("key2","value3") .build(); Request request = new Request.Builder() .url(url) .post(requestBody) .build(); Call call = client.newCall(request); Callback callback = new Callback() { @Override public void onFailure(Call call, IOException e) { Log.i(TAG, "postFuncEnqueue onFailure"); } @Override public void onResponse(Call call, Response response) throws IOException { ResponseBody body = response.body(); if (body == null) { Log.i(TAG, "postFuncEnqueue onResponse error"); return; } Log.i(TAG, "postFuncEnqueue onResponse:" + body); }}; call.enqueue(callback); }Copy the code
OkHttp process
Okhttpclient. newCall(Request) ==>==> Execute asynchronous Request call.enQueue (callback) Call. Execute () ==> Dispatcher dispatches request task Dispatcher ==> Interceptor makes request and returns result Interceptor ==> ResponseCopy the code
RealCall
RealCall implements and is the only implementation of the Call interface
Call interface
Public interface Call extends Cloneable {// Call extends Request Request (); Response execute() throws IOException; Void enqueue(Callback responseCallback); // Cancel the request void cancel(); // Call Whether Boolean isExecuted(); // Request whether to cancel Boolean isCanceled(); Call clone(); // Factory method; Implemented by OkHttpClient; RealCall Interface Factory {Call newCall(Request Request); }}Copy the code
RealCall#request
// Private RealCall(OkHttpClient client, Request originalRequest, Boolean forWebSocket) {this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); Static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { RealCall call = new RealCall(client, originalRequest, forWebSocket); call.eventListener = client.eventListenerFactory().create(call); return call; } return originalRequest @override public Request Request () {return originalRequest; }Copy the code
RealCall#execute
A synchronous request
@override public Response execute() throws IOException {// Synchronized (this) {if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); eventListener.callStart(this); Try {// Call client.dispatcher().executed(this); / / back to the Response by Interceptor Response result = getResponseWithInterceptorChain (); if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { eventListener.callFailed(this, e); throw e; } finally { client.dispatcher().finished(this); }}Copy the code
RealCall#enqueue
An asynchronous request
Override public void enqueue(Callback responseCallback) {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); eventListener.callStart(this); // Call from Dispatcher; AsyncCall client.dispatcher().enqueue(new AsyncCall(responseCallback)); }Copy the code
RealCall$AsyncCall
AsyncCall is a RealCall inner class; For asynchronous requests
Final class AsyncCall extends NamedRunnable {// Callback is a Callback interface that requires two external methods: onFailure/onResponse; Private final Callback responseCallback; AsyncCall(Callback responseCallback) { super("OkHttp %s", redactedUrl()); this.responseCallback = responseCallback; } String host() { return originalRequest.url().host(); } Request request() { return originalRequest; } RealCall get() { return RealCall.this; } @Override protected void execute() { boolean signalledCallback = false; Try {/ / back to the Response by Interceptor Response Response = getResponseWithInterceptorChain (); if (retryAndFollowUpInterceptor.isCanceled()) { signalledCallback = true; Results responseCallback. / / by the Callback returns the response body onFailure (RealCall. This new IOException (" Canceled ")); } else { signalledCallback = true; Results responseCallback. / / by the Callback returns the response body onResponse (RealCall. This response). } } catch (IOException e) { if (signalledCallback) { // Do not signal the callback twice! Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); } else { eventListener.callFailed(RealCall.this, e); responseCallback.onFailure(RealCall.this, e); }} finally {// Request completion client.dispatcher().finished(this); }}}Copy the code
RealCall#getResponseWithInterceptorChain
The response body is obtained by Interceptor. Execute in RealCall synchronous request RealCall#execute and asynchronous request RealCall$AsyncCall#execute; Is the core function that actually performs the requested work;
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); AddAll (client.interceptors()); / / retry and redirect the interceptor interceptors. Add (retryAndFollowUpInterceptor); Add (new BridgeInterceptor(client.cookiejar ())); Add (new CacheInterceptor(client.internalCache())); // Interceptors. add(new ConnectInterceptor(client)); if (! ForWebSocket) {/ / the user custom network interceptor interceptors. AddAll (client.net workInterceptors ()); } // Request server interceptors.add(new CallServerInterceptor(forWebSocket)); // RealInterceptorChain is the only implementation of the Interceptor.Chain responsibility Chain new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); return chain.proceed(originalRequest); }Copy the code
Dispatcher
Request the task distributor
The Dispatcher constructor
Public final class Dispatcher {// Maximum number of asynchronous requests private int maxRequests = 64; Private int maxRequestsPerHost = 5; private int maxRequestsPerHost = 5; Private @nullable Runnable idleCallback; private @nullable Runnable idleCallback; // The thread pool used by asynchronous requests private @nullable ExecutorService ExecutorService; Private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); Private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); Private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); public Dispatcher(ExecutorService executorService) { this.executorService = executorService; } public Dispatcher() { } public synchronized ExecutorService executorService() { if (executorService == null) { // The default thread pool implementation is 0 core threads + SynchronousQueue for maximum throughput // 0 core threads, no threads cached; // Use SynchronousQueue as thread wait queue; SynchronousQueue is a queue that has no capacity; Without capacity, each time adding a task to the queue will fail. // Failed to add the task to the queue. If there are no free threads, create a new thread to execute the task. If there are idle threads, they are reused. executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; }}Copy the code
Dispatcher#executed
A synchronous request
Synchronized void executed(RealCall call) {runningSynccalls.add (call); synchronized void executed(RealCall call) {runningSynccalls.add (call); }Copy the code
Dispatcher#enqueue
An asynchronous request
Synchronized void enqueue(AsyncCall Call) {// If the number of tasks in progress is less than 64 and the number of requests from the same domain name is less than 5, the synchronized void enqueue(AsyncCall Call) {// The synchronized void enqueue(AsyncCall Call) { If (runningAsynccalls.size () < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); }}Copy the code
Dispatcher#finished
After each task is completed, the FINISHED method is executed
Void finished(AsyncCall Call) {finished(runningAsyncCalls, call, true); } void finished(RealCall Call) {finished(runningSyncCalls, call, false); } private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) { int runningCallsCount; Runnable idleCallback; Synchronized (this) {// synchronized (this) {if (! calls.remove(call)) throw new AssertionError("Call wasn't in-flight!" ); If (promoteCalls) promoteCalls(); RunningCallsCount = runningCallsCount(); idleCallback = this.idleCallback; } if (runningCallsCount == 0 && idleCallback!) if (runningCallsCount == 0 && idleCallback! = null) { idleCallback.run(); }}Copy the code
Dispatcher#promoteCalls
Reconfigure the asynchronous task queue. Add the request from the waiting execution queue to the executing queue.
Private void promoteCalls() {// If (runningAsynccalls.size () >= maxRequests) return; // If (runningAsynccalls.size () >= maxRequests) return; If (readyAsyncCalls.isempty ()) return; readyAsynccalls.isempty ()) return; For (Iterator<AsyncCall> I = readyAsynccalls.iterator (); i.hasNext(); ) { AsyncCall call = i.next(); If (runningCallsForHost(call) < maxRequestsPerHost) { Join the thread pool and execute i.remove(); runningAsyncCalls.add(call); executorService().execute(call); If (runningAsynccalls.size () >= maxRequests) return; // If (runningAsynccalls.size () >= maxRequests) return; // Reached max capacity. } }Copy the code
Interceptor
OkHttp implements five interceptors by default, all of which implement the Interceptor interface
Interceptor interface
Public interface Interceptor {// Interceptor; Interceptor core functions; Each interceptor implements its own function Response Intercept (Chain Chain) throws IOException; // Interceptor inner class; Interface Chain {// Interceptor association Request Request (); // pass to the next interceptor method; Throws IOException 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
RealInterceptorChain
The Interceptor$Chain class is the only implementation of the Interceptor$Chain
Public final class RealInterceptorChain implements Interceptor.Chain {/** Private Final List<Interceptor> Interceptors; private Final List<Interceptor> interceptors; @Override public Request request() { return request; @override public Response proceed(Request Request) throws IOException {return proceed(Request, streamAllocation, httpCodec, connection); } public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection Connection) throws IOException {// The index on the chain exceeds the interceptor set size, If (index >= interceptors.size()) throw new AssertionError(); // By incrementing the index on the chain [index + 1], RealInterceptorChain next = new RealInterceptorChain(Interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout); Interceptor = interceptors.get(index); // Interceptor = interceptors.get(index); Response Response = interceptor.intercept(next); Response = interceptor.intercept(next); return response; }}Copy the code
RetryAndFollowUpInterceptor
Retry and redirection Interceptors are responsible for determining whether the request needs to be retried and whether a redirection is required based on the response code, restarting all interceptors if a redirection is required
RetryAndFollowUpInterceptor#intercept
Interception method concrete implementation
@override public Response Intercept (Chain Chain) throws IOException {/** Int followUpCount = 0; Response priorResponse = null; // A while (true) loop that continues until return while (true) {// Cancels the request and throws an exception that ends if (canceled) {streamallocation.release (); throw new IOException("Canceled"); } Response response; boolean releaseConnection = true; // Determine whether the request needs to be retried based on the exception type in try/catch. Proceed (request, streamAllocation, null, null); releaseConnection = false; } catch (RouteException e) { // The attempt to connect via a route failed. The request will not have been sent. if (! recover(e.getLastConnectException(), streamAllocation, false, request)) { throw e.getLastConnectException(); } releaseConnection = false; continue; } catch (IOException e) { // An attempt to communicate with a server failed. The request may have been sent. boolean requestSendStarted = ! (e instanceof ConnectionShutdownException); if (! recover(e, streamAllocation, requestSendStarted, request)) throw e; releaseConnection = false; continue; } finally { // We're throwing an unchecked exception. Release any resources. if (releaseConnection) { streamAllocation.streamFailed(null); streamAllocation.release(); If (priorResponse! = null) { response = response.newBuilder() .priorResponse(priorResponse.newBuilder() .body(null) .build()) .build(); } Request followUp = followUpRequest(response, streamallocation.route ()); FollowUp == null {if (followUp == null) {if (! forWebSocket) { streamAllocation.release(); } return response; } /** * How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox, * curl, and wget follow 20; Safari follows 16; */ / Private static Final int MAX_FOLLOW_UPS = 1; and HTTP/1.0 Recommends 5. */ / Private static final int MAX_FOLLOW_UPS = 1; // The maximum number of redirects allowed is 20; If (++followUpCount > MAX_FOLLOW_UPS) {streamAllocation. Release (); throw new ProtocolException("Too many follow-up requests: " + followUpCount); } // This redirection request is assigned to a new request request = followUp; PriorResponse = response; priorResponse = response; }}Copy the code
RetryAndFollowUpInterceptor#recover
Request Retry judgment
private boolean recover(IOException e, StreamAllocation streamAllocation, boolean requestSendStarted, Request userRequest) { streamAllocation.streamFailed(e); // If OkHttpClient does not allow retry, retry is not allowed; This parameter allows retries if (! client.retryOnConnectionFailure()) return false; Request body belonging to UnrepeatableRequestBody, RequestSendStarted && UserRequest.body () instanceof UnrepeatableRequestBody) return false; If (! isRecoverable(e, requestSendStarted)) return false; // If (! streamAllocation.hasMoreRoutes()) return false; // In other cases, return true is allowed to retry; }Copy the code
Private Boolean isRecoverable(IOException e, Boolean requestSendStarted) {// Protocol exception, If (e instanceof ProtocolException) {return false; If (e instanceof InterruptedIOException) {return e instanceof SocketTimeoutException &&! requestSendStarted; } // The SSL handshake is abnormal, and the certificate is abnormal. SSLHandshakeException {// If the problem was a CertificateException from the X509TrustManager, // do not retry. if (e.getCause() instanceof CertificateException) { return false; }} // The SSL handshake is not authorized. Not allowed to retry the if (e instanceof SSLPeerUnverifiedException) {/ / anyone with a certificate pinning error. Return false. } // In other cases, return true is allowed to retry; }Copy the code
RetryAndFollowUpInterceptor#followUpRequest
Request redirection judgment
Private Request followUpRequest(Response userResponse, Route Route) throws IOException {/** omit If (userResponse == null) throw new IllegalStateException(); int responseCode = userResponse.code(); final String method = userResponse.request().method(); // HTTP response code classification: // class 100, the server receives the request, the client continues to send the request; // Class 200, request sent and processing completed; // Class 300 needs to be redirected to a new address for further operations; // Class 400, client exception; // Class 500, server exception; Switch (responseCode) {// 407 case HTTP_PROXY_AUTH: return client.proxyAuthenticator().authenticate(route, userResponse); // 401 Response code case HTTP_UNAUTHORIZED: return client.authenticator().authenticate(route, userResponse); // 307/308 Response code case HTTP_PERM_REDIRECT: case HTTP_TEMP_REDIRECT: if (! method.equals("GET") && ! method.equals("HEAD")) { return null; } // 300-303 Response code case HTTP_MULT_CHOICE: case HTTP_MOVED_PERM: case HTTP_MOVED_TEMP: case HTTP_SEE_OTHER: return requestBuilder.url(url).build(); // 408 Case HTTP_CLIENT_TIMEOUT: return userResponse.request(); // 503 response code case HTTP_UNAVAILABLE: if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) { return userResponse.request(); } return null; // If null is returned by default, no redirection is required. }}Copy the code
BridgeInterceptor
The bridge interceptor is responsible for concatenating HTTP protocol request headers, determining whether gzip compression and gzip parsing are required, and Cookie handling
BridgeInterceptor#intercept
@Override public Response intercept(Chain chain) throws IOException { Request userRequest = chain.request(); Request.Builder requestBuilder = userRequest.newBuilder(); // "content-type" RequestBody body = userRequest.body(); if (body ! = null) { MediaType contentType = body.contentType(); if (contentType ! = null) { requestBuilder.header("Content-Type", contentType.toString()); } long contentLength = body.contentLength(); if (contentLength ! = -1) { requestBuilder.header("Content-Length", Long.toString(contentLength)); requestBuilder.removeHeader("Transfer-Encoding"); } else { requestBuilder.header("Transfer-Encoding", "chunked"); requestBuilder.removeHeader("Content-Length"); If (userRequest.header("Host") == null) {requestBuilder.header("Host", hostHeader(userRequest.url()), false)); If (userRequest.header("Connection") == null) {requestBuilder.header("Connection", "keep-alive "); } // splice "accept-encoding"; Boolean transparentGzip = false; if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) { transparentGzip = true; requestBuilder.header("Accept-Encoding", "gzip"); } // concatenate "Cookie" List<Cookie> cookies = cookiejar.loadForRequest (userRequest.url()); if (! cookies.isEmpty()) { requestBuilder.header("Cookie", cookieHeader(cookies)); If (userRequest.header(" user-agent ") == null) {requestBuilder.header(" user-agent ") == null) {requestBuilder (" user-agent "); Version.userAgent()); } Response networkResponse = chain.proceed(requestBuilder.build()); HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()); Response.Builder responseBuilder = networkResponse.newBuilder() .request(userRequest); If (transparentGzip && "gzip".equalsignorecase (networkResponse.header(" content-encoding ")) && HttpHeaders.hasBody(networkResponse)) { GzipSource responseBody = new GzipSource(networkResponse.body().source()); Headers strippedHeaders = networkResponse.headers().newBuilder() .removeAll("Content-Encoding") .removeAll("Content-Length") .build(); responseBuilder.headers(strippedHeaders); String contentType = networkResponse.header("Content-Type"); responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody))); } return responseBuilder.build(); }Copy the code
CacheInterceptor
The cache interceptor is responsible for determining whether caching is used, and if caching is used, the cached result is used as the result of the request response. OkHttp only supports caching GET requests by default
CacheInterceptor#intercept
@override public Response Intercept (Chain Chain) throws IOException {// Check whether a cache is required. Candidate Response cacheCandidate = cache! = null ? cache.get(chain.request()) : null; long now = System.currentTimeMillis(); // Cache policy class: CacheStrategy / / CacheStrategy.net workRequest is null, then the request does not need to use the Internet. / / CacheStrategy cacheResponse is null, // networkRequest == NULL /cacheResponse! = null; You don't need to use the network, you need to use the cache; // networkRequest! = null/cacheResponse == null; Need to use the network, do not need to use cache; Send a request directly to the server // networkRequest == NULL /cacheResponse == NULL; No need to use network, no need to use cache; Return 504 Error code // networkRequest! = null/cacheResponse ! = null; You need to use the network, you need to use the cache; CacheStrategy Strategy = new cacheStrategy.factory (now, chain-.request (), cacheCandidate).get(); Request networkRequest = strategy.networkRequest; Response cacheResponse = strategy.cacheResponse; if (cache ! = null) { cache.trackResponse(strategy); } // The cache candidate is not null, but if cacheResponse is NULL, caching is not required. If (cacheCandidate! = null && cacheResponse == null) { closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. If (networkRequest == NULL && cacheResponse == NULL) {return new Response.Builder().request(chain.request()) .protocol(Protocol.HTTP_1_1) .code(504) .message("Unsatisfiable Request (only-if-cached)") .body(Util.EMPTY_RESPONSE) .sentRequestAtMillis(-1L) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); } // No need to use network, need to use cache; If (networkRequest == null) {return Cacheresponse.newBuilder ().Cacheresponse (stripBody) cacheResponse) .build(); } Response networkResponse = null; try { networkResponse = chain.proceed(networkRequest); } finally { // If we're crashing on I/O or otherwise, don't leak the cache body. if (networkResponse == null && cacheCandidate ! = null) { closeQuietly(cacheCandidate.body()); }} // Network needs to be used, cache needs to be used; Initiate a request, and if the response is 304, update the cache and return the result if (cacheResponse! = null) {// If response is 304, If (networkResponse.code() == HTTP_NOT_MODIFIED) {Response 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.update(cacheResponse, response); return response; } else {// If the response is not 304, empty the cache content closeQuietly(cacheresponse.body ()); }} // In other cases, network needs to be used, not cache; Request directly to the server Response Response. = networkResponse newBuilder () cacheResponse (stripBody (cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); if (cache ! = null) { if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) { // Offer this request to the cache. CacheRequest cacheRequest = cache.put(response); return cacheWritingResponse(cacheRequest, response); } if (HttpMethod.invalidatesCache(networkRequest.method())) { try { cache.remove(networkRequest); } catch (IOException ignored) { // The cache cannot be written. } } } return response; }Copy the code
CacheStrategy
Cache Policy class
Public CacheStrategy get() {// CacheStrategy class: CacheStrategy / / CacheStrategy.net workRequest is null, then the request does not need to use the Internet. / / CacheStrategy cacheResponse is null, // networkRequest == NULL /cacheResponse! = null; You don't need to use the network, you need to use the cache; // networkRequest! = null/cacheResponse == null; Need to use the network, do not need to use cache; Send a request directly to the server // networkRequest == NULL /cacheResponse == NULL; No need to use network, no need to use cache; Return 504 Error code // networkRequest! = null/cacheResponse ! = null; You need to use the network, you need to use the cache; Make a request, if 304, update the cache and return the result CacheStrategy candidate = getCandidate(); // networkRequest ! = null requests need to use network, but the request is set to use cache only; // networkRequest == NULL /cacheResponse == null if (candidate.networkRequest! = null && request.cacheControl().onlyIfCached()) { // We're forbidden from using the network and the cache is insufficient. return new CacheStrategy(null, null); } return candidate; } /** Returns a strategy to use assuming the request can use the network. */ private CacheStrategy getCandidate() { // CacheResponse == NULL, no cache is required; // return networkRequest! = null/cacheResponse == null if (cacheResponse == null) { return new CacheStrategy(request, null); } // This request is HTTPS, but there is no corresponding handshake information in the cache, so the cache is invalid. = null/cacheResponse == null if (request.isHttps() && cacheResponse.handshake() == null) { return new CacheStrategy(request, null); } // Determine whether the cache is available according to the response code; If not available, return directly; // networkRequest! = null/cacheResponse == null if (! isCacheable(cacheResponse, request)) { return new CacheStrategy(request, null); } networkRequest! = networkRequest! = null/cacheResponse == null CacheControl requestCaching = request.cacheControl(); if (requestCaching.noCache() || hasConditions(request)) { return new CacheStrategy(request, null); } // networkRequest == NULL /cacheResponse! // networkRequest == NULL /cacheResponse! = null CacheControl responseCaching = cacheResponse.cacheControl(); if (responseCaching.immutable()) { return new CacheStrategy(null, cacheResponse); } long ageMillis = cacheResponseAge(); long freshMillis = computeFreshnessLifetime(); if (requestCaching.maxAgeSeconds() ! = -1) { freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds())); } long minFreshMillis = 0; if (requestCaching.minFreshSeconds() ! = -1) { minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds()); } long maxStaleMillis = 0; if (! responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() ! = -1) { maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds()); } networkRequest == null/cacheResponse! = null if (! responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) { Response.Builder builder = cacheResponse.newBuilder(); if (ageMillis + minFreshMillis >= freshMillis) { builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\""); } long oneDayMillis = 24 * 60 * 60 * 1000L; if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) { builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\""); } return new CacheStrategy(null, builder.build()); } / / obtain conditionValue data from the server, and splicing conditionName/conditionValue parameter String conditionName; String conditionValue; if (etag ! = null) { conditionName = "If-None-Match"; conditionValue = etag; } else if (lastModified ! = null) { conditionName = "If-Modified-Since"; conditionValue = lastModifiedString; } else if (servedDate ! = null) { conditionName = "If-Modified-Since"; conditionValue = servedDateString; } else {// If the server does not return a valid argument, there is no condition to determine, directly make a new request // return networkRequest! = null/cacheResponse == null return new CacheStrategy(request, null); // No condition! Make a regular request.} // networkRequest is returned if the server contains valid parameters, concatenate parameters, and initiate a request with a cache. = null/cacheResponse ! = null Headers.Builder conditionalRequestHeaders = request.headers().newBuilder(); Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue); Request conditionalRequest = request.newBuilder() .headers(conditionalRequestHeaders.build()) .build(); return new CacheStrategy(conditionalRequest, cacheResponse); }Copy the code
Cache Cache class
Only GET requests can be cached
@nullable CacheRequest put(Response Response) {String requestMethod = Response.request ().method(); // Verify the request method. Clean up POST/PUT/DELETE etc method if (HttpMethod. InvalidatesCache (response. The request () method ())) {try { remove(response.request()); } catch (IOException ignored) { // The cache cannot be written. } return null; } // Do not cache non-GET requests; It is technically possible to cache HEAD and some POST requests, but the complexity of doing so is high and the payoff is low // return NULL; Caching if (! requestMethod.equals("GET")) { // Don't cache non-GET responses. We're technically allowed to cache // HEAD requests and some POST requests, but the complexity of doing // so is high and the benefit is low. return null; }}Copy the code
ConnectInterceptor
The Connection interceptor is responsible for creating a Connection; The primary goal is to find reusable connections in the connection pool, or to create new connections
ConnectInterceptor#intercept
@Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Request request = realChain.request(); StreamAllocation streamAllocation = realChain.streamAllocation(); // We need the network to satisfy this request. Possibly for validating a conditional GET. boolean doExtensiveHealthChecks = ! request.method().equals("GET"); . / / core logic is encapsulated in StreamAllocation newStream method HttpCodec HttpCodec = StreamAllocation. NewStream (client, chain, doExtensiveHealthChecks); // After the newStream method is assigned; Returns the StreamAllocation member variable connection to ConnectInterceptor RealConnection connection = StreamAllocation. Connection (); return realChain.proceed(request, streamAllocation, httpCodec, connection); }Copy the code
StreamAllocation#newStream
Public HttpCodec newStream(OkHttpClient client, Interceptor.Chain Chain, Boolean doExtensiveHealthChecks) { Preserve core logic */ // Implement try {RealConnection resultConnection = in findHealthyConnection findHealthyConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks); HttpCodec resultCodec = resultConnection.newCodec(client, chain, this); synchronized (connectionPool) { codec = resultCodec; return resultCodec; } } catch (IOException e) { throw new RouteException(e); } } private RealConnection findHealthyConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Boolean doExtensiveHealthChecks) throws IOException {while (true) {// Implement RealConnection in findConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled); // If this is a brand new connection, we can skip the extensive health checks. synchronized (connectionPool) { if (candidate.successCount == 0) { return candidate; } } // Do a (potentially slow) check to confirm that the pooled connection is still good. If it // isn't, take it out of the pool and start again. if (! candidate.isHealthy(doExtensiveHealthChecks)) { noNewStreams(); continue; } return candidate; }}Copy the code
StreamAllocation#findConnection
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, Boolean connectionRetryEnabled) throws IOException {/** omit some code, Synchronized (connectionPool) {if (result == null) {// Attempt to obtain a connection from the pool; Get Internal. Instance. get(ConnectionPool, address, this, null); if (connection ! = null) { foundPooledConnection = true; result = connection; } else { selectedRoute = route; } } } if (result ! = null) {// If you can get a connection from the pool, return result; } synchronized (connectionPool) { if (! foundPooledConnection) { if (selectedRoute == null) { selectedRoute = routeSelection.next(); } route = selectedRoute; refusedStreamCount = 0; // if there are no reusable connections in the connectionPool, create a new connection result = new RealConnection(connectionPool, selectedRoute); acquire(result, false); Synchronized (connectionPool) {// save new connections to the connectionPool; Connectionpool.put internal.instance. Put (ConnectionPool, result); } return result; }Copy the code
ConnectionPool#get/put
@Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) { assert (Thread.holdsLock(this)); for (RealConnection connection : If (connection.iseligible (address, route)) {streamAllocation. Acquire (Connection, true); return connection; } } return null; }Copy the code
void put(RealConnection connection) { assert (Thread.holdsLock(this)); if (! CleanupRunning) {cleanupRunning = true; executor.execute(cleanupRunnable); } // Add the connection to the pool connections.add(connection); }Copy the code
RealConnection#isEligible
Check whether the connection meets the multiplexing conditions
Public Boolean isEligible(Address Address, @nullable Route Route) {// Uneligible (@nullable Route Route) {public Boolean isEligible(Address Address, @nullable Route Route) { Are not allowed to reuse the if (allocations. The size () > = allocationLimit | | noNewStreams) return false. // If (!) is not allowed to reuse if (! Internal.instance.equalsNonHost(this.route.address(), address)) return false; If (address.url().host().equals(this.route().address().url().host()))) {return true; // This connection is a perfect match.} If (http2Connection == null) return false; If (route == null) return false; if (route.proxy().type() ! = Proxy.Type.DIRECT) return false; if (this.route.proxy().type() ! = Proxy.Type.DIRECT) return false; if (! this.route.socketAddress().equals(route.socketAddress())) return false; If (route.address().hostNameverifier ()! = OkHostnameVerifier.INSTANCE) return false; if (! supportsUrl(address.url())) return false; / / 4. Determine the SSL authentication information try {address. CertificatePinner (). The check (address. The url (). The host (), handshake () peerCertificates ()); } catch (SSLPeerUnverifiedException e) { return false; } // Return true if a connection is not closed or occupied; // The caller's address can be carried by this connection. }Copy the code
CallServerInterceptor
Request server interceptor The interceptor’s main goal is to complete the encapsulation and parsing of HTTP protocol packets using HttpCodec to send a request to the server and parse a Response
CallServerInterceptor#intercept
@override public Response Intercept (Chain Chain) throws IOException {/** Keep core logic */ realchain-.eventListener ().requesTheadersstart (realchain-.call ()); / / write request header information, start stitching request information httpCodec. WriteRequestHeaders (request); realChain.eventListener().requestHeadersEnd(realChain.call(), request); Response.Builder responseBuilder = null; if (HttpMethod.permitsRequestBody(request.method()) && request.body() ! < span style = "box-sizing: border-box! Important; word-wrap: break-word! Important;" // If the request header contains "Expect: 100-continue", it indicates that the server needs to confirm whether to accept the request body; // If the server returns 100, the server agrees to accept the request body, and the client continues to send the request body. If ("100-continue".equalsignorecase (request.header("Expect"))) {// The request header contains "Expect: 100-continue" httpcodec.flushRequest (); realChain.eventListener().responseHeadersStart(realChain.call()); responseBuilder = httpCodec.readResponseHeaders(true); } if (responseBuilder == null) {// If ("Expect: 100-continue") request.body().writeto (bufferedRequestBody); bufferedRequestBody.close(); realChain.eventListener() .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount); } else if (! connection.isMultiplexed()) { streamAllocation.noNewStreams(); } // "Expect: 100-continue" httpCodec.finishRequest(); // responseBuilder is null; Indicates that this request does not Expect: 100-continue" if (responseBuilder == null) { realChain.eventListener().responseHeadersStart(realChain.call()); responseBuilder = httpCodec.readResponseHeaders(false); Response Response = responseBuilder.request (request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); int code = response.code(); If (code == 100) {// If "Expect: 100, 100 - the continue "request to the server returns the server to accept the request, the request of the client sends request body truly responseBuilder. = httpCodec readResponseHeaders (false); response = responseBuilder .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); code = response.code(); } return response; }Copy the code