深入剖析OkHttp系列(三) 來自官方的攔截器設計(中英互譯)

亓春傑發表於2018-08-15

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。

相關文章