OKHttp3 源码解析之拦截器(二)

在上一篇文章中,主要梳理了 OKHttp3.8.0 请求的整体流程,了解到拦截器在其中有非常重要的意义,那么本篇文章就重点介绍一下 OKHttp 中的精髓 —- 拦截器。本文中涉及到的自定义类的源码都在 Github 上的 OkHttpPractice 工程中。

  1. 拦截器的使用
  2. 源码中拦截器的应用及分析

1. 拦截器的使用

拦截器是 OKHttp 设计的精髓所在,每个拦截器负责不同的功能,使用责任链模式,通过链式调用执行所有的拦截器对象中的 Response intercept(Chain chain) 方法。拦截器在某种程度上也借鉴了网络协议中的分层思想,请求时从最上层到最下层,响应时从最下层到最上层。

一个拦截器可以拦截请求和响应,获取或修改其中的信息,这在编程中是非常有用的。不仅在源码中拦截器使用的很广泛,开发者也可以根据自己的需求自定义拦截器,并将其加入到 OkHttpClient 对象中。

1.1 自定义拦截器

拦截器的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Observes, modifies, and potentially short-circuits requests going out and the corresponding
* responses coming back in. Typically interceptors add, remove, or transform headers on the request
* or response.
*/
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
/**
* Returns the connection the request will be executed on. This is only available in the chains
* of network interceptors; for application interceptors this is always null.
*/
@Nullable Connection connection();
}
}

其中最重要的方法便是 Response intercept(Chain chain) 方法。自定义拦截器只要实现 interceptor 接口,重写其中的 Response intercept(Chain chain) 方法便基本完成。

在开发中,经常想查看服务器响应中的内容,如果使用拦截器的话,则可以非常方便的实现,只要自定义拦截器并加入到 OKHttpClient 对象中,那么使用此 OKHttpClient 对象进行的网络请求都会将其响应信息打印出来。自定义的拦截器 LogInterceptor,源码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class LogInterceptor implements Interceptor {
private static final Charset UTF8 = Charset.forName("UTF-8");
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response;
long startTime = System.currentTimeMillis();
response = chain.proceed(request);
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
BufferedSource source = response.body().source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.buffer();
String log = "\n================="
.concat("\nnetwork code ==== " + response.code())
.concat("\nnetwork url ===== " + request.url())
.concat("\nduration ======== " + duration)
.concat("\nrequest duration ============ " + (response.receivedResponseAtMillis() - response.sentRequestAtMillis()))
.concat("\nrequest header == " + request.headers())
.concat("\nrequest ========= " + bodyToString(request.body()))
.concat("\nbody ============ " + buffer.clone().readString(UTF8));
Log.i("lijk", "log is " + log);
return response;
}
/**
* 请求体转String
*
* @param request 请求体
* @return String 类型的请求体
*/
private static String bodyToString(final RequestBody request) {
try {
final Buffer buffer = new Buffer();
request.writeTo(buffer);
return buffer.readUtf8();
} catch (final Exception e) {
return "did not work";
}
}
}

1.2 注意事项

这里有个坑需要注意一下:response.body().string(); 方法只能被调用一次,如果多次调用 response.body().string(); 则会抛出如下异常:



可以看到 string() 的源码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Returns the response as a string decoded with the charset of the Content-Type header. If that
* header is either absent or lacks a charset, this will attempt to decode the response body in
* accordance to <a href="https://en.wikipedia.org/wiki/Byte_order_mark">its BOM</a> or UTF-8.
* Closes {@link ResponseBody} automatically.
*
* <p>This method loads entire response body into memory. If the response body is very large this
* may trigger an {@link OutOfMemoryError}. Prefer to stream the response body if this is a
* possibility for your response.
*/
public final String string() throws IOException {
BufferedSource source = source();
try {
Charset charset = Util.bomAwareCharset(source, charset());
return source.readString(charset);
} finally {
Util.closeQuietly(source);
}
}

因为在执行完读取数据之后,IO 流被关闭,如果再次调用此方法,就会抛出上面的异常。

而且从注释中可以看到,此方法将响应报文中的主体全部都读到了内存中,如果响应报文主体较大,可能会导致 OOM 异常。所以更推荐使用流的方式获取响应体的内容。如下所示:

1
2
3
4
BufferedSource source = response.body().source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.buffer();
String response = buffer.clone().readString(UTF8);

2. 源码中拦截器的应用及分析

在上一篇分析整体流程的文章中,在 RealCall 中有一个方法非常重要,不论是异步请求还是同步请求,都是通过该方法获取服务器响应的,源码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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);
// 通过 RealInterceptorChain 对象链式的调用拦截器,从而得到响应。
return chain.proceed(originalRequest);
}

