官方文檔:https://github.com/square/okhttp/wiki/Interceptors
攔截器是 OkHttp 提供的對 Http 請求和響應進行統一處理的強大機制,它能夠實現網絡監聽、請求以及響應重寫、請求失敗充實等功能。
OkHttp 中的 Interceptor 就是典型的責任鏈的實現,它能夠設置任意數量的 Intercepter 來對網絡請求及其響應作任何中間處理,好比設置緩存,Https證書認證,統一對請求加密/防篡改社會,打印log,過濾請求等等。html
下面是 OkHttp 官方的一個簡單的 Interceptor 示例,它記錄了要離開當前攔截器的 request 和 進入到當前攔截器的 responsenginx
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 使用列表來跟蹤攔截器,並按順序調用攔截器。git
OkHttp 中的攔截器分爲 Application Interceptor(應用攔截器) 和 NetWork Interceptor(網絡攔截器)兩種,下面以 LoggingInterceptor 爲例來展現這兩種註冊方式的區別:github
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();
上段代碼中的URL http://www.publicobject.com/helloworld.txt 被重定向到了 https://publicobject.com/helloworld.txt,OkHttp會自動跟隨此重定向。此時的應用攔截器會被調用一次,而且返回的 chain.proceed() 響應是重定向後的響應。web
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
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/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
很明顯的,網絡攔截器的請求包含了更多信息,好比 OkHttp 爲了減小數據的傳輸時間以及傳輸流量而自動添加的請求頭 Accept-Encoding:gzip 但願服務器能返回已壓縮過的響應數據。
網絡攔截器下的 Chain 具備一個非空的 Connection 對象,它能夠用來查詢客戶端所鏈接的服務器的IP地址以及TLS配置信息。
Chain的源碼中也說明了只有在網絡攔截器下的 chain 才能使用,而應用攔截器下的 chain.connection() 老是返回 null。緩存
public interface Interceptor { ... interface Chain { ... /** * 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(); ... } }
每種攔截器都有各自的優勢:
應用攔截器服務器
網絡攔截器網絡
攔截器能夠添加、移除和替換 request 的 headers 頭信息,它們還能夠轉換 request 的 body 請求體,好比可使用 application interceptor(應用攔截器)添加通過壓縮以後的請求主體,固然,這須要服務端也支持處理壓縮數據。app
/** This interceptor compresses the HTTP request body. Many webservers can't handle this! */ 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; // 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(); } }; } }
和重寫請求類似,攔截器能夠重寫響應頭而且能夠改變它的響應主體。相對於重寫請求而言,重寫響應一般是比較危險的一種作法,由於這種操做可能會改變服務端所要傳遞的響應內容的意圖。
固然,在不得已的狀況下,好比不處理的話的客戶端程序接受到此響應的話會Crash等,以及你還能夠保證解決重寫響應後可能出現的問題時,從新響應頭是一種很是有效的方式去解決這些致使項目Crash的問題。舉個栗子,你能夠修改服務器返回的錯誤的響應頭Cache-Control信息,去更好地自定義配置響應緩存保存時間。
/** Dangerous interceptor that rewrites the server's cache-control header. */ 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(); } };
不過一般最好的作法是在服務端修復這個問題。
注:Cache-Control 是 Http 通用首部字段,即 請求頭 和 響應頭 中均可包含該字段,可是 Cache-Control: max-age 在 request 和 response 中的意義不一樣。
request 中的 Cache-Control: max-age=0 表明強制要求服務器返回最新的文件內容
而 response 中的 Cache-Control: max-age=0 則表示服務器要求瀏覽器在使用本地緩存的時候,必須先和服務器進行一遍通訊,將etag,if-not-modified等字段信息傳遞給服務器以便驗證當前瀏覽器使用的文件是不是最新的。
若是瀏覽器使用的是最新的文件,http狀態碼返回304。(304狀態表示服務器端資源未改變,可直接使用客戶端未過時的緩存)
若是返回200,瀏覽器須要從新加載一次資源。
更詳細的請看:
https://stackoverflow.com/questions/1046966/whats-the-difference-between-cache-control-max-age-0-and-no-cache
參考其餘翻譯官方文檔: https://www.jianshu.com/p/fc4d4348dc58 https://www.jianshu.com/p/d04b463806c8