Why OkHttp

  • Http2.0 is supported by default to support all the benefits of 2.0
  • Connection pooling reduces request latency (if HTTP/2 is not available)
  • Perform transparent GZIP to reduce the download size
  • The default implementation of front-end caching, response caching completely avoid repeated network requests
  • Good functional encapsulation and overall structure decoupling

Source code analysis purposes

Network knowledge is very important for a programmer. We can learn network knowledge through books, but the best way to learn is to read source code. Peacetime development, although the bottom layer is the network layer TCP/UDP, but these are good components of the kernel, can not be freely customized, we can contact the operation is the application layer protocol, that is, the most common HTTP/FTP and so on. You are free to implement your own HTTP framework. Develop your own Http library. As android developers, the familiar network request framework is OKHttp, he has been integrated into the Android system source, instead of HttpClient, visible its authority. So android developers can learn about the web by reading OKHttp’s source code, which is a shortcut to standing on the shoulders of giants. Not only will we learn about networking, we can also learn about the design architecture of OKHttp. This series of articles will first briefly introduce each component from the top level of use, have a general understanding of the entire network request process, know the meaning and use of each component. The following sections describe each component in more detail. From the shallow to the deep. After reading this series of articles, I’m sure you have a good understanding of OKHttp’s internals. Let’s start with the use outside of the framework. Source code analysis based on com. Squareup. Okhttp3: okhttp: 3.12.6 version.

Using OkHttp

As a framework, simplicity is very simple to use. It just takes a few simple steps. Given the URL to request, you can easily retrieve the data from the web request.

OkHttpClient okHttpClient = new OkHttpClient.Builder().build(); Request Request = new request.builder ().url(" Request address ").build(); Call call = okHttpClient.newCall(request); // Synchronize request try {Response Response = call.execute(); } catch (IOException e) { e.printStackTrace(); } // call. Enqueue (new Callback() {@override public void onFailure(call, Callback); IOException e) {Override public void onResponse(Call Call, Throws IOException {// Asynchronous request callback successfully}});Copy the code

From the simple Request code above, you can see that there are three objects used: OkHttpClient, Request, and Call. Finally, the Call synchronous execute or asynchronous enQueue method is used to request, and the Response is received, and the request ends. Let’s take a look at the functions and uses of each module one by one. Future chapters will cover each module in detail.

OkHttp top-level module introduction

1. OkHttpClient

1.1 responsibilities

OkHttpClient is responsible for two main functions:

  1. Responsible for creating Call as a factory for Call. Call is an interface. The implementation class is newCall. Call is responsible for sending and receiving requests, as explained below.
  2. Accepts some externally configurable parameters, equivalent to a configuration class.

The framework recommends that we share an OkHttpClient object because we can reuse the ConnectionPool ConnectionPool and thread pool to improve performance. If an OkHttpClient is created for each connection, these resources are wasted. We can create an OkHttpClient directly using the constructor, or we can create an OkHttpClient using the Builder pattern. Of course, using the constructor also uses a Builder with default parameters inside.

Properties within OkHttpClient are package private and final, it is a good practice to do everything possible to reduce visibility and shape-ability.

1.2 Implementation Details

