In-depth explanation of OkHttp source code analysis and application practice

ChatGPT has been soaring for 160 days, and the world is no longer what it used to be.

A new artificial intelligence Chinese website https://ai.weoknow.com
updates the available chatGPT resources available in China every day


OkHttp is widely used in the world of Java and Android, deep learning of source code is helpful to master software features and improve programming level.

This paper first briefly analyzes the core code in the process of request initiation from the source code, then briefly introduces the overall structure of OkHttp through the flow chart and architecture diagram, focuses on the analysis of the interceptor chain of responsibility model design, and finally lists the OkHttp interception The practical application of the device in the project.

1. Background introduction

In production practice, such a scenario is often encountered: it is necessary to perform unified processing for a certain type of Http request, such as adding request parameters in the Header or modifying the request response. A more elegant solution to this type of problem is to use interceptors to uniformly process requests and responses.

OkHttp is widely used in Android and Java world for its efficiency and ease of use. As an excellent open source Http request framework, a deep understanding of its implementation principles can help us learn excellent software design and coding experience, help us better use its features, and help troubleshoot problems in special scenarios. This article attempts to explore the basic principles of OkHttp from the source code, and lists a simple example to illustrate the actual application of interceptors in our project. The source code of this article is based on OkHttp 3.10.0.

2. Basic principles of OkHttp

2.1 Starting from an example request

OkHttp can be used to send synchronous or asynchronous requests. The main difference between an asynchronous request and a synchronous request is that the asynchronous request will be handed over to the thread pool to schedule the execution of the request. The code to send a synchronous request using OkHttp is quite simple, the sample code is as follows:

Synchronous GET request example

 

// 1.创建OkHttpClient客户端OkHttpClient client = new OkHttpClient();public String getSync(String url) throws IOException {
   
         OkHttpClient client = new OkHttpClient();      // 2.创建一个Request对象      Request request = new Request.Builder()              .url(url)              .build();      // 3.创建一个Call对象并调用execute()方法      try (Response response = client.newCall(request).execute()) {
   
             return response.body().string();      }  }

The execute() method is the entry point for request initiation, and the source code of the execute() method of the RealCall object is as follows:

RealCall's execute() method source code

 

