Okhttp features

Okhttp is an efficient, faster, and more traffic-saving HTTP library. The following features are available.

  1. SPDY and HTTP2 are supported, and all requests to the same server share the same socket.
  2. Automatic maintenance of the socket connection pool to reduce the number of handshakes.
  3. The socket automatically selects the best route and supports automatic reconnection.
  4. Have a queue thread pool, easy to write concurrency.
  5. Having Interceptors makes it easy to handle requests and responses (such as transparent GZIP compression, LOGGING).
  6. Seamless support for GZIP to reduce data traffic.
  7. Support for a Headers based caching strategy to cache response data to reduce repeated network requests.
  8. Supports server multiple IP reconnection, supports automatic recovery from common connection problems, also handles proxy server problems and SSL handshake failures.
Note: what is SPDY? SPDY (pronounced "SPeeDY") is a TCP-based transport layer protocol developed by Google to minimize latency, speed up the network, and optimize the user experience. SPDY is not intended as an alternative to HTTP, but rather an enhancement of the HTTP protocol. Features of the new protocol include data stream multiplexing, request prioritization, and HTTP header compression. Google said pages loaded 64 percent faster in lab tests after the introduction of SPDY.Copy the code

OkHttpClient analyzes the version

Okhttp3 (3.2.0 version)

A simple call to OkHttpClient

A Get request

public void doGet(String url){
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder().url(url).build();
    Response response = client.newCall(request).execute();
    Log.i("Output:" + response.body().string());
}

Copy the code

A Post request

public void doPost(String url){
    MediaType json = MediaType.parse("application/json; charset=utf-8")
    RequestBody body = RequestBody.create(JSON, json);
    
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder().url(url).post(body).build();
    Response response = client.newCall(request).execute();
    Log.i("Output:" + response.body().string());
}

Copy the code

OkHttpClient introduction

OkHttpClient is, as the name implies, an HTTP request client that implements the Call.factory interface and provides the newCall method to create a request that invokes Call.

@Override public Call newCall(Request request) {
    return new RealCall(this, request);
}
Copy the code

OkHttpClient contains many modules, including the Dispatcher (request Dispatcher), ProxySelector (proxy server selection), InternalCache (InternalCache) and other modules. It provides external access to these modules. It is a typical appearance mode. At the same time, OkHttpClient is designed for Builder mode, providing Builder for easy configuration of different modules.

Request, Response, Call, RealCall, AsynCall introduction

  • Request encapsulates Request information, including Request URL, Request method, headers, RequestBody, and tag.
  • Response encapsulates corresponding information and contains corresponding request information, such as Request, HTTP protocol, Response code, headers, message, and ResponseBody.
  • As a request interface, Call provides the definition of methods such as obtaining request information, executing request execute, asynchronously entering enqueue, and canceling request cancel.
  • RealCall is a concrete implementation of Call request, which implements synchronous request execution, asynchronous request joining, request cancellation and other operations. At the same time provide in-house ApplicationInterceptorChain responsible for request and response to intercept processing.
  • AsynCall is a Runnable asynchronous request task that the Dispatcher manages and gives to the thread pool to execute its Execute method if the number of requests available is sufficient.

Dispatcher requests to invoke the Dispatcher

Dispatcher is a Call Dispatcher that manages the waiting, execution, and cancellation of calls. It is divided into synchronous request RealCall and asynchronous request AsyncCall management.

  • For synchronization requests, use the runningSyncCalls queue to record ongoing synchronization requests.
  • For asynchronous requests, the asynchronous request is logged with readyAsyncCalls (asynchronous request queue to execute) and runningAsyncCalls (asynchronous request queue to execute).
  • ExecutorService () provides an internal thread pool that performs asynchronous requests.
public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher".false));
    }
    return executorService;
}
Copy the code

For this thread pool, we look at parameter resolution:

1. Parameter 0 represents the number of core threads, that is, the minimum number of threads in the thread pool (when all threads are idle). If it is 0, it indicates that no threads are reserved when threads are idle, so as to achieve low thread occupancy. Integer.MAX_VALUE specifies the maximum number of threads in the pool. If integer. MAX_VALUE is Integer, an unlimited number of threads can be started and then closed. 3. Parameter 60 and parameter timeunit. SECONDS indicate that if the number of thread pools is greater than the number of core threads, idle threads will be closed after 60 SECONDS so that the total number of threads will not be greater than the number of core threads. 4. The new SynchronousQueue<Runnable>() parameter indicates that the thread waiting queue is synchronous, and that when a thread enters, another thread exits, which is appropriate for high frequency requests. 5. Parameter Util. ThreadFactory ("OkHttp Dispatcher".false), which provides a thread factory for creating new threads.Copy the code

