阿里P7移動互聯網架構師進階視頻(每日更新中)免費學習請點擊:https://space.bilibili.com/474380680
本篇文章將繼續從以下兩個內容來解析OkHttp3源碼:
- [HTTP重定向]
- [緩存的處理]
一、HTTP重定向
1.1 重定向
重定向(Redirect)就是通過各種方法將各種網絡請求重新定個方向轉到其它位置(如:網頁重定向、域名的重定向、路由選擇的變化也是對數據報文經由路徑的一種重定向)。 ----百度百科
1.2 原理
在 HTTP 協議中,重定向操作由服務器通過發送特殊的響應(即 redirects)而觸發。HTTP 協議的重定向響應的狀態碼為 3xx 。瀏覽器在接收到重定向響應的時候,會采用該響應提供的新的 URL ,並立即進行加載;大多數情況下,除了會有一小部分性能損失之外,重定向操作對於用戶來說是不可見的。
image不同類型的重定向映射可以划分為三個類別:永久重定向、臨時重定向和特殊重定向。
1.3 永久重定向
這種重定向操作是永久性的。它表示原 URL 不應再被使用,而應該優先選用新的 URL。搜索引擎機器人會在遇到該狀態碼時觸發更新操作,在其索引庫中修改與該資源相關的 URL 。
注意了:瀏覽器自動重定向,不管代碼怎么寫都會自動重定向到第一次你永久重定向的URL,沒辦法,這時候你只能清楚瀏覽器緩存
編碼 | 含義 | 處理方法 | 典型應用場景 |
---|---|---|---|
301 | Moved Permanently | GET 方法不會發生變更,其他方法有可能會變更為 GET 方法。 | 網站重構。 |
308 | Permanent Redirect | 方法和消息主體都不發生變化。 | 網站重構。with non-GET links/operations(?) |
1.4 臨時重定向
有時候請求的資源無法從其標准地址訪問,但是卻可以從另外的地方訪問。在這種情況下可以使用臨時重定向。搜索引擎不會記錄該新的、臨時的鏈接。在創建、更新或者刪除資源的時候,臨時重定向也可以用於顯示臨時性的進度頁面。
那么臨時重定向就是解決自己傻乎乎的用永久重定向的問題。
編碼 | 含義 | 處理方法 | 典型應用場景 |
---|---|---|---|
302 | Found | GET 方法不會發生變更,其他方法有可能會變更為 GET 方法。 | 由於不可預見的原因該頁面暫不可用。在這種情況下,搜索引擎不會更新它們的鏈接。 |
303 | See Other | GET 方法不會發生變更,其他方法會變更為 GET 方法(消息主體會丟失)。 | 用於PUT 或 POST 請求完成之后進行頁面跳轉來防止由於頁面刷新導致的操作的重復觸發。 |
307 | Temporary Redirect | 方法和消息主體都不發生變化。 | 由於不可預見的原因該頁面暫不可用。在這種情況下,搜索引擎不會更新它們的鏈接。當站點支持非 GET 方法的鏈接或操作的時候,該狀態碼優於 302 狀態碼。 |
1.5 特殊重定向
除了上述兩種常見的重定向之外,還有兩種特殊的重定向。304 (Not Modified,資源未被修改)會使頁面跳轉到本地緩存的版本當中(該緩存已過期(?)),而 300 (Multiple Choice,多項選擇) 則是一種手工重定向:以 Web 頁面形式呈現在瀏覽器中的消息主體包含了一個可能的重定向鏈接的列表,用戶可以從中進行選擇。
編碼 | 含義 | 典型應用場景 |
---|---|---|
300 | Multiple Choice | 不會太多:所有的選項在消息主體的 HTML 頁面中列出。也可以返回 200 OK 狀態碼。 |
304 | Not Modified | 緩存刷新:該狀態碼表示緩存值依然有效,可以使用。 |
二、緩存處理
2.1 緩存的優勢
緩存的使用場景很多,通過它可以將數據通過一定的規則存儲起來,再次請求數據的時候就可以快速從緩存中讀取了,緩存有以下優勢。
- 減少向服務器請求的次數,減輕服務器的負載。
- 加快了本地的響應速度,直接從緩存中取數據比從網絡讀取要快很多。
- 提供無網模式下的瀏覽體驗,沒有網絡的情況下也能顯示內容。
2.2 HTTP的緩存機制
HTTP本身提供了一套緩存相關的機制。這套機制定義了相關的字段和規則,用來客戶端和服務端進行緩存相關的協商,如響應的數據是否需要緩存,緩存有效期,緩存是否有效,服務器端給出指示,而客戶端則根據服務端的指示做具體的緩存更新和讀取緩存工作。http緩存可以分為兩類:
強制緩存
強制緩存,是直接向緩存數據庫請求數據,如果找到了對應的緩存數據,並且是有效的,就直接返回緩存數據。如果沒有找到或失效了,則向服務器請求數據,返回數據和緩存規則,同時將數據和緩存規則保存到緩存數據庫中。
對比緩存
對比緩存,是先向緩存數據庫獲取緩存數據的標識,然后用該標識去服務器請求該標識對應的數據是否失效,如果沒有失效,服務器會返回304未失效響應,則客戶端使用該標識對應的緩存。如果失效了,服務器會返回最新的數據和緩存規則,客戶端使用返回的最新數據,同時將數據和緩存規則保存到緩存數據庫中。
強制緩存
強制緩存,在緩存數據未失效的情況下,可以直接使用緩存數據,有兩個字段Expires和Cache-Control用於標明失效規則。
Expires
表示過期時間,由服務端返回。那么下次請求數據時,判斷這個Expires過期時間是否已經過了,如果還沒有到過期時間,則使用緩存,如果過了過期時間,則重新請求服務器的數據。Expires格式如下:
Expires: Sat, 11 Nov 2017 10:30:01 GMT
表示到期時間是2017年11月11日10點30分,在這個時間之前可以使用緩存,過了這個時間就要重新請求服務器數據了。
不過因為服務器和客戶端的時間並不是同步的,用一個絕對時間作為過期的標記並不是很明智,所以HTTP1.1之后更多的是Cache-Control,它的控制更加靈活。
Cache-Control
表示緩存的控制,有服務端返回。它有以下幾個取值:
public
表示數據內容都可以被儲存起來,就連有密碼保護的網頁也儲存,安全性很低
private
表示數據內容只能被儲存到私有的cache,僅對某個用戶有效,不能共享
no-cache
表示可以緩存,但是只有在跟WEB服務器驗證了其有效后,才能返回給客戶端,觸發對比緩存
no-store
表示請求和響應都禁止被緩存,強制緩存,對比緩存都不會觸發
max-age
表示返回數據的過期時間
默認情況下是private,也就是不能共享的。Cache-Control格式如下:
Cache-Control:public, max-age=31536000
表示可以被公共緩存,有效時間是1年,也就是說一年時間內,請求該數據時,直接使用緩存,而不用請求服務器了。
對比緩存
對比緩存,表示需要和服務端進行相關信息的對比,由服務器決定是使用緩存還是最新內容,如果服務器判定使用緩存,返回響應嗎304,判定使用最新內容,則返回響應碼200和最新數據。對比緩存的判定字段有兩組:
ETag和If-None-Match
ETag表示資源的一種標識信息,用於標識某個資源,由服務端返回,優先級更高。格式如下:
Etag:"AFY10-6MddXmSerSiXP1ZTiU65VS"
表示該資源的標識是AFY10-6MddXmSerSiXP1ZTiU65VS
然后客戶端再次請求時,加入字段If-None-Match,格式如下:
If-None-Match:"AFY10-6MddXmSerSiXP1ZTiU65VS"
服務端收到請求的該字段時(之前的Etag值),和資源的唯一標識進行對比,如果相同,說明沒有改動,則返回狀態碼304,如果不同,說明資源被改過了,則返回狀態碼200和整個內容數據。
Last-Modified和If-Modified-Since
Last-Modified表示資源的最近修改時間,由服務端返回,優先級更低。格式如下:
Last-Modified: Sat, 11 Nov 2017 10:30:01 GMT
表示上次修改時間是2017年11月11日10點30分。
If-Modified-Since: Sat, 11 Nov 2017 10:30:01 GMT
客戶端請求,表示我指定的這個2017年11月11日10點30分是不是你服務器最新的修改時間。
Last-Modified
由服務器返回,表示響應的數據最近修改的時間。
If-Modified-Since
由客戶端請求,表示詢問服務器這個時間是不是上次修改的時間。如果服務端該資源的修改時間小於等於If-Modified-Since指定的時間,說明資源沒有改動,返回響應狀態碼304,可以使用緩存。如果服務端該資源的修改時間大於If-Modified-Since指定的時間,說明資源又有改動了,則返回響應狀態碼200和最新數據給客戶端,客戶端使用響應返回的最新數據。
Last-Modified字段的值(服務端返回的資源上次修改時間),常常被用於客戶端下次請求時的If-Modified-Since字段中。
兩種緩存的區別
強制緩存的情況下,如果緩存是有效的,則直接使用緩存,而對比緩存不管緩存是否有效,都需要先去和服務器對比是否有新的數據,沒有新的數據才使用緩存數據。
兩種緩存的使用情景
對於強制緩存,服務器通知瀏覽器一個緩存時間,在緩存時間內,下次請求,直接用緩存,不在時間內,執行對比緩存策略。
對於對比緩存,將緩存信息中的Etag和Last-Modified通過請求發送給服務器,由服務器校驗,返回304狀態碼時,瀏覽器直接使用緩存。
HTTP的緩存規則總結
HTTP的緩存規則是優先考慮強制緩存,然后考慮對比緩存。
- 首先判斷強制緩存中的數據的是否在有效期內。如果在有效期,則直接使用緩存。如果過了有效期,則進入對比緩存。
- 在對比緩存過程中,判斷ETag是否有變動,如果服務端返回沒有變動,說明資源未改變,使用緩存。如果有變動,判斷Last-Modified。
- 判斷Last-Modified,如果服務端對比資源的上次修改時間沒有變化,則使用緩存,否則重新請求服務端的數據,並作緩存工作。
2.3 Okhttp緩存相關類
Okhttp緩存相關的類有如下:
CacheControl(HTTP中的Cache-Control和Pragma緩存控制)
CacheControl是用於描述HTTP的Cache-Control和Pragma字段的類,用於指定緩存的規則。
CacheStrategy(緩存策略類)
CacheStrategy是用於判定使用緩存數據還是網絡請求的決策類。
Cache(緩存類)
對外開放的緩存類,提供了緩存的增刪改查接口。
InternalCache(內部緩存類)
對內使用的緩存類接口,沒有具體實現,只是封裝了Cache的使用。
DiskLruCache(文件化的LRU緩存類)
這是真正實現緩存功能的類,將數據存儲在文件中,並使用LRU規則(由LinkedHashMap實現),控制對緩存文件的增刪改查。
2.4 Okhttp緩存的啟用
要開啟使用Okhttp的緩存其實很簡單,只需要給OkHttpClient對象設置一個Cache對象即可,創建一個Cache時指定緩存保存的目錄和緩存最大的大小即可。
那么下面我們來看看Okhttp緩存執行的大概流程
2.5 Okhttp的緩存流程
Okhttp的緩存流程分為讀取緩存和存儲緩存兩個過程,我們分別分析。
Okhttp讀取緩存流程
讀取使用緩存的流程從HttpEngine的sendRequest發送請求開始。
- 首先獲取OkHttpClient的Cache緩存對象,就是之前創建OkHttpClient時設置的Cache。
- 然后傳入Request請求到Cache的get方法去查找緩存響應數據Response。
- 構造一個緩存策略,傳入Request請求和緩存響應Response,然后調用它的get方法去決策使用網絡請求還是緩存響應。
- 策略判定之后,如果是使用緩存,則它的cacheResponse不為空,networkRequest為空,如果使用請求,則相反。然后再將策略給出的這兩個值,繼續處理。
- 如果使用請求,但是之前又找到了緩存響應,則要關閉緩存響應資源。
- 如果策略得出緩存響應為空,網絡請求也為空,則返回請求不合理的響應。(比如強制使用緩存,但是找不到緩存的情況下)
- 如果請求為空,緩存不為空,也就是使用緩存的情況,則使用緩存響應來構造返回的響應數據。
- 最后就是只使用網絡請求的情況,走網絡請求路線。
總的來說就是,先查找是否有可用的Cache,然后通過Cache找到請求對應的緩存,然后將請求和緩存交給緩存策略去判斷使用請求還是緩存,得出結果后,自己再判斷使用緩存還是請求,如果使用緩存,用緩存構造響應直接返回,如果使用請求,那么開始網絡請求流程。
接下來我們分析
- Cache是如何獲取緩存的。
- 緩存策略是如何判斷的。
Cache獲取緩存
從Cache的get方法開始。它按以下步驟進行。
- 計算request對應的key值,md5加密請求url得到。
- 根據key值去DiskLruCache查找是否存在緩存內容。
- 存在緩存的話,創建緩存Entry實體。ENTRY_METADATA代表響應頭信息,ENTRY_BODY代表響應體信息。
- 然后根據緩存Entry實體得到響應,其中包含了緩存的響應頭和響應體信息。
- 匹配這個緩存響應和請求的信息是否匹配,不匹配的話要關閉資源,匹配的話返回。
如果存在緩存的話,在指定的緩存目錄中,會有兩個文件“****.0”和“****.1”,分別存儲某個請求緩存的響應頭和響應體信息。(“****”是url的md5加密值)對應的ENTRY_METADATA響應頭和ENTRY_BODY響應體。緩存的讀取其實是由DiskLruCache來讀取的,DiskLruCache是支持Lru(最近最少訪問)規則的用於磁盤存儲的類,對應LruCache內存存儲。它在存儲的內容超過指定值之后,就會根據最近最少訪問的規則,把最近最少訪問的數據移除,以達到總大小不超過限制的目的。
接下來我們分析CacheStrategy緩存策略是怎么判定的。
CacheStrategy緩存策略
直接看CacheStrategy的get方法。緩存策略是由請求和緩存響應共同決定的。
- 如果緩存響應為空,則緩存策略為不使用緩存。
- 如果請求是https但是緩存響應沒有握手信息,同上不使用緩存。
- 如果請求和緩存響應都是不可緩存的,同上不使用緩存。
- 如果請求是noCache,並且又包含If-Modified-Since或If-None-Match,同上不使用緩存。
- 然后計算請求有效時間是否符合響應的過期時間,如果響應在有效范圍內,則緩存策略使用緩存。
- 否則創建一個新的有條件的請求,返回有條件的緩存策略。
- 如果判定的緩存策略的網絡請求不為空,但是只使用緩存,則返回兩者都為空的緩存策略。
接來下我們看看CacheControl類里有些什么。
CacheControl
可以發現,它就是用於描述響應的緩存控制信息。
然后我們再看看Okhttp存儲緩存是怎么進行的。
Okhttp存儲緩存流程
存儲緩存的流程從HttpEngine的readResponse發送請求開始的。
可以看到這里先通過maybeCache寫入了響應頭信息,再通過cacheWritingResponse寫入了響應體信息。我們再進去看Cache的put方法實現。
我們繼續看Cache的writeTo方法,可以看到是寫入一些響應頭信息。
到這里Okhttp緩存的讀取和存儲流程我們就清楚了。可以說,緩存的使用策略基本都是按照HTTP的緩存定義來實現的,所以對HTTP緩存相關字段的理解是很重要的。然后關於DiskLruCache是如何管理緩存文件的,這個其實也很好理解,首先的原則就是按照LRU這種最近最少使用刪除的原則,當總的大小超過限定大小后,刪除最近最少使用的緩存文件,它的LRU算法是使用LinkedHashMap進行維護的,這樣來保證,保留的緩存文件都是更常使用的。具體實現大家可以分析DiskLruCache和LinkedHashMap的實現原理。
參考 https://www.jianshu.com/p/00d281c226f6
https://www.jianshu.com/p/3f9128380cf7
阿里P7移動互聯網架構師進階視頻(每日更新中)免費學習請點擊:https://space.bilibili.com/474380680