Written in the beginning

OkHttp is currently considered the mainstream Android web framework, and even Google has officially replaced the implementation of web requests with okHttp.

There are also a lot of people on the Internet to analyze okHttp source code, but based on the analysis of everyone is not the same, the reader seems to harvest is also different, so I sorted out the idea, write a bit of my own analysis.

This article is based on the OKHttp3.11.0 version analysis

Basic usage

String url = "http://www.baidu.com";
//'1. Generate OkHttpClient instance object '
OkHttpClient okHttpClient = new OkHttpClient();
//'2. Generate Request object '
Request request = new Request.Builder()
    .url(url)
    .post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"),"test content"))
    .build();
Call call = okHttpClient.newCall(request);

call.enqueue(new Callback() {
    @Override
    public void onFailure(@NonNull Call call, @NonNull IOException e) {}@Override
    public void onResponse(@NonNull Call call, @NonNull Response response)  {}});Copy the code

The whole process

Borrow a flow chart from someone else to summarize the okHttp request to the original source

The overall okHttp process can be divided into the following phases
  1. Create the request object (URL, Method,body)–> Request –>Call

  2. Request event queue, thread pool distribution enqueue–>Runnable–>ThreadPoolExecutor

  3. Recursive Interceptor: an Interceptor that sends requests. InterceptorChain

  4. Request callback, data parsing. Respose–>(code,message,requestBody)

Create the request object

Request maintains the attributes of the Request object

public final class Request {
    final HttpUrl url;  
    final String method;
    final Headers headers;
    final @Nullable RequestBody body;
    At okHttp2.x, okHttpClint provides the Cancel(tag) method to Cancel requests in batches
    // However, the bulk request API on 3.x has been removed, and the only way to cancel the request is to call call.cancel() in Callback.
    // Therefore, the tags parameter can only be written by the developer to implement the batch cancel request operation
    finalMap<Class<? >, Object> tags; }Copy the code

The Call wrapper interface for the request response

public interface Call extends Cloneable {
	Request request(a);
	Response execute(a) throws IOException;
	void enqueue(Callback responseCallback);
	void cancel(a);
}
Copy the code

Request event queue, thread pool distribution

Call’s implementation classes RealCall and AsyncCall

  @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));
  }

AsyncCall is an inner class of RealCall that inherits from Runnable so that AsyncCall's execute function can be called back and forth through the thread pool

final class AsyncCall extends NamedRunnable {
    @Override 
    protected void execute(a) {
        boolean signalledCallback = false;
        try {
            / / getResponseWithInterceptorChain interceptor chain of logic, is also a request the real entrance
            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) { ... }... }}Copy the code

Recursive Interceptor: an Interceptor that sends requests

Response getResponseWithInterceptorChain(a) throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
 	// User-defined interceptors (note that addAll allows you to add multiple custom interceptors)
    interceptors.addAll(client.interceptors());
    // Retry with redirection interceptor
    interceptors.add(retryAndFollowUpInterceptor);
    // Content interceptor
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    // Cache interceptor
    interceptors.add(new CacheInterceptor(client.internalCache()));
    // Network connection interceptor
    interceptors.add(new ConnectInterceptor(client));
    if(! forWebSocket) {// User-defined network interceptor
      interceptors.addAll(client.networkInterceptors());
    }
    // Interceptor for service requests
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null.null.null.0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }
Copy the code

The core of okHttp is the Interceptor chain. Each Interceptor is responsible for its own part of the functionality, internally traversing each Interceptor in a recursive fashion. The recursive logic is under the RealInterceptorChain class

public final class RealInterceptorChain implements Interceptor.Chain {
    
    // Interceptor recursive entry
    public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {...// Interceptor recursive core code, according to the interceptors list to execute each interceptor intercept function
    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); .returnresponse; }}Copy the code

Recursive after to get the request and response, so that our request behavior at this interceptor in the chain, then we should see is responsible for that part of the interceptor network request, from the name of the class can easily see that ConnectInterceptor and The CallServerInterceptor is the main job of both interceptors.

Network connection interceptorConnectInterceptor
public final class ConnectInterceptor implements Interceptor {
  
    @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.
        booleandoExtensiveHealthChecks = ! request.method().equals("GET");
        HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
        RealConnection connection = streamAllocation.connection();

        returnrealChain.proceed(request, streamAllocation, httpCodec, connection); }}Copy the code

There are a few objects to illustrate

  • **StreamAllocation:** Memory stream storage space. This object can be obtained directly from the realChain, indicating that it has been assigned in the previous interceptor chain

  • HttpCodec(Encodes HTTP Requests and decodes HTTP Responses): Encoding requests and decoding response data

  • ** realchain.proceed ():** notifies the next interceptor to proceed

What happens in the newStream function that creates the HttpCodec object

//HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);