其中的每个拦截器负责具体不同的功能,接下来就分析每个拦截器的功能,因为拦截器中最重要的方法便是 Response intercept(Chain chain),所以我们也重点分析 Response intercept(Chain chain) 方法的实现。

2.1 RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor 拦截器主要负责实现 HTTP 协议中的认证质询、重定向和超时重试等协议机制。

RetryAndFollowUpInterceptor 拦截器的主要功能如下所示:

  1. 初始化连接对象 StreamAllocation
  2. 通过 RealInterceptorChain 调用链对象得到响应
  3. 通过得到的响应,根据 HTTP 协议做认证质询、重定向和超时重试等处理,通过 followUpRequest() 方法创建后续新的请求
  4. 若没有后续请求,即 followUpRequest() 方法返回为 null,则说明当前请求结束,返回响应
    ,若后续请求不为空,则继续进行请求

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 创建一个 StreamAllocation 对象
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
// 启动一个 While 死循环
while (true) {
// 判断是否已经取消,若已取消则抛出 IO 异常
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
// 通过 RealInterceptorChain 对象调用下一个拦截器,并从中得到响应
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
// 如果进入 RouteException 路由异常,则尝试是否可以重新进行请求,若可以则从头开始新的请求
if (!recover(e.getLastConnectException(), false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
// 若是进入 IOException IO异常,若可以重新尝试请求,则从头开始新的请求
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
// 如果没有抛出异常,则释放资源。
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// Attach the prior response if it exists. Such responses never have a body.
// 如果之前发生过重定向,并且 priorResponse 不为空,则创建新的 响应对象,并将其 body 置位空
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
// 添加认证需要的头部,处理重定向或超时重试,得到新的请求
// followUpRequest() 方法很重要,涉及到 HTTP 中认证质询、重定向和重试等协议的实现
Request followUp = followUpRequest(response);
// 若 followUp 重试请求为空,则当前请求结束,并返回当前的响应
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
// 关闭响应结果
closeQuietly(response.body());
// 若重定向、认证质询、重试次数超过 MAX_FOLLOW_UPS,则抛出 ProtocolException 异常
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
// 若请求中的主体为 UnrepeatableRequestBody 不可被重复使用的请求体类型,则抛出异常
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
// 判断是否是相同的连接,若不相同则释放 streamAllocation,并重新创建新的 streamAllocation 对象
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(followUp.url()), callStackTrace);
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
request = followUp;
priorResponse = response;
}
}

2.2 BridgeInterceptor

BridgeInterceptor 拦截器主要功能是:

  • 设置一些请求和响应首部,如:Content-TypeContent-LengthHost 等常见的请求和响应首部。
  • 处理 HTTP 请求和响应中的 Cookie
  • 如果在请求中设置了编码,要从响应流中解码

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
if (body != null) {
// 在请求中设置实体首部 Content-Type
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
// 在请求中设置实体首部 Content-Length 和 Transfer-Encoding
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
// 设置请求首部 `Host`
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
// 设置请求首部 Connection,若 `Connection` 为空,则打开长连接
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
// 如果在请求中设置了 "Accept-Encoding: gzip",要记得从响应流中解码
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
// 为请求添加 Cookie
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
// 设置请求首部 "User-Agent"
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
Response networkResponse = chain.proceed(requestBuilder.build());
// 从响应中得到 Cookie,并交给传入的 CookieJar 对象处理
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
// 如果之前在请求中设置了 "Accept-Encoding: gzip" 编码,则需要对响应流进行解码操作并移除响应中的首部字段 “Content-Encoding” 和 “Content-Length”
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}

这里涉及到 OKHttp 中对 Cookie 的处理,其中有一个接口十分重要 —- CookieJar,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface CookieJar {
/** A cookie jar that never accepts any cookies. */
CookieJar NO_COOKIES = new CookieJar() {
@Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
}
@Override public List<Cookie> loadForRequest(HttpUrl url) {
return Collections.emptyList();
}
};
// 从响应中获取 Cookie
void saveFromResponse(HttpUrl url, List<Cookie> cookies);
// 为请求添加 Cookie
List<Cookie> loadForRequest(HttpUrl url);
}

OKHttp 对 Cookie 的管理还是十分方便简洁的。创建一个类实现 Cookiejar 接口,并将其对象设置给 OKHttpClient,那么使用此 OKHttpClient 对象进行的网络请求都会自动处理 Cookie。这里有一个我实现的自动管理 Cookie 的类 CustomCookieManager,可以实现 Cookie 的持久化,Github 上还有其他很好的实现在 OKHttp 中管理 Cookie 的类,也可以参考。

2.3 CacheInterceptor

