Hello everyone, I am the Cang King.
The following is my series of related articles, you can refer to, you can give a like or follow my article.
[Android] How to make a crash rate of less than three per thousand of the app- chapter list [Android] you do not know Android process – process information
OKhttp3 interceptor scheduler source code.
Let’s take a look at the interceptor flowchart
Here is a simple example of an OKHttp3 GET request.
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d("OkHttp"."Call Failed:" + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d("OkHttp"."Call succeeded:"+ response.message()); }});Copy the code
(1) Creating OkHttpClient will initialize a lot of things, Dispenser, protocol, proxy, interceptor, cookie, cache, socket factory, etc. (2) Request uses the builder pattern to set some Request parameters, including the Request form GET/PUT, Request address, etc (3) OkHttpClient.newCall calls realCall. newRealCall
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false/ *for web socket */);
}
Copy the code
Dipatcher calls enQueue in OKHttpClient
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
Copy the code
(5) The Dispatcher will call Excute AsyncCall
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
(6) AsyncCall inheritance in Runnable, its main call in excute function can see the main is to use getResponseWithInterceptorChain () call intercept chain
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else{ eventListener.callFailed(RealCall.this, e); responseCallback.onFailure(RealCall.this, e); } } finally { client.dispatcher().finished(this); }}Copy the code
(7) The chain contains several different interceptors. Construct a ReaInterceptorChain and call the PROCEED chain.
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors()); interceptors.add(retryAndFollowUpInterceptor); Add (new BridgeInterceptor(client.cookiejar ())); Add (new CacheInterceptor(client.internalCache())); // Interceptors. add(new ConnectInterceptor(client)); // Connect interceptorif (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket)); Chain Chain = new RealInterceptorChain(Interceptors, null, NULL, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis());return chain.proceed(originalRequest);
}
Copy the code
(8) The next chain object is called by interceptor. Intercept
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
....
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout); Interceptor interceptor = interceptors.get(index); Response response = interceptor.intercept(next); .return response;
}
Copy the code
(9) here RetryAndFollowUpInterceptor interceptors, important function is to retry after network request failed; When the server returns that the current request needs to be redirected, it directly initiates a new request and reuses the current connection if conditions permit.
@Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); RealInterceptorChain realChain = (RealInterceptorChain) chain; Call call = realChain.call(); EventListener eventListener = realChain.eventListener(); StreamAllocation = new streamAllocation (client.connectionPool(), createAddress(request.url()), call, streamAllocation = new streamAllocation (client.connectionPool(), createAddress(request.url()), call, eventListener, callStackTrace); int followUpCount = 0; Response priorResponse = null;while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true; try { response = realChain.proceed(request, streamAllocation, null, null); // Get the response of the next interceptor releaseConnection =false; } catch (RouteException e) {The attempt to connect via a route failed. The request will not have been sent. Will not send the requestif(! recover(e.getLastConnectException(),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, requestSendStarted, request)) throw e; releaseConnection =false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources. if (releaseConnection) { streamAllocation.streamFailed(null); streamAllocation.release(); } } // 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(); } Request followUp = followUpRequest(response); // Redirect and retry requests if (followUp == null) {if (! forWebSocket) { streamAllocation.release(); } return response; } closeQuietly(response.body()); if (++followUpCount > MAX_FOLLOW_UPS) { streamAllocation.release(); throw new ProtocolException("Too many follow-up requests: " + followUpCount); } if (followUp.body() instanceof UnrepeatableRequestBody) { streamAllocation.release(); throw new HttpRetryException("Cannot retry streamed HTTP body", response.code()); } if (! sameConnection(response, followUp.url())) { streamAllocation.release(); streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(followUp.url()), call, eventListener, callStackTrace); } else if (streamAllocation.codec() ! = null) { throw new IllegalStateException("Closing the body of " + response + " didn't close its backing stream. Bad interceptor?"); } request = followUp; priorResponse = response; }}Copy the code
Response = realchain. proceed(Request, streamAllocation, NULL, null); Content encoding Sets gZIP compression and decompresses the content upon receipt. Save the application layer processing data decompression trouble add cookie set other headers, such as user-agent,Host, keep-alive and so on. Keep-alive is a necessary step to realize multiplexing
@Override public Response intercept(Chain chain) throws IOException { Request userRequest = chain.request(); Request.Builder requestBuilder = userRequest.newBuilder(); RequestBody body = userRequest.body(); // Build the Header contentif(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");
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing // the transfer stream. boolean transparentGzip = false; if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) { transparentGzip = true; requestBuilder.header("Accept-Encoding", "gzip"); } List
cookies = cookiejar.loadForRequest (userRequest.url()); if (! cookies.isEmpty()) { requestBuilder.header("Cookie", cookieHeader(cookies)); } if (userRequest.header("User-Agent") == null) { requestBuilder.header("User-Agent", Version.userAgent()); } Response networkResponse = chain.proceed(requestBuilder.build()); / / to the next interceptor 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
(11) The CacheInterceptor is responsible for Cache management. When a network request has a valid Cache, it returns the Cache. When the server returns a new Cache, it updates the current Cache.
@Override public Response intercept(Chain chain) throws IOException { Response cacheCandidate = cache ! = null ? cache.get(chain.request()) : null; long now = System.currentTimeMillis(); CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); Request networkRequest = strategy.networkRequest; Response cacheResponse = strategy.cacheResponse;if(cache ! = null) { cache.trackResponse(strategy); }if(cacheCandidate ! = null && cacheResponse == null) { closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. } // If we're forbidden from using the network and the cache is insufficient, fail.
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();
}
// If we don't need the network, we're done.
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()); } } // If we have a cache response too,then we// Return the cached content if (cacheResponse! = null) { 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 ()). / / if the Header content changes, just update the cache cache. TrackConditionalCacheHit (); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } } 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
(12) ConnectInterceptor Configures a RealConnection
@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");
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks); RealConnection connection = streamAllocation.connection(); // Find the appropriate connection, return to the connection pool to take care of the connectionreturn realChain.proceed(request, streamAllocation, httpCodec, connection);
}
Copy the code
(13) The CallServerInterceptor calls the connection server request
@Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; HttpCodec httpCodec = realChain.httpStream(); StreamAllocation streamAllocation = realChain.streamAllocation(); RealConnection connection = (RealConnection) realChain.connection(); Request request = realChain.request(); long sentRequestMillis = System.currentTimeMillis(); realChain.eventListener().requestHeadersStart(realChain.call()); / request/response head start callback httpCodec. WriteRequestHeaders (request); Realchain-eventlistener ().requesTheaderSend (realchain-.call (), request); ResponseBuilder responseBuilder = null;if(HttpMethod.permitsRequestBody(request.method()) && request.body() ! = 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 't get that, return
// what we did get (such as a 4xx response) without ever transmitting the request body.
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue"expectation was met. realChain.eventListener().requestBodyStart(realChain.call()); long contentLength = request.body().contentLength(); CountingSink requestBodyOut = new CountingSink(httpCodec.createRequestBody(request, contentLength)); BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); Request.body ().writeto (bufferedRequestBody); / / request data flow bufferedRequestBody. Close (); realChain.eventListener() .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount); // Call request end}else if(! 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.
streamAllocation.noNewStreams();
}
}
httpCodec.finishRequest();
if(responseBuilder == null) { realChain.eventListener().responseHeadersStart(realChain.call()); / / read response headers responseBuilder. = httpCodec readResponseHeaders (false); } the Response the Response = responseBuilder / / read the Response message. The request (request) handshake (streamAllocation. Connection (). Handshake ()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); realChain.eventListener() .responseHeadersEnd(realChain.call(), response); Int code = response.code();if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
Copy the code
(14) Add interceptors. There are two types of custom interceptors in OKHttp3: application interceptors and network interceptors. Both interceptors need to inherit from Interceptor.
Application Interceptors Apply Interceptors
* There is no need to worry about intermediate process responses such as redirects and retries. * Always call only once, even if the HTTP response is fetched from the cache. * Observe the original intention of the application. * Allows short-circuiting without calling chain.proceed (), that is, aborting the call. * Allows retries so that the chain.proceed () call is added multiple times via the addInterceptor function
Network Interceptors Network Interceptors
* The ability to perform operations on intermediate responses such as redirection and retry. * The cached response results are not called when a network short circuit occurs. * Monitor data as it is transferred over the network. * Access the Connection that hosts the request. Add it by addNetworkInterceptor function
Take a look at interceptor sorting in RealCall
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors()); / / the application of custom interceptors, it is the beginning so could intercept requests and added some initialization intercept interceptors. Add (retryAndFollowUpInterceptor); interceptors.add(new BridgeInterceptor(client.cookieJar())); interceptors.add(new CacheInterceptor(client.internalCache())); interceptors.add(new ConnectInterceptor(client));if (!forWebSocket) { interceptors.addAll(client.networkInterceptors()); // Custom network interceptor, called before the actual request, can intercept and change all the default parameters. } interceptors.add(new CallServerInterceptor(forWebSocket)); Chain Chain = new RealInterceptorChain(Interceptors, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis());return chain.proceed(originalRequest);
}
Copy the code
So much for analyzing the interceptor source code.