@Override public Response execute() throws IOException {
   
     synchronized (this) { // 同步锁定当前对象,将当前对象标记为“已执行”    if (executed) throw new IllegalStateException("Already Executed");    executed = true;  }  captureCallStackTrace(); // 捕获调用栈  eventListener.callStart(this); // 事件监听器记录“调用开始”事件  try {
   
       client.dispatcher().executed(this); // 调度器将当前对象放入“运行中”队列    Response result = getResponseWithInterceptorChain(); // 通过拦截器发起调用并获取响应    if (result == null) throw new IOException("Canceled");    return result;  } catch (IOException e) {
   
       eventListener.callFailed(this, e); // 异常时记录“调用失败事件”    throw e;  } finally {
   
       client.dispatcher().finished(this); // 将当前对象从“运行中”队列移除  }}

The execute() method first marks the current request as "executed", then adds stack trace information for the retry tracking interceptor, then the event listener records the "call started" event, and the scheduler puts the current object into "running" Queue, then initiate a call and get a response through the interceptor, and finally remove the current request from the "running" queue in the finally block, and the event listener records the "call failed" event when an exception occurs. The key method is 

The source code of getResponseWithInterceptorChain() is as follows:

Response getResponseWithInterceptorChain() throws IOException {
   
       // 构建一个全栈的拦截器列表    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, ……);     return chain.proceed(originalRequest);  }

In this method, an ordered interceptor list is created in a specific order, and then the interceptor list is used to create an interceptor chain and initiate a proceed() method call. In the chain.proceed() method, the interceptors in the list will be concatenated in a recursive manner to process the request object in turn. The implementation of the interceptor chain is a clever part of OkHttp, and we will use a section to discuss it later. Before continuing to analyze, through the above code snippets, we have roughly seen the overall process of a request initiation.

2.2 OkHttp core execution process

The core execution process of an OkHttp request is shown in the following flowchart:

Figure 2-1 OkHttp request execution flow chart

The meaning and function of each part in the figure are as follows:

  • OkHttpClient : It is the core management class of the entire OkHttp. From the perspective of object-oriented abstract representation, it represents the client itself and is the call factory for requests, used to send requests and read responses. In most cases this class should be shared, because each Client object holds its own connection pool and thread pool. Repeated creation will result in a waste of resources on the free pool. The Client object can be created through the default no-argument construction method or a custom Client object can be created through the Builder. The thread pool and connection pool resources held by the Client can be automatically released when they are idle without manual release by the client code, and manual release is also supported in special cases.

  • Request : A Request object represents an Http request. It contains attributes such as request address url, request method type method, request headers, request body, etc. The attributes of this object are generally decorated with the final keyword, as described in the description document of this class, when this class This class is an immutable object when body is empty or body itself is an immutable object.

  • Response : A Response object represents a Http response. This instance object is an immutable object, only responseBody is a value that can be used once, and other attributes are immutable.

  • RealCall : A RealCall object represents a request call that is ready to be executed. It can only be executed once. At the same time, it is responsible for the two important tasks of scheduling and chain of responsibility organization.

  • Dispatcher : the scheduler. It determines when the asynchronous call is executed, internally uses ExecutorService to schedule execution, and supports custom Executor.

  • EventListener : Event listener. The abstract class EventListener defines the method of recording various events in a request life cycle. By listening to various events, it can be used to capture the execution indicators of application HTTP requests. This allows you to monitor the frequency and performance of HTTP calls.

  • Interceptor : Interceptor. Corresponding to the interceptor pattern in the software design pattern, the interceptor can be used to change and enhance the routine processing flow of the software. The core feature of this pattern is that the change of the software system is transparent and automatic. OkHttp splits the complex logic of the entire request into multiple independent interceptor implementations, connects them together through the design pattern of the chain of responsibility, and completes the process of sending requests and obtaining response results.

2.3 OkHttp Overall Architecture

By further reading the OkHttp source code, you can see that OkHttp is a layered structure. Software layering is a common method for complex system design. Through layering, complex problems can be divided into smaller sub-problems, and divide and conquer. At the same time, the layered design is also conducive to the encapsulation and reuse of functions. The architecture of OkHttp can be divided into: application interface layer, protocol layer, connection layer, cache layer, and I/O layer. Different interceptors provide call entries for each level of processing, and the interceptors are connected into an interceptor chain through the chain of responsibility mode to complete the complete processing flow of an Http request. As shown below:

Figure 2-2 Architecture diagram of OkHttp (picture from the Internet)

2.4 Types and functions of OkHttp interceptors

The core functions of OkHttp are realized through interceptors. The functions of various interceptors are:

  • client.interceptors : The interceptor set by the developer will perform the earliest interception processing before all interceptors are processed, and can be used to add some public parameters, such as custom headers, custom logs, and so on.

  • RetryAndFollowUpInterceptor : Mainly responsible for retrying and redirection processing.

  • BridgeInterceptor : Mainly responsible for the conversion of requests and responses. Convert the request object constructed by the user into a request object sent to the server, and convert the response returned by the server into a user-friendly response.

  • CacheInterceptor : It is mainly responsible for the related processing of the cache, and puts the Http request result into the cache, so that the next time the same request is made, the result can be directly read from the cache to improve the response speed.

  • ConnectInterceptor : Mainly responsible for establishing a connection, establishing a TCP connection or a TLS connection.

  • client.networkInterceptors : The interceptor set by the developer is essentially similar to the first interceptor, but because of the different location, the usage is different.

  • CallServerInterceptor : It is mainly responsible for the request and response of network data, that is, the actual network I/O operation. Send the request header and request body to the server, and parse the response returned by the server.

In addition to the interceptors provided by the framework, OkHttp supports user-defined interceptors to enhance the processing of requests. Custom interceptors can be divided into two categories, namely application interceptors and network interceptors. Their functional hierarchy is as follows picture:

Figure 2-3 Interceptor (picture from OkHttp official website)

Different interceptors have different application scenarios, and their respective advantages and disadvantages are as follows:

application blocker

  • No need to worry about intermediate responses like redirects and retries.

  • Always called once, even if the HTTP response is served from cache.

  • The raw request of the application can be observed. Don't care about the headers injected by OkHttp.

  • Allows short-circuiting without calling the Chain.proceed() method.

  • Allow retries and multiple calls to the Chain.proceed() method.

  • The call timeout can be adjusted using withConnectTimeout, withReadTimeout, withWriteTimeout.

network blocker

  • Ability to operate on intermediate responses such as redirects and retries.

  • Cached responses will not be called.

  • Raw data transmitted over the network can be observed.

  • The link carrying the request can be accessed.

2.5 Chain of Responsibility Mode Serial Interceptor Call

OkHttp has built-in 5 core interceptors to complete the key processing in the request life cycle, and it also supports adding custom interceptors to enhance and extend the Http client. These interceptors are connected in series through the chain of responsibility mode, making the request It can be transferred and processed between different interceptors.

2.5.1 Chain of Responsibility Model

The Chain of Responsibility pattern is a behavioral design pattern that allows requests to be sent along a chain of handlers. After receiving the request, each processor can process the request or pass it to the next processor in the chain.

Figure 2-4 Chain of Responsibility (picture from the Internet)

Applicable scenarios  include:

  • When a program needs to handle different kinds of requests in different ways

  • When a program must execute multiple handlers sequentially

  • When the required handlers and their order must be changed at runtime

advantage:

  • You can control the order in which requests are processed

  • The class that initiates the operation and performs the operation can be decoupled.

  • New handlers can be added to a program without changing existing code.

2.5.2 Serial connection of interceptors

The entry of the chain of responsibility begins with the proceed() method call of the first RealInterceptorChain object. The design of this method is very ingenious. Some more rigorous checks will be done in the complete proceed() method. After removing these checks, the core code of the method is as follows:

 

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

This code can be seen as three steps:

  1. Index judgment. The initial value of index is 0, which indicates the index order of the interceptor object in the list. The parameter increments by 1 each time the proceed method is executed, and exits abnormally when the index value is greater than the index subscript of the interceptor list.

  2. Create the next chain of responsibility object.

  3. Get an interceptor in index order and call the intercept() method.

chain of responsibility

Looking at this method alone, it seems that all interceptors cannot be connected in series. The key to the series lies in the intercept() method. The intercept() method is a method that must be implemented when implementing the interceptor interface. This method holds the next chain of responsibility object chain, In the implementation class of the interceptor, it is only necessary to call the chain.proceed() method again at the appropriate place in the intercept() method, and the program instruction will return to the above code fragment, so that the search and execution of the next interceptor can be triggered. Called, the order of interceptor objects in the list is very important in this process, because the order in which interceptors are called is their index order in the list.

recursive method 

From another point of view, the proceed() method can be seen as a recursive method. The basic definition of the recursive method is "calling the function itself in the definition of the function". Although the proceed() method does not call itself directly, except for the last interceptor, all other interceptors in the interceptor chain will call the chain at the appropriate position. The proceed() method, the chain of responsibility object and the interceptor object together form a logic loop that calls itself. According to the author's personal understanding, this is why the Chain interface in the source code is designed as the internal interface of the Interceptor interface. When understanding this code, the two interfaces should be viewed as a whole. From this perspective, such The interface design is in line with the principle of "high cohesion".

The relationship between the interceptor interceptor and the chain of responsibility chain is as follows:

Figure 2-5 Interceptor and Chain relationship diagram

3. The application of OkHttp interceptor in the project

In our project, there is a type of request that needs to add authentication information in the request header. Using interceptors to implement it can greatly simplify the code and improve code readability and maintainability. The core code only needs to implement interceptors that meet business needs as follows:

Add request header interceptor

 

public class EncryptInterceptor implements Interceptor {
   
       @Override    public Response intercept(Chain chain) throws IOException {
   
           Request originRequest = chain.request();         // 计算认证信息        String authorization = this.encrypt(originRequest);                 // 添加请求头        Request request = originRequest.newBuilder()                .addHeader("Authorization", authorization)                .build();        // 向责任链后面传递        return chain.proceed(request);    }}

Later, when creating the OkHttpClient client, use the addInterceptor() method to register our interceptor as an application interceptor, which can automatically and non-inductively add real-time authentication information to the request header.

Register an application interceptor

 

OkHttpClient client = new OkHttpClient.Builder()    .addInterceptor(new EncryptInterceptor())    .build();

4. Review and summary

OkHttp is widely used in the Java and Android worlds. By using OkHttp interceptors, one type of problem can be solved——to uniformly modify the request or response content for a type of request. An in-depth understanding of the design and implementation of OkHttp can not only help us learn the design and coding experience of excellent open source software, but also help us better use software features and troubleshoot problems in special scenarios. This article tries to start with an example of a synchronous GET request. First, it briefly analyzes the core code involved in a request initiation process through source code fragments, then summarizes the request execution process in the form of a flow chart, and then uses an architecture diagram to show OkHttp. Layer design, introduces the purpose, working level, advantages and disadvantages of various interceptors, and then focuses on the analysis of the responsibility chain model design of the interceptor-essentially a recursive call, and finally introduces the actual use of OkHttp interceptors with a simple example Applications in production scenarios.

reference:

  1. OkHttp Official Documentation

  2. OkHttp source code analysis series of articles


ChatGPT has been soaring for 160 days, and the world is no longer what it used to be.

A new artificial intelligence Chinese website https://ai.weoknow.com
updates the available chatGPT resources available in China every day

Guess you like

Origin blog.csdn.net/zyqytsoft/article/details/131075615