CacheInterceptor 实现了 HTTP 协议中的缓存机制,其主要功能如下:

  1. 从缓存中读取缓存,并创建缓存策略对象
  2. 根据创建的缓存策略对象,从缓存、网络获取响应并生成最终的响应对象
  3. 更新缓存内容,并返回响应对象

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
public final class CacheInterceptor implements Interceptor {
final InternalCache cache;
public CacheInterceptor(InternalCache cache) {
// 从 OKHttpClient 中传入的 InternalCache(内部缓存)对象
this.cache = cache;
}
@Override public Response intercept(Chain chain) throws IOException {
// 获取候选缓存
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 创建缓存策略对象,并从中得到请求和响应
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// If we're forbidden from using the network and the cache is insufficient, fail.
// 如果不走网络进行请求,并且缓存响应为空,则创建状态码为 504 的响应并返回
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// If we don't need the network, we're done.
// 如果不走网络进行请求,并且缓存响应不为空,则返回从缓存中获取的响应
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
// 如果前面的条件都不满足,从拦截器链中进行网络请求并得到响应
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
// 如果缓存响应也不为空,并且网络响应的状态码为 304,则根据缓存响应结果生成最终的响应并返回
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
// 更新缓存中的响应内容
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
// 根据网络请求响应生成最终的响应
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
// 如果缓存对象不为空,则将响应加入到缓存中
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
......
}

2.4 ConnectInterceptor

ConnectInterceptor 拦截器的主要功能是:

  • 得到从 RetryAndFollowUpInterceptor 中创建的 StreamAllocation 对象
  • 通过 StreamAllocation 对象创建 HttpCodec 对象
  • 通过 StreamAllocation 对象创建 RealConnection 对象
  • 最终通过 RealInterceptorChainproceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection) 方法得到响应对象并返回。

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/** Opens a connection to the target server and proceeds to the next interceptor. */
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@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.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}

ConnectInterceptor 拦截器中用到了 StreamAllocation 类,此类创建了 HttpCodecRealConnection 对象,这两个类在网络请求中都是非常重要的类,会在后面文章中详细分析

2.5 CallServerInterceptor

CallServerInterceptor 拦截器中最主要的功能就是:

  • 遵循 HTTP 协议规范,通过 HttpCodec 对象写入请求头、请求主体、读取响应头和响应主体
  • 生成最初的响应对象并返回

源码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
@Override public Response intercept(Chain chain) throws IOException {
// 得到 httpCodec、streamAllocation、connection 和 request 对象
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
// 向 HttpCodec 对象中写入请求头部信息
long sentRequestMillis = System.currentTimeMillis();
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
// 判断该请求的请求方法是否允许被发送请求体,请求体是否为空
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return what
// we did get (such as a 4xx response) without ever transmitting the request body.
// 若在请求头部中存在 ”Expect: 100-continue“,先不发送请求主体,只有收到 ”100-continue“ 响应报文才会将请求主体发送出去。
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
} else if (!connection.isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection from
// being reused. Otherwise we're still obligated to transmit the request body to leave the
// connection in a consistent state.
streamAllocation.noNewStreams();
}
}
// 完成请求的发送
httpCodec.finishRequest();
// 读取响应头部信息
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
// 创建请求响应对象
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int 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 {
// 读取响应中的响应体信息
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
// 判断是否关闭长连接
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
// 如果响应的状态码为 204 和 205 并且响应体不为空,则抛出异常
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}

在此拦截器中非常重要的一个对象是 HttpCodec,它是一个接口,具体的实现类有 Http1Codec 和 Http2Codec 两个类,分别对应着 HTTP1.1 和 HTTP2。在 HttpCodec 内部是通过 sinksource 来实现的。

2.6 注意

关于 OKHttp 的拦截器需要注意的一点是,从 RealCallgetResponseWithInterceptorChain() 方法中可以看到开发自定义的拦截器有两种类型的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
return chain.proceed(originalRequest);
}

一种是在最开始,通过 client.interceptors() 方法添加的普通拦截器;另一种是通过 client.networkInterceptors() 方法添加的网络请求拦截器,两者的区别如下:

  • 普通拦截器:对发出去的请求做最初的处理,对最终得到的响应做处理
  • 网络拦截器:对发出去的请求做最后的处理,对收到的响应做最初的处理

两种拦截器都可以通过在初始化 OKHttpClient 对象的时候设置,如下所示:

1
2
3
4
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LogInterceptor())
.addNetworkInterceptor(new LogInterceptor())
.build();

虽然都是添加了 LogInterceptor 拦截器给 OKHttpClient 对象,但是从 RealCallgetResponseWithInterceptorChain() 方法中可以看出两个拦截器调用的时机不同。


本文中涉及到的自定义类的源码都在 Github 上的 OkHttpPractice 工程中。


参考资料:

OkHttp源码解析俞其荣

OkHttp源码解析高沛

OKhttp源码学习(十)——写在最后小禤大叔