深入剖析OkHttp系列(三) 来自官方的拦截器设计(中英互译)

372 阅读6分钟

Interceptors

Interceptors are a powerful mechanism that can monitor, rewrite, and retry calls. Here's a simple interceptor that logs the outgoing request and the incoming response.

Interceptors(拦截器)是一种能监控, 重写, 重试请求的强大机制。这是一个简单的拦截器, 用于打印请求和响应的信息。
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;
  }
}

A call to chain.proceed(request) is a critical part of each interceptor’s implementation. This simple-looking method is where all the HTTP work happens, producing a response to satisfy the request.

在每个拦截器实现中, chain.proceed(request)的调用是关键的一部分。这种看似简单的方法是存在于所有HTTP工作的地方, 最终产生符合request的响应。

Interceptors can be chained. Suppose you have both a compressing interceptor and a checksumming interceptor: you'll need to decide whether data is compressed and then checksummed, or checksummed and then compressed. OkHttp uses lists to track interceptors, and interceptors are called in order.

拦截器是可以被链接的。 假如你同时有一个压缩拦截器和一个校验和拦截器: 你需要确定数据是否已经被压缩,然后校验, 或者是否被校验,然后压缩。 OkHttp使用一个列表来追踪拦截器并有序调用拦截器。

拦截器工作流

应用拦截器

Interceptors are registered as either application or network interceptors. We'll use the LoggingInterceptor defined above to show the difference.

Register an application interceptor by calling addInterceptor() on OkHttpClient.Builder:

拦截器被注册为应用拦截器或网络拦截器。 我们使用上面定义的LoggingInterceptor来展示有哪些不同。

通过在OkHttpClient.Builder上调用addInterceptor()来注册一个应用拦截器:
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new LoggingInterceptor())
    .build();

Request request = new Request.Builder()
    .url("http://www.publicobject.com/helloworld.txt")
    .header("User-Agent", "OkHttp Example")
    .build();

Response response = client.newCall(request).execute();
response.body().close();

The URL www.publicobject.com/helloworld.… redirects to publicobject.com/helloworld.…, and OkHttp follows this redirect automatically. Our application interceptor is called once and the response returned from chain.proceed() has the redirected response:

URL从http://www.publicobject.com/helloworld.https://publicobject.com/helloworld.txt, OkHttp会自动跟踪这次重定向。 我们的应用拦截器被调用了一次, 从chain.proceed()返回的响应具有重定向响应:
INFO: Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example

INFO: Received response for https://publicobject.com/helloworld.txt in 1179.7ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive

We can see that we were redirected because response.request().url() is different from request.url(). The two log statements log two different URLs.

我们可以看到我们已经重定向了, 因为response.request().url()和request().url()是不同的。 两次语句打印打印了不同的URLs。

网络拦截器

Registering a network interceptor is quite similar. Call addNetworkInterceptor() instead of addInterceptor():

注册一个网络拦截器很相似。 调用addNetworkInterceptor()替代addInterceptor():
OkHttpClient client = new OkHttpClient.Builder()
    .addNetworkInterceptor(new LoggingInterceptor())
    .build();

Request request = new Request.Builder()
    .url("http://www.publicobject.com/helloworld.txt")
    .header("User-Agent", "OkHttp Example")
    .build();

Response response = client.newCall(request).execute();
response.body().close();

When we run this code, the interceptor runs twice. Once for the initial request to www.publicobject.com/helloworld.…, and another for the redirect to publicobject.com/helloworld.….

当我们运行这段代码, 该拦截器运行了两次。 第一次是初始请求到http://www.publicobject.com/helloworld.txt, 第二次是重定向到https://publicobject.com/helloworld.txt。
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}
User-Agent: OkHttp Example
Host: www.publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip

INFO: Received response for http://www.publicobject.com/helloworld.txt in 115.6ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/html
Content-Length: 193
Connection: keep-alive
Location: https://publicobject.com/helloworld.txt

INFO: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1}
User-Agent: OkHttp Example
Host: publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip

INFO: Received response for https://publicobject.com/helloworld.txt in 80.9ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive

