Interceptors
拦截器是一种强大的机制,可以监视,重写和重试调用。
这是一个简单的拦截器,记录传出请求和传入响应。
1 | class LoggingInterceptor implements Interceptor { |
对chain.proceed(request)的调用是每个拦截器实现的关键部分。
这种简单的方法是所有HTTP工作发生的地方,产生满足请求的响应。
拦截器可以链式调用。
假设您同时拥有压缩拦截器和校验和拦截器:您需要确定数据是否已压缩,然后进行校验和再压缩。
OkHttp使用列表来跟踪拦截器,并按顺序调用拦截器。
Application Interceptors
拦截器被注册为应用程序或网络拦截器。
我们将使用上面定义的LoggingInterceptor来显示差异。
通过在OkHttpClient.Builder上调用addInterceptor()来注册应用程序拦截器:
1 | OkHttpClient client = new OkHttpClient.Builder() |
URL http://www.publicobject.com/helloworld.txt重定向到https://publicobject.com/helloworld.txt,OkHttp会自动跟随此重定向。
我们的应用程序拦截器被调用一次,而从chain.proceed()返回的响应具有重定向的响应:
1 | INFO: Sending request http://www.publicobject.com/helloworld.txt on null |
我们可以看到我们被重定向,因为response.request()。url()与request.url()不同。
这两个日志语句记录了两个不同的URL。
Network Interceptors
注册网络拦截器非常相似。
调用addNetworkInterceptor()而不是addInterceptor():
1 | OkHttpClient client = new OkHttpClient.Builder() |
当我们运行此代码时,拦截器运行两次。
一次是对http://www.publicobject.com/helloworld.txt的初始请求,另一次是为了重定向到https://publicobject.com/helloworld.txt
1 | INFO: Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=none protocol=http/1.1} |
网络请求还包含更多数据,例如OkHttp添加的Accept-Encoding:gzip标头,用于通告对响应压缩的支持。
网络拦截器的链具有非空连接,可用于询问用于连接到Web服务器的IP地址和TLS配置。
Choosing between application and network interceptors
每个拦截链都有相对的优点。
Application interceptors
- 不需要担心重定向和重试等中间响应。
- 即使从缓存提供HTTP响应,也始终调用一次。
- 观察应用程序的原始意图。没有关注OkHttp注入的标题,如If-None-Match。
- 允许短路而不是调用Chain.proceed()。
- 允许重试并对Chain.proceed()进行多次调用。
Network Interceptors
- 能够对重定向和重试等中间响应进行操作。
- 未调用使网络短路的缓存响应。
- 观察数据,就像它将通过网络传输一样。
- 访问带有请求的Connection。
Rewriting Requests
拦截器可以添加,删除或替换请求标头。
他们还可以转换那些拥有一个请求的主体。
例如,如果要连接到已知支持它的Web服务器,则可以使用应用程序拦截器添加请求主体压缩。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/** This interceptor compresses the HTTP request body. Many webservers can't handle this! */
final class GzipRequestInterceptor implements Interceptor {
public Response intercept(Interceptor.Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
return chain.proceed(originalRequest);
}
Request compressedRequest = originalRequest.newBuilder()
.header("Content-Encoding", "gzip")
.method(originalRequest.method(), gzip(originalRequest.body()))
.build();
return chain.proceed(compressedRequest);
}
private RequestBody gzip(final RequestBody body) {
return new RequestBody() {
public MediaType contentType() {
return body.contentType();
}
public long contentLength() {
return -1; // We don't know the compressed length in advance!
}
public void writeTo(BufferedSink sink) throws IOException {
BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
body.writeTo(gzipSink);
gzipSink.close();
}
};
}
}Rewriting Responses¶
同样的,拦截器可以重写响应头并转换响应体。
这通常比重写请求标头更危险,因为它可能违反了网络服务器的期望。
如果您处于棘手的情况并准备应对后果,重写响应标头是解决问题的有效方法。
例如,您可以修复服务器配置错误的Cache-Control响应标头,以实现更好的响应缓存:
1 | /** Dangerous interceptor that rewrites the server's cache-control header. */ |
通常,这种方法在补充Web服务器上的相应修复时效果最佳!