OkHttpClient accepts some external configurable parameters, mainly to receive parameters, let’s look at the details.

  1. Protocols ALPN: an application-layer protocol for negotiation. The most common is HTTP, and there are many versions of HTTP, from the original 1.0 through 1.1 to the latest 2.0. And SPDY, QUIC and other protocols. Are created to solve different problems. OkHttp sets up the supported application layer protocols via the Protocols variable, which is a collection of types of Protocol. The default assignment is HTTP1.1 and HTTP2.0 by default, which can also be customized using Protocols () of Builder. SPDY and QUIC are not currently supported.

    static final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(
    Protocol.HTTP_2, Protocol.HTTP_1_1);
    Copy the code
  2. ConnectionSpecs Configuration specifications for the connection, including whether SSL is used, TSL, and some version and password configuration. Two configurations are supported by default: HTTP plaintext and TSL. The TSL specification configuration is that the default password suite used is APPROVED_CIPHER_SUITES and the security protocol versions are TSL1.3 and 1.2. TSL extensions are also supported. The TSL is suitable for most client platforms.

  3. A thread pool is used internally to determine when asynchronous requests are initiated. The default maximum number of simultaneous requests is 64 and the maximum number of simultaneous requests per host address is 5. Synchronous requests are also stored internally in runningSyncCalls. The system includes an implementation by default, but we can also pass in our own configuration via dispatcher(Dispatcher Dispatcher), including setting an idle callback and a limit on the number of requests.

  4. The proxy used by proxy and proxySelector, which has a higher priority than proxySelector. If no proxy is set, a proxy server is selected by calling Select () of proxySelector. The Proxy server is a Proxy class inside OKHttp, which stores the type and address of the Proxy server. Types include direct request (no proxy), HTTP, and SOCKS.

  5. Interceptors and networkInterceptors are custom interceptors. OKHttp network requests are implemented one by one. Externally we are also allowed to create our own interceptors and participate in the request process. Interceptors can intercept the entire request process, including retry and caching, while networkInterceptors can intercept only the process of real network calls.

  6. The listener for eventListenerFactory and eventListener events, OKHttp when it’s running, will emit some events, and we can implement our own factory and return our own implementation to listen. There are many internal callbacks, including request start end, DNS start end, etc.

  7. The cookieJar provides support for cookies, including retrieving and storing cookies. The default implementation does nothing.

  8. Cache and internalCache front-end cache, the framework of the specific implementation is cache, the default implementation of Http cache strategy, internal memory management using LRU strategy. The two values are mutually exclusive, and setting one sets the other to NULL. We can implement internalCache, or we can create a Cache and pass it into the Builder. The system has no Cache by default. If a cache is required, you can configure the cache(@nullable cache cache) or setInternalCache(@nullable InternalCache InternalCache).

  9. SocketFactory creates a factory for sockets and sslsockets. The DefaultSocketFactory is DefaultSocketFactory, which generates java.net.Socket. These are all network libraries provided in Java. SslSocketFactory defaults to null. SslSocketFactory is set primarily to configure Https requests. If we set our ConnectionSpec to support TSL, there will be a default implementation. We’ll talk about this later when WE talk about Https.

  10. CertificateChainCleaner is related to the sslSocketFactory above, If we set the sslSocketFactory will pass Platform. The get () buildCertificateChainCleaner (sslSocketFactory); Get. Mainly used to deal with certificate chains.

  11. HostnameVerifier Domain name authentication, the default is OkHostnameVerifier. Use verify to verify the validity of the server hostname.

  12. CertificatePinner fixed certificates. We can configure some fixed certificates, such as the certificate for capturing packets. The obtained certificate must meet the requirements of the fixed certificate.

  13. ProxyAuthenticator and authenticator authentication components for the server to return 407 for re-authentication. Including common authentication and proxy authentication.

  14. ConnectionPool Connection multiplexing pool improves efficiency and saves memory. More on this control later.

  15. DNS Configure the DNS and obtain an IP address. The default is dns. SYSTEM, which internally calls Java’s inetaddress. getAllByName to obtain the IP address.

  16. timeout

There are four types of timeout. int callTimeout; Int connectTimeout; Int readTimeout; // Write Socket time int writeTimeout; // Read timeCopy the code
  1. retryOnConnectionFailure

    Reconnect the configuration and retry the failed request. The default is true
  2. followRedirects

    Redirection configuration, whether redirection is allowed when redirection occurs.

These are the more important build parameters in the entire network request process. We just need to know that this thing exists, that it works in general. Each control will be explained in more detail later.

2.Request

Request basically encapsulates our network requests. The Builder pattern is used, with a few familiar fields inside.

final HttpUrl url; final String method; final Headers headers; final @Nullable RequestBody body; final Map<Class<? >, Object> tags;Copy the code
  1. The url represents the unified resource locator for the request. The type is HttpUrl. We can create it using HttpUrl’s Builder.
    https://www.google.com/search?q=polar%20bears
    HttpUrl url = new HttpUrl.Builder()
       .scheme("https")
       .host("www.google.com")
       .addPathSegment("search")
       .addQueryParameter("q", "polar bears")
       .build();
    Copy the code

    This way we can easily create a Url.

  2. Method indicates the request type. The default is GET request. We can use POST, DELETE, and other methods
  3. Headers represents the head of the request, which is the key: value pair. An array of strings is stored in the Headers class
    String[] namesAndValues;
    Copy the code

    It stores string pairs, keys and values. We can also create the Header using the Builder.

  4. The body represents the request body, and the following method determines whether the request body is needed. For example, when we set method to POST, we need a request body to place our parameters.
    public static boolean requiresRequestBody(String method) {
     return method.equals("POST")
         || method.equals("PUT")
         || method.equals("PATCH")
         || method.equals("PROPPATCH") // WebDAV
         || method.equals("REPORT");   // CalDAV/CardDAV (defined in WebDAV Versioning)
    }
    Copy the code
  5. Tags represent local tags that can be tagged for this request. As a marker for future processing.

Once we have created the above two objects, we can use them to create a Call object. OkHttpClient’s newCall method is used here. Call is an interface that implements a RealCall object. He relies on the above two objects. Let’s look at Call in more detail.

@Override public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */);  }Copy the code

3.Call

Call relies on the two objects we created above. OkHttpClient can be understood as the frame parameter information required by an OkHttp Request. Request is the specific details of the Request. Using the framework parameter information and request details, everything is ready to be completed using RealCall. You can see that the specific request entry is within the Call and is responsible for the specific logic, whether synchronous or asynchronous.

3.1 Construction method

private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); this.timeout = new AsyncTimeout() { @Override protected void timedOut() { cancel(); }}; this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS); }Copy the code