The network requests also contain more data, such as the Accept-Encoding: gzip header added by OkHttp to advertise support for response compression. The network interceptor's Chain has a non-null Connection that can be used to interrogate the IP address and TLS configuration that were used to connect to the webserver.

本次网络请求也包含了更多的数据, 比如Accept-Encoding: OkHttp添加了gzip头来声明对响应压缩的支持。 网络拦截器的链有一个用于询问连接服务器的IP地址和TLS配置。

Choosing between application and network interceptors, Each interceptor chain has relative merits.

在应用拦截器和网络拦截器之间选择, 每个拦截器都有相对的优点。

Application interceptors

Don't need to worry about intermediate responses like redirects and retries. Are always invoked once, even if the HTTP response is served from the cache. Observe the application's original intent. Unconcerned with OkHttp-injected headers like If-None-Match. Permitted to short-circuit and not call Chain.proceed(). Permitted to retry and make multiple calls to Chain.proceed().

应用拦截器:
1. 不需要担心重定向和重试等中间响应。
2. 始终调用一次, 即使请求是从缓存中提供的。
3. 观察应用程序的初始意图, 并没有关注OkHttp注入的headers, 如If-Non-Match。
4. 允许短路而不调用chain.proceed()。
5. 允许重试并多次调用chain.proceed()。

Network Interceptors

Able to operate on intermediate responses like redirects and retries. Not invoked for cached responses that short-circuit the network. Observe the data just as it will be transmitted over the network. Access to the Connection that carries the request.

网络拦截器:
1. 能够对重定向和重试等中间响应进行操作。
2. 未针对网络短路的缓存响应调用。
3. 观察数据,就像它通过网络传输一样。
4. 可以访问带request的连接。

重写请求

Interceptors can add, remove, or replace request headers. They can also transform the body of those requests that have one. For example, you can use an application interceptor to add request body compression if you're connecting to a webserver known to support it.

拦截器可以添加, 移除或替换请求头。 它们还可以转换那些有一个请求的主体。 比如, 如果你正在连接到一个可以支持压缩的服务器, 你可以通过应用拦截器来添加request body的压缩。

final class GzipRequestInterceptor implements Interceptor {
  
  @Override 
  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() {
      @Override 
      public MediaType contentType() {
        return body.contentType();
      }

      @Override 
      public long contentLength() {
        return -1; 
      }

      @Override 
      public void writeTo(BufferedSink sink) throws IOException {
        BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
        body.writeTo(gzipSink);
        gzipSink.close();
      }
    };
  }
}

重写响应

Symmetrically, interceptors can rewrite response headers and transform the response body. This is generally more dangerous than rewriting request headers because it may violate the webserver's expectations!

对应的,拦截器可以重写响应的headers, 转换response body。 这通常比重写request headers更危险, 因为它可能违反了服务器的规则。

If you're in a tricky situation and prepared to deal with the consequences, rewriting response headers is a powerful way to work around problems. For example, you can fix a server's misconfigured Cache-Control response header to enable better response caching:

如果你当前处于很棘手的状况, 并且准备应对后果, 重写response headers是一个解决问题的有力的方式。 比如, 你可以修复服务器错误配置的Cache-Control响应头, 来实现更好的响应缓存。
private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
  @Override public Response intercept(Interceptor.Chain chain) throws IOException {
    Response originalResponse = chain.proceed(chain.request());
    return originalResponse.newBuilder()
        .header("Cache-Control", "max-age=60")
        .build();
  }
};

Typically this approach works best when it complements a corresponding fix on the webserver!

实际上, 当它在服务器上补充了一个响应的修复后, 这种效果最好。

适用性

OkHttp's interceptors require OkHttp 2.2 or better. Unfortunately, interceptors do not work with OkUrlFactory, or the libraries that build on it, including Retrofit ≤ 1.8 and Picasso ≤ 2.4.

OkHttp的拦截器需要OkHttp 2.2及以上版本。不幸的是,拦截器不能与OkUrlFactory或基于它构建的库一起使用,包括Retrofit≤1.8和Picasso≤2.4。