成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

聚焦http協(xié)議緩存策略(RFC7234)在okhttp中的實(shí)現(xiàn)

sanyang / 1562人閱讀

摘要:計(jì)算緩存從產(chǎn)生開(kāi)始到現(xiàn)在的年齡。響應(yīng)頭中的時(shí)間,文件在緩存服務(wù)器中存在的時(shí)間。響應(yīng)碼為表示經(jīng)服務(wù)器效驗(yàn)緩存的響應(yīng)式有效的可以使用,更新緩存年齡。小結(jié)通過(guò)本篇我們知道了協(xié)議的緩存策略,已經(jīng)在中是如何實(shí)踐的。

前言

分析基于okhttp v3.3.1

Okhttp處理緩存的類(lèi)主要是兩個(gè)CacheIntercepter緩存攔截器,以及CacheStrategy緩存策略。 CacheIntercepter在Response intercept(Chain chain)方法中先得到chain中的request然后在Cache獲取到Response,然后將Request和Respone交給創(chuàng)建CahceStrategy.Factory對(duì)象,在對(duì)象中得到CacheStrategy。代碼看的更清晰:

@Override public Response intercept(Chain chain) throws IOException { //cache中取Response對(duì)象cacheCandidate Response cacheCandidate = cache != null ");

1、 關(guān)于RFC7234在Okhttp中的實(shí)現(xiàn)

1.1、 獲取CacheStrategy緩存策略

看下CacheStrategy.Factory使用原始的Request和在緩存中得到的Response對(duì)象CacheCandidate,怎樣生成CacheStrategy的。 CacheStrategyFactory的生成

... public Factory(long nowMillis, Request request, Response cacheResponse) { //獲取當(dāng)前時(shí)間 this.nowMillis = nowMillis; this.request = request; this.cacheResponse = cacheResponse; if (cacheResponse != null) { this.sentRequestMillis = cacheResponse.sentRequestAtMillis(); this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis(); Headers headers = cacheResponse.headers(); for (int i = 0, size = headers.size(); i < size; i++) { String fieldName = headers.name(i); String value = headers.value(i); if ("Date".equalsIgnoreCase(fieldName)) { //取出緩存響應(yīng)當(dāng)時(shí)服務(wù)器的時(shí)間 servedDate = HttpDate.parse(value); servedDateString = value; } else if ("Expires".equalsIgnoreCase(fieldName)) { //取出過(guò)期時(shí)間 expires = HttpDate.parse(value); } else if ("Last-Modified".equalsIgnoreCase(fieldName)) { //取出最后一次更改時(shí)間 lastModified = HttpDate.parse(value); lastModifiedString = value; } else if ("ETag".equalsIgnoreCase(fieldName)) { //取出etag etag = value; } else if ("Age".equalsIgnoreCase(fieldName)) { // ageSeconds = HttpHeaders.parseSeconds(value, -1); } } } }

1.2、 CacheStrategy生成,緩存策略的生成。

緩存策略最終會(huì)產(chǎn)生三種策略中的一種:

直接使用緩存

不使用緩存

有條件的使用緩存

CacheStrategy中最后request為空表示可以使用緩存,如果Response為空表示不能使用緩存 如果都為空 說(shuō)明不能使用直接返回504

具體判斷

    判斷本地是否有cacheReponse 如果沒(méi)有直接返回new CacheStrategy(request, null)

    判斷https的handshake是否丟失 如果丟失直接返回 return new CacheStrategy(request, null)

    判斷response和request里的cache-controlheader的值如果有no-store直接返回 return new CacheStrategy(request, null);

    如果request的cache-contro 的值為no-cache或者請(qǐng)求字段有“If-Modified-Sine”或者“If-None—Match”(這個(gè)時(shí)候表示不能直接使用緩存了)直接返回 return new CacheStrategy(request, null); 5.判斷是否過(guò)期,過(guò)期就帶有條件的請(qǐng)求,未過(guò)期直接使用。

源碼上加了注釋

/** Returns a strategy to use assuming the request can use the network. */ private CacheStrategy getCandidate() { // 在緩存中沒(méi)有獲取到緩存 if (cacheResponse == null) { return new CacheStrategy(request, null); } // https不滿足的條件下不使用緩存 if (request.isHttps() && cacheResponse.handshake() == null) { return new CacheStrategy(request, null); } //Request和Resonse中不滿足緩存的條件 if (!isCacheable(cacheResponse, request)) { return new CacheStrategy(request, null); } //在header中存在著If-None-Match或者If-Modified-Since的header可以作為效驗(yàn),或者Cache-control的值為noCache表示客戶端使用緩存資源的前提必須要經(jīng)過(guò)服務(wù)器的效驗(yàn)。 CacheControl requestCaching = request.cacheControl(); if (requestCaching.noCache() || hasConditions(request)) { return new CacheStrategy(request, null); } //緩存的響應(yīng)式恒定不變的 CacheControl responseCaching = cacheResponse.cacheControl(); if (responseCaching.immutable()) { return new CacheStrategy(null, cacheResponse); } //計(jì)算響應(yīng)緩存的年齡 long ageMillis = cacheResponseAge(); //計(jì)算保鮮時(shí)間 long freshMillis = computeFreshnessLifetime(); //Request中保鮮年齡和CacheResponse中保鮮年齡取小 if (requestCaching.maxAgeSeconds() != -1) { freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds())); } //取Request的minFresh(他的含義是當(dāng)前的年齡加上這個(gè)日期是否還在保質(zhì)期內(nèi)) long minFreshMillis = 0; if (requestCaching.minFreshSeconds() != -1) { minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds()); } //當(dāng)緩存已經(jīng)過(guò)期且request表示能接受過(guò)期的響應(yīng),過(guò)期的時(shí)間的限定。 long maxStaleMillis = 0; if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) { maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds()); } //判斷緩存能否被使用 if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) { Response.Builder builder = cacheResponse.newBuilder(); //已經(jīng)過(guò)期但是能使用 if (ageMillis + minFreshMillis >= freshMillis) { builder.addHeader("Warning", "110 HttpURLConnection "Response is stale""); } //緩存的年齡已經(jīng)超過(guò)一天的時(shí)間 long oneDayMillis = 24 * 60 * 60 * 1000L; if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) { builder.addHeader("Warning", "113 HttpURLConnection "Heuristic expiration""); } return new CacheStrategy(null, builder.build()); } // Find a condition to add to the request. If the condition is satisfied, the response body // will not be transmitted. String conditionName; String conditionValue; if (etag != null) { conditionName = "If-None-Match"; conditionValue = etag; } else if (lastModified != null) { conditionName = "If-Modified-Since"; conditionValue = lastModifiedString; } else if (servedDate != null) { conditionName = "If-Modified-Since"; conditionValue = servedDateString; } else { return new CacheStrategy(request, null); // No condition! Make a regular request. } Headers.Builder conditionalRequestHeaders = request.headers().newBuilder(); Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue); Request conditionalRequest = request.newBuilder() .headers(conditionalRequestHeaders.build()) .build(); return new CacheStrategy(conditionalRequest, cacheResponse); }

1.2.1、 判斷是否過(guò)期

判斷是否過(guò)期的依據(jù):ageMillis + minFreshMillis < freshMillis + maxStaleMillis,(當(dāng)前緩存的年齡加上期望有效時(shí)間)小于(保鮮期加上過(guò)期但仍然有效期限)

特別的當(dāng)respone headercache-control:must-revalidate時(shí)表示不能使用過(guò)期的cache也就是maxStaleMillis=0。

//計(jì)算緩存從產(chǎn)生開(kāi)始到現(xiàn)在的年齡。 long ageMillis = cacheResponseAge(); //計(jì)算服務(wù)器指定的保鮮值 long freshMillis = computeFreshnessLifetime(); //請(qǐng)求和響應(yīng)的保鮮值取最小 if (requestCaching.maxAgeSeconds() != -1) { freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds())); } //期望在指定時(shí)間內(nèi)的響應(yīng)仍然有效,這是request的期望 long minFreshMillis = 0; if (requestCaching.minFreshSeconds() != -1) { minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds()); } //接受已經(jīng)過(guò)期的響應(yīng)時(shí)間, long maxStaleMillis = 0; if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) { maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds()); } //規(guī)則,緩存的年齡加上期望指定的有效時(shí)間期限小于實(shí)際的保鮮值加上過(guò)期時(shí)間 if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) { //雖然緩存能使用但是已經(jīng)過(guò)期了這時(shí)候要在header內(nèi)加提醒 Response.Builder builder = cacheResponse.newBuilder(); if (ageMillis + minFreshMillis >= freshMillis) { builder.addHeader("Warning", "110 HttpURLConnection "Response is stale""); } //緩存已經(jīng)超過(guò)一天了雖然還沒(méi)有過(guò)期加提醒 long oneDayMillis = 24 * 60 * 60 * 1000L; if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) { builder.addHeader("Warning", "113 HttpURLConnection "Heuristic expiration""); } return new CacheStrategy(null, builder.build()); }

1.2.2、計(jì)算緩存從產(chǎn)生開(kāi)始到現(xiàn)在的年齡(RFC 7234)

確定緩存的年齡的參數(shù)是一下四個(gè)。

發(fā)起請(qǐng)求時(shí)間:sentRequestMillis。

接收響應(yīng)時(shí)間:receivedResponseMillis。

當(dāng)前時(shí)間:nowMillis。

響應(yīng)頭中的age時(shí)間:ageSeconds,文件在緩存服務(wù)器中存在的時(shí)間。

根據(jù)RFC 7234計(jì)算的算法如下。

private long cacheResponseAge() { //接收到的時(shí)間減去資源在服務(wù)器端產(chǎn)生的時(shí)間得到apparentReceivedAge long apparentReceivedAge = servedDate != null ");

1.2.3、計(jì)算CacheRespone中的保鮮期

如果在CacheResone的cacheContro中獲取maxAgeSecends就是保鮮器

否則,就嘗試在expires header中獲取值減去服務(wù)器響應(yīng)的時(shí)間就是保鮮期

否在,服務(wù)器時(shí)間減去lastmodifide的時(shí)間的十分之一做為保鮮期。

private long computeFreshnessLifetime() { //cacheRespone中的control CacheControl responseCaching = cacheResponse.cacheControl(); if (responseCaching.maxAgeSeconds() != -1) { //如果cacheContro中存在maxAgeSecends直接使用 return SECONDS.toMillis(responseCaching.maxAgeSeconds()); } else if (expires != null) { //如果沒(méi)有max-age就使用過(guò)期時(shí)間減去服務(wù)器產(chǎn)生時(shí)間 long servedMillis = servedDate != null ");

2、對(duì)響應(yīng)CacheStrategy的處理

當(dāng)networkRequest為空且cahceResponse為空的時(shí)候,表示可以使用緩存且現(xiàn)在的緩存不可用,返回504。responecode 504的語(yǔ)義:但是沒(méi)有及時(shí)從上游服務(wù)器收到請(qǐng)求。

當(dāng)networRequest為空且cacheResponse不為空表示,可以使用緩存且緩存可用,就可以直接返回緩存response了。

當(dāng)networkRequest為空的時(shí)候,表示需要和服務(wù)器進(jìn)行校驗(yàn),或者直接去請(qǐng)求服務(wù)器。

響應(yīng)碼為304表示經(jīng)服務(wù)器效驗(yàn)緩存的響應(yīng)式有效的可以使用,更新緩存年齡。

響應(yīng)碼不為304更新緩存,返回響應(yīng);且有可能響應(yīng)式不可用的,返回body為空,header有信息的respone。

@Override public Response intercept(Chain chain) throws IOException { Response cacheCandidate = cache != null ");

3、解惑

3.1、 ETAG和if-Modified-Sine什么時(shí)候生效

在CacheSategry里從respone中獲取的,如果存在Etag或者M(jìn)odify的字段就只用在ConditionalRequet設(shè)置對(duì)應(yīng)的值做請(qǐng)求了,帶有條件的請(qǐng)求,去服務(wù)器驗(yàn)證。

3.2、Request中的no-cache的語(yǔ)義以及和no-store的區(qū)別

nocache的意思是不使用不可靠的緩存響應(yīng),必須經(jīng)過(guò)服務(wù)器驗(yàn)證的才能使用

CacheStrategy#Factory#getCandidate()中

CacheControl requestCaching = request.cacheControl(); if (requestCaching.noCache() || hasConditions(request)) { return new CacheStrategy(request, null); }

在CacheStrategy中的判斷邏輯是:當(dāng)request中請(qǐng)求頭中cache-control的值為no-cache的時(shí)候或者請(qǐng)求頭中存在if-none-match或者if-modified-sine的時(shí)候直接去請(qǐng)求服務(wù)放棄緩存。

3.3、 什么條件下會(huì)使用緩存呢

當(dāng)緩存可用,沒(méi)有超過(guò)有效期,且不需要經(jīng)過(guò)驗(yàn)證的時(shí)候可以直接從緩存中獲取出來(lái)。

需要經(jīng)過(guò)驗(yàn)證,經(jīng)過(guò)服務(wù)端驗(yàn)證,響應(yīng)碼為304表示緩存有效可以使用。

3.4、 must-revalidate 、no-cache、max-age = 0區(qū)別

must-revalidate(響應(yīng)中cacheContorl的值)
如果過(guò)期了就必須去服務(wù)器做驗(yàn)證不能夠使用過(guò)期的資源,這標(biāo)志了max-StaleSe失效

no-cache 只能使用源服務(wù)效驗(yàn)過(guò)的respone,不能使用未經(jīng)效驗(yàn)的respone。

根據(jù)Okhttp代碼中處理的策略是在request中cache-control為no-cache的時(shí)候,就直接去服務(wù)端去請(qǐng)求,如果需要添加額外的條件需要自己手動(dòng)去添加。

在respone中cache-control的條件為no-cache的時(shí)候表示客戶端使用cache的時(shí)候需要經(jīng)過(guò)服務(wù)器的驗(yàn)證,使用If-None-Match或者If-Modified-Since。

max-age=0表示保質(zhì)期為0,表示過(guò)了保鮮期,也就是需要去驗(yàn)證了。

4、小結(jié)

通過(guò)本篇我們知道了http協(xié)議的緩存策略,已經(jīng)在okhttp中是如何實(shí)踐的??偟膩?lái)說(shuō)會(huì)有這樣幾個(gè)步驟

判斷是否符合使用緩存的條件,是否有響應(yīng)緩存,根據(jù)cache-contorl字段緩存是否可用,是否過(guò)期。

如果需要服務(wù)器端進(jìn)行驗(yàn)證,主要是兩種方式request的header中 if-none-match:etag和If-Modified-Since:時(shí)間戳。

響應(yīng)碼304表示驗(yàn)證通過(guò),非304表示緩存不可用更新本地緩存。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/6709.html

相關(guān)文章

  • 前端開(kāi)發(fā)中常見(jiàn)的 HTTP 相關(guān)知識(shí)(一)

    摘要:前言前端開(kāi)發(fā)中總是要和接口和緩存打交道,所以相關(guān)內(nèi)容多多少少還是要知道一些,干起活來(lái)才能事半功倍。處于中的應(yīng)用層。部分安全性問(wèn)題發(fā)布于年的版本,也是當(dāng)前的最新標(biāo)準(zhǔn)?;诠雀杼岢龅亩鴣?lái),之前用于瀏覽器中來(lái)訪問(wèn)的加密服務(wù),在發(fā)布后功成身退。 前言 前端開(kāi)發(fā)中總是要和接口和緩存打交道,所以HTTP相關(guān)內(nèi)容多多少少還是要知道一些,干起活來(lái)才能事半功倍。下面我從業(yè)務(wù)出發(fā),簡(jiǎn)單說(shuō)下一些可能會(huì)碰到的...

    劉東 評(píng)論0 收藏0
  • 重學(xué)前端學(xué)習(xí)筆記(十一)--瀏覽器工作解析(一)

    摘要:緊跟在后面的是請(qǐng)求頭,每行用冒號(hào)分隔名稱和值按下兩次回車(chē),收到服務(wù)端回復(fù)響應(yīng)部分第一行被稱作,它也分為三個(gè)部分,協(xié)議和版本狀態(tài)碼和狀態(tài)文本。對(duì)前端來(lái)說(shuō)系列的狀態(tài)碼是非常陌生的,原因是的狀態(tài)被瀏覽器庫(kù)直接處理掉了,不會(huì)讓上層應(yīng)用知曉。 筆記說(shuō)明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時(shí)間開(kāi)的一個(gè)專(zhuān)欄,每天10分鐘,重構(gòu)你的前端知識(shí)體系,筆者主要整理學(xué)習(xí)過(guò)程的一些要...

    paulli3 評(píng)論0 收藏0
  • 重學(xué)前端學(xué)習(xí)筆記(十一)--瀏覽器工作解析(一)

    摘要:緊跟在后面的是請(qǐng)求頭,每行用冒號(hào)分隔名稱和值按下兩次回車(chē),收到服務(wù)端回復(fù)響應(yīng)部分第一行被稱作,它也分為三個(gè)部分,協(xié)議和版本狀態(tài)碼和狀態(tài)文本。對(duì)前端來(lái)說(shuō)系列的狀態(tài)碼是非常陌生的,原因是的狀態(tài)被瀏覽器庫(kù)直接處理掉了,不會(huì)讓上層應(yīng)用知曉。 筆記說(shuō)明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時(shí)間開(kāi)的一個(gè)專(zhuān)欄,每天10分鐘,重構(gòu)你的前端知識(shí)體系,筆者主要整理學(xué)習(xí)過(guò)程的一些要...

    付永剛 評(píng)論0 收藏0
  • 重學(xué)前端學(xué)習(xí)筆記(十一)--瀏覽器工作解析(一)

    摘要:緊跟在后面的是請(qǐng)求頭,每行用冒號(hào)分隔名稱和值按下兩次回車(chē),收到服務(wù)端回復(fù)響應(yīng)部分第一行被稱作,它也分為三個(gè)部分,協(xié)議和版本狀態(tài)碼和狀態(tài)文本。對(duì)前端來(lái)說(shuō)系列的狀態(tài)碼是非常陌生的,原因是的狀態(tài)被瀏覽器庫(kù)直接處理掉了,不會(huì)讓上層應(yīng)用知曉。 筆記說(shuō)明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時(shí)間開(kāi)的一個(gè)專(zhuān)欄,每天10分鐘,重構(gòu)你的前端知識(shí)體系,筆者主要整理學(xué)習(xí)過(guò)程的一些要...

    alighters 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<