This thread pool is designed to create an infinite number of threads when needed, keep no threads when not needed, and recycle idle threads if they remain idle after 60 seconds to ensure high blocking and low occupancy.

  • MaxRequests and maxRequestsPerHost are set to limit the total number of simultaneous requests and the number of requests for a single server. If these limits are exceeded, Add AsyncCall to readyAsyncCalls (asynchronous request queue to be executed) to wait for execution. These two parameters can be configured.
  • Enqueue method, AsyncCall queue operation
synchronized void enqueue(AsyncCall call) {
    if(runningAsynccalls.size () < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {// Limit not exceeded, Execute join to the thread pool execute asynchronous request runningAsynccalls.add (call); executorService().execute(call); }elseReadyasynccalls.add (call); }}Copy the code
  • The FINISHED method, which indicates that the current AsyncCall is complete and that the request is considered to be pulled from the asynchronous wait queue for execution.
Synchronized void finished(AsyncCall call) {synchronized void finished(AsyncCall call) {if(! runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!"); promoteCalls(); } // Fetch the request from the asynchronous wait queue to execute, while logging to the asynchronous queue in progress private voidpromoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();
    
      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }
    
      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
}
Copy the code
  • CancelAll cancels all requests, including all synchronous requests, all asynchronous requests in progress, and all asynchronous requests waiting to be executed.
public synchronized void cancelAll() {
    for (AsyncCall call : readyAsyncCalls) {
      call.cancel();
    }
    
    for (AsyncCall call : runningAsyncCalls) {
      call.cancel();
    }
    
    for(RealCall call : runningSyncCalls) { call.cancel(); }}Copy the code

Where RealCall actually performs the request invocation

RealCall is real implementation of the requested entry, both synchronous request the execute, or asynchronous requests the enqueue (distribution) by the Dispatcher, eventually to RealCall getResponseWithInterceptorChain.

SequenceDiagram OkHttpClient->>RealCall: newCall(Request) Create a Request to call RealCall->>RealCall: Execute Synchronous RealCall->>RealCall: GetResponseWithInterceptorChain RealCall - > > RealCall: the enqueue asynchronous execution RealCall - > > Dispatcher: enqueue(AsyncCall) Dispatcher->>AsyncCall: execute AsyncCall->>RealCall: getResponseWithInterceptorChainCopy the code

In getResponseWithInterceptorChain created a ApplicationInterceptorChain interceptor in the chain to deal with the current RealCall Request Request data. Here’s how interceptors work in Okhttp.

Interceptors Principle of Interceptors

Interceptors use the chain of responsibility pattern to process request response information layer by layer. Httploggingtor log print interceptor is added to the httploggingtor log print interceptor.

Final class RealCall implements the Call {/ / execution request, here created ApplicationInterceptorChain interceptor chain, is responsible for all the interceptor invocation, Calls proceed start processing intercept private Response getResponseWithInterceptorChain (BooleanforWebSocket) throws IOException {
    Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
    return chain.proceed(originalRequest);
  }

  class ApplicationInterceptorChain implements Interceptor.Chain {
    private final int index;
    private final Request request;
    private final boolean forWebSocket;

    ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) {
      this.index = index;
      this.request = request;
      this.forWebSocket = forWebSocket;
    }

    @Override public Connection connection() {
      return null;
    }

    @Override public Request request() {
      returnrequest; } // Here we traverse the interceptor, find the corresponding interceptor in the interceptor chain through index, and then call intercept to intercept processing. @override public Response Proceed (Request Request) throws IOException {// If there's another interceptor in the chain, call that. if (index < client.interceptors().size()) { Interceptor.Chain chain = new ApplicationInterceptorChain(index +  1, request, forWebSocket); Interceptor interceptor = client.interceptors().get(index); Response interceptedResponse = interceptor.intercept(chain); if (interceptedResponse == null) { throw new NullPointerException("application interceptor " + interceptor + " returned null"); } return interceptedResponse; } // After all the interceptors have traversed the processing, the actual request is executed and the Response is returned. This is where the actual Response is generated. // No more interceptors. Do HTTP. return getResponse(request, forWebSocket); }}}Copy the code

This is a recursive process. After all the interceptors have processed the Request information (i.e. the pre-processing before the actual Request, such as logging, modifying the Request header, etc.), The network request engine is really handed over to execute the request, return the Response information, and then intercept the Response information in reverse order (that is, the reprocessing of the Response information, such as printing the Response information). Httploggingtor log print interceptor will make this process clearer.

Public final class HttpLoggingInterceptor implements Interceptor {// @override public Response Intercept (Chain chain) throws IOException { Level level = this.level; Request Request = chain.request(); // If no log is printed, chain.proceed is called to perform the next interception and nothing else is done.if (level == Level.NONE) {
      returnchain.proceed(request); } / / this part is to print request information, request for pretreatment (here you can modify the request information or do other operations) -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- a BooleanlogBody = level == Level.BODY;
    boolean logHeaders = logBody || level == Level.HEADERS; RequestBody requestBody = request.body(); boolean hasRequestBody = requestBody ! = null; Connection connection = chain.connection(); Protocol protocol = connection ! = null ? connection.protocol() : Protocol.HTTP_1_1; String requestStartMessage ="--> " + request.method() + ' ' + request.url() + ' ' + protocol;
    if (!logHeaders && hasRequestBody) {
      requestStartMessage += "(" + requestBody.contentLength() + "-byte body)";
    }
    logger.log(requestStartMessage);

    if (logHeaders) {
      if(hasRequestBody) { // Request body headers are only present when installed as a network interceptor. Force // them to be  included (when available) so there values are known.if(requestBody.contentType() ! = null) { logger.log("Content-Type: " + requestBody.contentType());
        }
        if(requestBody.contentLength() ! = -1) { logger.log("Content-Length: " + requestBody.contentLength());
        }
      }

      Headers headers = request.headers();
      for (int i = 0, count = headers.size(); i < count; i++) {
        String name = headers.name(i);
        // Skip headers from the request body as they are explicitly logged above.
        if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
          logger.log(name + ":"+ headers.value(i)); }}if (!logBody || ! hasRequestBody) { logger.log("--> END " + request.method());
      } else if (bodyEncoded(request.headers())) {
        logger.log("--> END " + request.method() + " (encoded body omitted)");
      } else {
        Buffer buffer = new Buffer();
        requestBody.writeTo(buffer);

        Charset charset = UTF8;
        MediaType contentType = requestBody.contentType();
        if(contentType ! = null) { charset = contentType.charset(UTF8); } logger.log("");
        logger.log(buffer.readString(charset));

        logger.log("--> END " + request.method()
            + "(" + requestBody.contentLength() + "-byte body)"); }} / / request pretreatment complete -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / here call chain. Proceed to perform the next interceptor operations, return the Response Response information, ApplicationInterceptorChain proceed method, it's easy to see is a chain of responsibility pattern of recursive call long startNs = System. NanoTime (); Response response = chain.proceed(request); long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs); // Then print the response information, Response to the Response reprocessing (here the Response information can be modified or do other operations) -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ResponseBody ResponseBody = response.body(); long contentLength = responseBody.contentLength(); String bodySize = contentLength ! = 1? contentLength +"-byte" : "unknown-length";
    logger.log("< -" + response.code() + ' ' + response.message() + ' '
        + response.request().url() + "(" + tookMs + "ms" + (!logHeaders ? ","
        + bodySize + " body" : "") + ') ');

    if (logHeaders) {
      Headers headers = response.headers();
      for (int i = 0, count = headers.size(); i < count; i++) {
        logger.log(headers.name(i) + ":" + headers.value(i));
      }

      if (!logBody || ! HttpEngine.hasBody(response)) { logger.log("<-- END HTTP");
      } else if (bodyEncoded(response.headers())) {
        logger.log("<-- END HTTP (encoded body omitted)");
      } else {
        BufferedSource source = responseBody.source();
        source.request(Long.MAX_VALUE); // Buffer the entire body.
        Buffer buffer = source.buffer();

        Charset charset = UTF8;
        MediaType contentType = responseBody.contentType();
        if(contentType ! = null) { try { charset = contentType.charset(UTF8); } catch (UnsupportedCharsetException e) { logger.log("");
            logger.log("Couldn't decode the response body; charset is likely malformed.");
            logger.log("<-- END HTTP");

            returnresponse; }}if(contentLength ! = 0) { logger.log("");
          logger.log(buffer.clone().readString(charset));
        }

        logger.log("<-- END HTTP (" + buffer.size() + "-byte body)"); }} / / response rework done -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / returns the response information herereturnresponse; }}Copy the code

Now that we know how the interceptor works, we know that the actual request is initiated in the getResponse method of RealCall.

/**
* Performs the request and returns the response. May return null if this call was canceled.
*/
Response getResponse(Request request, boolean forThrows IOException {// This is responsible for executing the request and returning the response data... }Copy the code

The specific request will be analyzed in the next section.

You can follow my GitHub wechat official account hesong by scanning the qr code below: