OkHttp 3.x 源碼解析之Interceptor 攔截器


攔截器

Java里的攔截器是動態攔截Action調用的對象。它提供了一種機制可以使開發者可以定義在一個action執行的前后執行的代碼,也可以在一個action執行前阻止其執行,同時也提供了一種可以提取action中可重用部分的方式。
在AOP(Aspect-Oriented Programming)中攔截器用於在某個方法或字段被訪問之前,進行攔截然后在之前或之后加入某些操作。

過濾器

過濾器可以簡單理解為“取你所想取”,忽視掉那些你不想要的東西;攔截器可以簡單理解為“拒你所想拒”,關心你想要拒絕掉哪些東西,比如一個BBS論壇上攔截掉敏感詞匯。

1.攔截器是基於java反射機制的,而過濾器是基於函數回調的。
2.過濾器依賴於servlet容器,而攔截器不依賴於servlet容器。
3.攔截器只對action起作用,而過濾器幾乎可以對所有請求起作用。
4.攔截器可以訪問action上下文、值棧里的對象,而過濾器不能。
5.在action的生命周期里,攔截器可以多起調用,而過濾器只能在容器初始化時調用一次。
Android里面過濾器大家用的已經無法再陌生了,Filter就是一個很好的列子,在清單文件注冊Filter就可以過濾啟動摸某個組件的Action.

Okhttp攔截器因此應運而生,處理一次網絡調用的Action攔截,做修改操作。

 

OKHTTP INTERCEPTOR

使用

okhttp攔截器用法很簡單,構建OkHttpClient時候通過.addInterceptor()就可以將攔截器加入到一次會話中。

OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
1
2
3
1
2
3
攔截器

攔截器是Okhttp一種強大的機制,可以監視,重寫和重試每一次請求。下面示列了一個簡單的攔截器,用於記錄傳出的請求和傳入的響應。

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;
}
}
呼叫chain.proceed(request)是每個攔截器實現的關鍵部分。這個簡單的方法是所有HTTP工作發生的地方,產生滿足請求的響應。

攔截器可以鏈接。假設您同時擁有一個壓縮攔截器和一個校驗和攔截器:您需要確定數據是否已壓縮,然后進行校驗和,或校驗和然后壓縮。OkHttp使用列表來跟蹤攔截器,攔截器按順序調用。

應用攔截器

攔截器被注冊為應用程序或網絡攔截器。我們將使用LoggingInterceptor上面定義來顯示差異。

注冊一個應用程序通過調用攔截器addInterceptor()上OkHttpClient.Builder:

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();
URLhttp://www.publicobject.com/helloworld.txt重定向到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
我們可以看到,我們被重定向是因為response.request().url()不同request.url()。兩個日志語句記錄兩個不同的URL。

網絡攔截器

注冊網絡攔截器是非常相似的。調用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();

當我們運行這個代碼時,攔截器運行兩次。一次為初始請求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/ www.tengxun10001.cn 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
網絡請求還包含更多的數據,例如Accept-Encoding: gzip由OkHttp添加的標題來廣播支持響應壓縮。網絡攔截器Chain具有非空值Connection,可用於詢問用於連接到Web服務器的IP地址和TLS配置。

在應用攔截器和網絡攔截器之間如何讓進行選擇?

每個攔截鏈有相對優點。

應用攔截器

不需要擔心中間響應,如重定向和重試。
總是調用一次,即使從緩存提供HTTP響應。
遵守應用程序的原始意圖。不注意OkHttp注入的頭像If-None-Match。
允許短路和不通話Chain.proceed()。
允許重試並進行多次呼叫Chain.proceed()。
網絡攔截器

能夠對重定向和重試等中間響應進行操作。
不調用緩存的響應來短路網絡。
觀察數據,就像通過網絡傳輸一樣。
訪問Connection該請求。
重寫請求

攔截器可以添加,刪除或替換請求頭。還可以轉換具有一個請求的正文。例如,如果連接到已知支持它的Web服務器,則可以使用應用程序攔截器添加請求體壓縮。

final class GzipRequestInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null |www.jnd3658.cn| 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;
// We don't know the compressed length in advance!
}

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

對稱地攔截器可以重寫響應頭並轉換響應體。這通常比重寫請求頭更危險,因為他可以篡改違反了網絡服務器的數據的本意!

如果在棘手的情況,並准備應對后果,重寫響應標頭是解決問題的有效方式。例如,可以修復服務器配置錯誤的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(www.vboyl130.cn)
.header("Cache-Control", "max-age=60")
.build();
}
};
通常,這種方法在補充Web服務器上的相應修復程序時效果最好!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM