OkHttp Interceptors and the Chain of Responsibility Pattern
May 1, 2018 · 835 words
OkHttp is currently the most popular HTTP networking library for Android. Since Android 4.4, the underlying implementation of the standard library HttpURLConnection started using OkHttp. OkHttp + Retrofit is currently the mainstream choice for Android network requests.
OkHttp's source code offers many learning opportunities. This article describes the evolution of OkHttp's code architecture. OkHttp's current code architecture is quite clear. Among them, interceptors, which are core to sending network requests, are a typical application of the Chain-of-responsibility pattern in design patterns.
Basic Usage of OkHttp
OkHttp uses the Request and Response classes to model the input and output of network requests, and uses Call to model the behavior of network requests.
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.header("Accept", "text/html")
.build();
Call call = client.newCall(request);
Response response = call.execute();
int responseCode = response.code();
Interceptors
Interceptor is a powerful mechanism provided by OkHttp. Users can use Interceptors to monitor, rewrite, or retry network requests (calls). Users create an interceptor by implementing the Interceptor interface. For example, here is an interceptor that logs requests/responses:
class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
Custom interceptors can be specified when creating an OkHttp client:
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
As the name "interceptor" suggests, it can "intercept" requests or responses during a network request and perform custom operations.
Interceptors Chain
However, in OkHttp's internal implementation, interceptors are not just simple "interceptors." In fact, all core functionalities for sending network requests in OkHttp, including establishing connections, sending requests, and reading from cache, are implemented through interceptors. These interceptors collaborate during execution, forming an interceptor chain.
Let's understand how the interceptor chain works by looking at OkHttp's source code. Whether it's a synchronous or asynchronous request, OkHttp will enter getResponseWithInterceptorChain():
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());
return chain.proceed(originalRequest);
}
Each Interceptor here is a module that performs specific work, including user-defined client.interceptors(), RetryAndFollowUpInterceptor responsible for retrying failures and redirects, BridgeInterceptor responsible for converting user-constructed requests into requests sent to the server and converting server responses into user-friendly responses, CacheInterceptor responsible for returning cached responses directly and updating the cache, ConnectInterceptor responsible for establishing connections with the server, user-defined client.networkInterceptors(), and CallServerInterceptor responsible for sending request data to the server and reading response data from the server.
The Chain class is a utility class that assists interceptors in their execution. The 5th parameter of the Chain constructor, index, indicates that the i-th interceptor and subsequent ones are active. The initial value of index is 0, and it increments by 1 each time an interceptor is executed. When Chain.proceed(Request) is called, it will execute from the i-th interceptor sequentially, eventually returning a response object. Each interceptor.intercept() method calls Chain.proceed() to execute the subsequent interceptors. This way, all interceptors can be called in sequence. The sequence diagram is as follows:

Chain-of-responsibility pattern
The Chain-of-responsibility pattern involves command objects and a series of handler objects that implement the same interface, with these handler objects connected to form a chain of responsibility. Each handler object decides which command objects it can process, and for command objects it cannot handle, it passes them to the next handler object in the chain.
In OkHttp, the command object is the Request object, and the handler object is each Interceptor object. Each interceptor processes the request through some steps and passes the remaining work to the next interceptor. It's worth noting that if a handler object in the chain can fully process a command object, it does not need to pass it to the next handler object. OkHttp's CacheInterceptor also has the ability to fully handle requests. If the result of a request is already cached, there's no need to pass it to ConnectInterceptor or others for server connection and request sending; the cached response can be returned directly.