conclusion

  1. Connections can be reused, which can be interpreted as reusing sockets
  2. Retries or redirects are reusable connections if scheme,host, and port are the same
  3. Okhttp has two types of interceptors, the first called before all interceptors and the second called after a connection has been established (that is, after the TCP + HTTPS handshake has completed). The second type is also known as the network interceptor
    • Therefore, the network interceptor can get information about the requested HTTP protocol and so on, because it is called after the connection is established
  4. You can set the socketFactory and sslSocketFactory used for the connection and DNS in the OkHttpClient
  5. HTTP caching only supports GET requests

The interceptor

Okhttp involves multiple classes when making network requests, and the initialization of these classes is scattered among various interceptors. OkHttp interceptors are connected in a chain of responsibility pattern, called one by one, and then processed in turn to return results.

OkHttpClient.Builder.addInterceptor

This is the first of all interceptors to be called, and can do common things after the request, such as adding some headers, decrypting the data returned by the server, etc

RetryAndFollowUpInterceptor

The total number of retries and redirects cannot exceed 20

BridgeInterceptor

Add some request headers and gzip decompression to the request. For example, add headers such as Connection, Host, and User-agent

CacheInterceptor

Process the cache. This is mainly based on the HTTP protocol to store and parse GET requests

ConnectInterceptor

Create a connection and process the TCP + HTTPS handshake. After this step, the client has a link to communicate with the server and can send data to the server directly

Only after this step will the return result of chain.connection () in the interceptor intercepting method not be empty

The CallServerInterceptor interceptor starts sending data to the server and reading data from the server

OkHttpClient.Builder.addNetworkInterceptor

An interceptor added to a consumer before a network request. Since the connection has been established at this point, you can get the requested version number and so on.

CallServerInterceptor

Actually make the network request. This step mainly uses the HttpCodec class

A few class

When requesting a server, there is at least a request and connection, and a flow that writes the request to the connection/reads data from the server.

The OkHttp Request is represented by Call (subclass RealCall, which contains the Request we set up), which is represented by Connection (subclass RealConnection, which includes sockets, handshake messages, etc.), Streams are represented using HttpCodec. Why is a Codec called a stream? Since StreamAllocation#newStream() returns an HttpCodec object, the name is a stream.

A single connection can host multiple streams, which means that a single RealConnection can serve multiple StreamAllocation. StreamAllocation for its service is recorded in RealConnect # Allocations. The purpose of this is to save the handshake time when establishing a connection (TCP + HTTPS handshake).

RealConnection: connection. Contains the Socket and handshake information. It encapsulates the Socket. Owning it is equivalent to owning a link that can communicate with the server. A TCP + HTTPS handshake is performed when a connection is created

ConnectionPool: OkHTTP multiplexes connections. This class is responsible for managing connections

HttpCodec: flow. Used to write and read data to the server. It contains Socket input and output streams, so it can read and write data to the server

StreamAllocation: Coordinates the bridge between HttpCodec, Connection, and Call. Its main goal is twofold: one is to find the appropriate connection and one is to establish the corresponding flow. The former is done with newStream() and the latter is done with the associated RealConenction#newCodec(). Here’s a look at its main properties:

Class initialization

The real network request is in the CallServerInterceptor


// CallServerInterceptor.java
// These are the classes involved
RealInterceptorChain realChain = (RealInterceptorChain) chain;

HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Copy the code

The above mentioned HttpCodec and RealConnection classes are generated in the ConnectInterceptor

// ConnectInterceptor.java

@Override public Response intercept(Chain chain) throws IOException {
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  // Get StreamAllocation set
  StreamAllocation streamAllocation = realChain.streamAllocation();
  
  booleandoExtensiveHealthChecks = ! request.method().equals("GET");
  // Generate HttpCodec with StreamAllocation
  HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
  // Get the RealConnection set in StreamAllocation
  RealConnection connection = streamAllocation.connection();
  return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
Copy the code

And StreamAllocation RetryAndFollowUpInterceptor added

// RetryAndFollowUpInterceptor.java

StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
    createAddress(request.url()), call, eventListener, callStackTrace);
Copy the code

The initializations for each class have been found, and now look back to see what each class does.

ConnectionPool

Connection pool. The RealConnection object, used to manage links, is reused when conditions are met and cleared when the maximum time is exceeded, somewhat like a thread pool.

Its initialization is in the okHttpClient.Builder constructor, which looks like this:

save