An interceptor, a retry and redirection interceptor, is also created to handle retries and redirects. And handles timeout-related logic. AsyncTimeout is used to complete the timeout details. In addition, the timeout method is used to set the timeout time. Details will be analyzed later.

3.2 Synchronization Request

@Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); timeout.enter(); eventListener.callStart(this); try { client.dispatcher().executed(this); Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { e = timeoutExit(e); eventListener.callFailed(this, e); throw e; } finally { client.dispatcher().finished(this); }}Copy the code

A synchronous request invokes the Execute () method of RealCall to perform the request operation on the thread we invoked. If it’s on the main thread, that blocks the main thread, so it must be on an asynchronous thread. Let’s go over every detail.

  • Do not repeat requests: The executed variable flag will throw an exception if invoked repeatedly. That is, a Call cannot be executed twice.
  • Timeout: Call timeout.Enter () to start the determination of timeout. The later timeoutExit(e) method stops the timing to prevent a memory leak. The logic of the timeout is inside the timer.
  • Event listening: Trigger event listening, callStart to request the start event
  • Data request: A call to the Dispatcher’s executed cache and a call to finish in the finally block to clear the request, where the Dispatcher is not responsible for the request but for statistics and unified control. To obtain the Response directly call getResponseWithInterceptorChain method to go, it’s as simple as that.
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor>  interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors()); 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()); } interceptors.add(new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); Response response = chain.proceed(originalRequest); if (retryAndFollowUpInterceptor.isCanceled()) { closeQuietly(response); throw new IOException("Canceled"); } return response; }Copy the code

The above is the core method of requesting data for OKHttp. The logic is relatively clear, creating many interceptors directly and integrating them into a chain. The first is our custom Interceptors interceptor, followed by retry/redirect, bridge, Cache, connect, custom networkInterceptors interceptor, and request interceptor. This is obviously a chain of responsibility model, and it goes down the chain, alas! The data comes out… As you can see from the above method, the Interceptors interceptor intercepts the entire request process, while the networkInterceptors interceptor only handles the last request interceptor. Verify our introduction to custom interceptors. How does this chain of connectors work

3.3 Operation of interceptor chain

RealInterceptorChain is the main object for chain transmission and operation. The proceed method triggers the run link.

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { if (index >= interceptors.size()) throw new AssertionError(); . 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

The Proceed method calls the interceptor’s Intercept method directly, and each interceptor’s Intercept method implements its own functionality. The next RealInterceptorChain is created, passing the index + 1 index. Get (index) to get the specific interceptor. Step by step, plus 1 every time. This completes the execution of the interceptor chain. The architecture is very flexible: execute the next interceptor before executing its own interceptor code, and execute its own postcode after the next interceptor returns. The functionality in the individual network requests is successfully decoupled. This is how OkHttp works at its core, and the rest of the analysis is based on it.

3.4 Asynchronous Request

The asynchronous request invokes the enqueue method.

@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

As with synchronous requests, you are not allowed to execute twice. Call eventListener.callStart(this) to trigger the Start event. Requests are made primarily through the Dispatcher’s enqueue method, which is different from synchronous, which is delivered asynchronously to the Dispatcher for processing. That is, to the thread pool inside for processing. The direct executant is AsyncCall, and the direct executant is AsyncCall which is a NamedRunnable inherited class. NamedRunnable is a Runnable, and the run method directly calls the abstract Execute method. The execute method of AsyncCall is implemented as follows.

@Override protected void execute() {
  boolean signalledCallback = false;
  timeout.enter();
  try {
    Response response = getResponseWithInterceptorChain();
    signalledCallback = true;
    responseCallback.onResponse(RealCall.this, response);
  } catch (IOException e) {
    e = timeoutExit(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);
    }
  } catch (Throwable t) {
    cancel();
    if (!signalledCallback) {
      IOException canceledException = new IOException("canceled due to " + t);
      responseCallback.onFailure(RealCall.this, canceledException);
    }
    throw t;
  } finally {
    client.dispatcher().finished(this);
  }
}
Copy the code

The same logic also call getResponseWithInterceptorChain method requests for data and logic of synchronous request is consistent. When we’re done, we call responseCallback, which is the callback we passed in when we called enqueue, and it’s called back when the data request succeeds and when it fails. It can be seen that asynchronous and synchronous only differ in the dispatcher thread pool.

4. Conclusion:

Using OkHttp outside of the framework is very simple, summed up in the diagram below

StateDiagram -v2 [*] --> Create OkHttpClient create OkHttpClient --> Create Request Create Request --> Create Call through OkHttpClient+Request OkHttpClient+Request create Call --> synchronous execute Request Create Call --> asynchronous enQueue Request synchronize execute Request --> execute intercept responsibility chain Asynchronous enqueue requests --> add dipatcher thread pool --> execute block responsibility chain --> [*]

5. Think about:

  1. Why does OKHttp use the chain of responsibility pattern?

From the analysis in this section, you should have a general idea of the OKHttp request flow. Each of the following sections explains each control in detail. Each interceptor includes the core of the interceptor responsibility chain.