public HttpCodec newStream(
      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {...try {
      //findHealthyConnection internally looks for an available connection through an infinite loop, preferentially using the available connection, otherwise using the // thread pool, where the synchronized keyword is used to prevent multiple concurrent problems
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

      synchronized (connectionPool) {
        codec = resultCodec;
        returnresultCodec; }}catch (IOException e) {
      throw newRouteException(e); }}Copy the code

Moving down the code, you can see that the class responsible for the network connection function is actually a class called RealConnection, which has a function called Connect

RealConnection#connect

 public void connect(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
      EventListener eventListener) {...while (true) {
      try {
        if (route.requiresTunnel()) {
          // This function will eventually end up in the connectSocket() function
          connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
          if (rawSocket == null) {
            // We were unable to connect the tunnel but properly closed down our resources.
            break; }}else{ connectSocket(connectTimeout, readTimeout, call, eventListener); }}... }/ / end call or the Socket object to create the network connection, including connectTimeout, readTimeout parameters such as this time is really set up.
Copy the code
Network request interceptorCallServerInterceptor

This is the last interceptor in the chain. It makes a network call to the server.

Look directly at the Intercept function of CallServerInterceptor

@Override
public Response intercept(Chain chain) throws IOException{
    // The following parameters are generated by previous interceptors
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();
    
    // Send the request header, which is also the beginning of the network request
    httpCodec.writeRequestHeaders(request);
    
    Response.Builder responseBuilder = null;
    // The request is not get, and the request body is added, and the request body information is written
    if(HttpMethod.permitsRequestBody(request.method()) && request.body() ! =null) {
      // If there is an Expect:100-continue attribute in the request header
      // It sends a header section to the server and asks if the server supports Expect:100-continue as an extension field
      // OkHttp3 provides this judgment in order to be compatible with http2's connection reuse behavior
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        // Flush the cache to write data to the server
        httpCodec.flushRequest();
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(true);
      }
		
      // Write the request body
      if (responseBuilder == null) {
        realChain.eventListener().requestBodyStart(realChain.call());
        long contentLength = request.body().contentLength();
        CountingSink requestBodyOut =
            newCountingSink(httpCodec.createRequestBody(request, contentLength)); BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); request.body().writeTo(bufferedRequestBody); bufferedRequestBody.close(); realChain.eventListener() .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount); }... httpCodec.finishRequest();// Response related code. }Copy the code

Write the core code for the request body

// Write the request body to BufferedSink, which is a class from Okio, another library
CountingSink requestBodyOut =
            new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);


/ / httpCodec. Call finishRequest will eventually sink. Flush (), the object of the sink is BufferedSink, BufferedSink at the bottom
// Pushes its data to the server, acting as a buffer flush function
httpCodec.finishRequest();
Copy the code

Response-related code

if (responseBuilder == null) {
    realChain.eventListener().responseHeadersStart(realChain.call());
  	If code==100, the buffer will return the response header. If code==100, the buffer will return the response header. If code==100, the buffer will return the response header
    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 the server response code is 100, we need to request again
    // The preceding 100 is the identifier of headerLine
    responseBuilder = httpCodec.readResponseHeaders(false);

    response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    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 {
    // Read the response body
    response = response.newBuilder()
        .body(httpCodec.openResponseBody(response))
        .build();
}
return response;
Copy the code

Read the response body HttpCodec#openResponseBody

public ResponseBody openResponseBody(Response response) throws IOException {... Source source = newFixedLengthSource(contentLength);return newRealResponseBody(contentType, contentLength, Okio.buffer(source)); . }//openResponseBody passes the Socket InputStream InputStream to OkIo's Source object and encapsulates RealResponseBody (a subclass of ResponseBody) as the body of Response.

// We read the RealResponseBody parent class ResponseBody, which has a string() function

// The response body is stored in memory, and source.readString is called to read the data from the server. Note that the method ends by calling closeQuietly to close the InputStream InputStream for the current request, so the string() method can only be called once and an error will be reported if called again
public final String string(a) throws IOException {
    BufferedSource source = source();
    try {
        Charset charset = Util.bomAwareCharset(source, charset());
        return source.readString(charset);
    } finally{ Util.closeQuietly(source); }}Copy the code

Request callback, data parsing

After we get the Response that requested the callback, we go back to the code that we originally called,

String url = "http://www.baidu.com";
//'1. Generate OkHttpClient instance object '
OkHttpClient okHttpClient = new OkHttpClient();
//'2. Generate Request object '
Request request = new Request.Builder()
    .url(url)
    .post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"),"test content"))
    .build();
Call call = okHttpClient.newCall(request);

call.enqueue(new Callback() {
    @Override
    public void onFailure(@NonNull Call call, @NonNull IOException e) {}@Override
    public void onResponse(@NonNull Call call, @NonNull Response response)  {
        Headers responseHeaders = response.headers();
        for (int i = 0, size = responseHeaders.size(); i < size; i++) {
            System.out.println(responseHeaders.name(i) + ":"+ responseHeaders.value(i)); } System.out.println(response.body().string()); }});Copy the code

We can get all the data we need from the Response object, including header and body. At this point, the general flow of okHttp’s network request has been analyzed, and there are some interceptors that are not covered in this article. For those interested, see the reference links below or Google yourself.

Refer to the article

Okhttp: CallServerInterceptor

OkHttp for each interceptor resolution

Android Skill Tree – Network summary OkHttp super super super super super super super super super detail analysis

OkHttp3.0 parsing – a source code perspective on what to do when making a web request

Further reading

Expect: 100-continue for Http headers

Expect request header fields that indicate specific server behavior required by clients. If the server cannot understand or meet any Expectation in the Expect field, it must return to a state of 417 Failed, or 4XX if there are other problems with the request. The purpose of the Expect: 100-continue handshake is to allow the client to determine whether the source server is willing to accept the request (based on the request header) before sending the request content. Expect: 100-continue The Expect: 100-continue handshake should be used with caution, as it can cause problems with servers or proxies that do not support HTTP/1.1.Copy the code

The main advantages of HTTP2 over http1.x are the following

  • New data formats, HTTP based on file protocol parsing, HTTP2 based on binary protocol parsing,
  • MultiPlexing connection sharing
  • Header compression, which reduces header size to make requests faster
  • Compression algorithm from GZIP to HPACK algorithm, anti-cracking
  • Resetting the connection performs better. Http1. x cancels the request by disconnecting the connection directly, whereas http2 is a stream that disconnects a connection
  • More secure SSL

The resources

Http1.x differs from http2