void put(RealConnection connection) {
  // Start cleanup. Later said
  if(! cleanupRunning) { cleanupRunning =true;
    executor.execute(cleanupRunnable);
  }
  // Simply log the parameters passed in
  connections.add(connection);
}
Copy the code

Clean up the

Another thread pool is used to perform the cleanup while storage is being performed

The cleanup logic called above is very simple, and its return value is the amount of time that the longest idle connection has been idle, truncating the most important one as follows:

Cleanup cleans only the longest idle connections at a time, combining with while(true) in cleanupRunnable() to cleanup any out-of-set connections in the pool

Call the above pruneAndGetAllocationCount () method, which can be seen from this method RealConnection through internal attributes allocations StreamAllocation recorded all its services

take

It determines whether the connection can be used for the current request before fetching

As for whether it is reusable or not, it needs to be differentiated according to the HTTP protocol version. We know that only http2 can be multiplexed, so it can be multiplexed

The get() method is followed by a call to Streamallocation.acquire (), which is simple enough

At this point, we’re done accessing RealConnection, but keep in mind that ConnectionPool#get may return null

StreamAllocation

NewStream () associates it with a connection, and noNewStreams() closes its connection

newStream

The above analysis only completes the analysis of the ConnectionPool, which is considered to complete the creation of StreamAllocation. As the ConnectInterceptor continues, proceed to its newStream method

FindHealthyConnection calls findConnection in an infinite loop. FindConnection () is complicated, look at it in sections

Cited above, the two cases, we know that the RetryAndFollowUpInterceptor StreamAllocation. Intercept created in, therefore each time the request is a virtually impossible, unless redirected or try again.

In RetryAndFollowUpInterceptor intercept method has an infinite loop, there is such a piece of code, the code says: retry or redirect if scheme, host, port is the same, reusable connection or reuse the Socket

Go back to findConnection() and, if cases 1 and 2 already found the connection, return directly and the whole method ends. If not, go three, four

Case three is in cache, case four is new. When case three occurs, the method ends; In case four, you need to create a new connection

This completes the analysis of newStream, whose core function is to create a connection for the current StreamAllocation, namely a RealConnecton object.

ConnectInterceptor. Intercept () and step below, it returns the codec of the StreamAllocation directly, is very simple.

To summarize what newStream does:

  1. Reuse or create connections. Multiplexing means multiplexing allocated connections from a ConnectionPool or using StreamAllocation; Create a RealConnection object that cannot be reused
  2. If you make a new connection, it will connect to the server. Here is mainly Socket connection, including TCP + HTTPS handshake process
  3. in
  4. Now that the connection is established, the CallServerInterceptor interceptor starts sending and reading data from the server

noNewStreams

This step closes the corresponding Socket

RealConnection

The connect method

All of the analysis so far has just opened a RealConnection object for StreamAllocation, which is all the ConnectInterceptor does

As mentioned above, if it is a new connection, the RealConnection#connect() method is called, which completes the TCP + HTTPS handshake:

Http1.1 adds a tunnel to http1.1.

Then watch connectSocket () :

Then look at establishProtocol ()

StartHttp2 () is called, and the most important thing this method does is assign RealConnection#http2Connection, which creates an http2Connection object.

Where coonectTls() uses SSLSocket to complete the HTTPS handshake:

There is an if judgment in the code above, which mainly verifies that the resulting certificate is usable. This includes x509 certificate knowledge (mainly the Subject Alternative Name field on certificates) and can be used on OkHostnameVerifier. If judgments are ultimately related methods in this class.

socketFactory

In the connect () to create a Socket () to use the factory pattern, factory source is the Address of the corresponding properties, and create in RetryAndFollowUpInterceptor Address

The above three values are all from client, which means they can be specified using okHttpClient.builder:

CallServerInterceptor

All of the above analysis was to establish the connection, and now that the connection has been established, what remains is to use the connection to send requests and receive data. This part is in the CallServerInterceptor. The code is longer, so look at it in sections

Http1Codec/Http2Codec/Http2Codec/Http1Codec/Http2Codec

After the above two steps, all the request data is sent, the following is the result of reading, and some cases:

In HTTP, 204,205 should have no response body, so the last judgment will handle some exceptions.

HttpCodec

So far, only the parts that actually read and write data have been left out. This part is the implementation of HttpCodec. It is divided into two implementations of http1Codec and http2Codec according to the version of HTTP.

The former is simpler and can be read and written directly using the input and output streams. The latter will first convert the data into binary frames, and then read and write, which will not be analyzed.