http的緩存機制在flask的實現

http協議有一系列的緩存機制(RFC2616),相關的參數就在協議header中。緩存機制的合理使用可以大大減緩對服務器的壓力。

1 HTTP緩存頭的設置參數

HTTP緩存頭的參數包括:

  • Cache-Control(用于本地緩存)
  • Expires(用于本地緩存)
  • Last-Modified(協商緩存)
  • Etag(協商緩存)。

1.1 Cache-Control

指定請求和響應遵循的緩存機制。在請求消息或響應消息中設置Cache- Control并不會修改另一個消息處理過程中的緩 存處理過程。

  • 請求時的緩存指令包括no-cache、no-store、max-age、max-stale、min-fresh、only-if- cached;
  • 響應消息中的指令包括public、private、no-cache、no-store、no-transform、must- revalidate、proxy-revalidate、max-age。

1.2Expires

是一個絕對時間,作用與cache-control的max-age相類似,表示資源信息失效的時間。

1.3 Last-Modified

被訪問的資源的最近一次更改時間(http1.0)

1.4 ETag

資源的一個唯一標志(http1.1),通過這個標識,可以實現客戶端與服務端的協商機制。它的作用與Last-Modified是相類似的。

2 原理

是事實上,上述四者的實現機制上,可以歸納為:

  • cache-control/Expires:資源有效時間定義機制
  • Last-Modified/ETag:資源更新傳輸協商機制

2.1 cache-control/Expires:資源有效時間定義機制

最好的請求是不必與服務器進行通信的請求:通過響應的本地副本,我們可以避免所有的網絡延遲以及數據傳輸的數據成本。為此,HTTP 規范允許服務器返回 一系列不同的 Cache-Control 指令,控制瀏覽器或者其他中繼緩存如何緩存某個響應以及緩存多長時間。

cache-control控制緩存原理

一些參數的說明
  • no-cache和 no-store
    no-cache表示必須先與服務器確認返回的響應是否被更改,然后才能使用該響應來滿足后續對同一個網址的請求。因此,如果存在合適的驗證令牌 (ETag),no-cache 會發起往返通信來驗證緩存的響應,如果資源未被更改,可以避免下載。
    相比之下,no-store更加簡單,直接禁止瀏覽器和所有中繼緩存存儲返回的任何版本的響應 - 例如:一個包含個人隱私數據或銀行數據的響應。每次用戶請求該資源時,都會向服務器發送一個請求,每次都會下載完整的響應。
  • public和private
    如果響應被標記為public,即使有關聯的 HTTP 認證,甚至響應狀態碼無法正常緩存,響應也可以被緩存。大多數情況下,public不是必須的,因為明確的緩存信息(例如max-age)已表示 響應可以被緩存。
    相比之下,瀏覽器可以緩存private響應,但是通常只為單個用戶緩存,因此,不允許任何中繼緩存對其進行緩存 - 例如,用戶瀏覽器可以緩存包含用戶私人信息的 HTML 網頁,但是 CDN 不能緩存。
  • max-age
    該指令指定從當前請求開始,允許獲取的響應被重用的最長時間(單位為秒) - 例如:max-age=60表示響應可以再緩存和重用 60 秒。
  • 關于expires
    Cache-Control 頭在 HTTP/1.1 規范中定義,取代了之前用來定義響應緩存策略的頭(例如 Expires)。當前的所有瀏覽器都支持 Cache-Control,因此,使用它就夠了。
  • cache-control在請求端和服務端的命令相互獨立
    cache-control不會因為請求設置的值而修改響應設置,反之亦然。這里考慮到一種場景,響應頭中設置了一定的緩存時間,然而請求端仍然需要獲取最新結果,則將請求頭的緩存設置中加上“max-age=0”,則強制服務端響應這個請求。

2.2 Last-Modified/ETag:資源更新傳輸協商機制

Last-Modified

在瀏覽器第一次請求某一個URL時,服務器端的返回狀態會是200,內容是你請求的資源,同時有一個Last-Modified的屬性標記此文件在服務期端最后被修改的時間,格式類似這樣:

Last-Modified: Fri, 12 May 2006 18:53:33 GMT 

客戶端第二次請求此URL時,根據 HTTP 協議的規定,瀏覽器會向服務器傳送 If-Modified-Since 報頭,詢問該時間之后文件是否有被修改過:

If-Modified-Since: Fri, 12 May 2006 18:53:33 GMT

如果服務器端的資源沒有變化,則自動返回 HTTP 304 (Not Changed.)狀態碼,內容為空,這樣就節省了傳輸數據量。當服務器端代碼發生改變或者重啟服務器時,則重新發出資源,返回和第一次請求時類似。從而保證不向客戶端重復發出資源,也保證當服務器有變化時,客戶端能夠得到最新的資源。

ETag

HTTP 協議規格說明定義ETag為“被請求變量的實體值” 。另一種說法是,ETag是一個可以與Web資源關聯的記號(token)。典型的Web資源可以一個Web頁,但也可能是JSON或XML文檔。服務器單獨負責判斷記號是什么及其含義,并在HTTP響應頭中將其傳送到客戶端,以下是服務器端返回的格式:

ETag: "50b1c1d4f775c61:df3" 

客戶端的查詢更新格式是這樣的:

If-None-Match: W/"50b1c1d4f775c61:df3" 

如果ETag沒改變,則返回狀態304然后不返回,這也和Last-Modified一樣。

基于ETag驗證的緩存原理

2.3 一種緩存分級策略實例

  • max-age=86400
    瀏覽器和任何中繼緩存均可以將響應(如果是public的)緩存長達一天(60 秒 x 60 分 x 24 小時)
  • private, max-age=600
    客戶端瀏覽器只能將響應緩存最長 10 分鐘(60 秒 x 10 分)
  • no-cache
    通過ETag協商
  • no-store
    不允許緩存響應,每個請求必須獲取完整的響應。
    上面是一種緩存分級機制的栗子,可以根據資源的更新情況進行響應的配置。當然還可以有更多靈活配置。

3 基于flask實現

3.1 cache-control的flask實現

flask有一個擴展包解決這個問題:flask-cachecontrol。秉承flask的傳統,使用的方法十分簡單(看看代碼也好)。

from flask.ext.cachecontrol import (
    FlaskCacheControl,
    cache,
    cache_for,
    dont_cache)
flask_cache_control = FlaskCacheControl()
flask_cache_control.init_app(app)

@app.route('/')
@cache_for(hours=3)
def index_view():
    return render_template('index_template')

@app.route('/stats')
@cache(max_age=3600, public=True)
def stats_view():
    return render_template('stats_template')

@app.route('/dashboard')
@dont_cache()
def dashboard_view():
    return render_template('dashboard_template')

它簡化為了三個場景,將相關的配置都自動在響應包中添加。例如,采用cache(max_age=3, public=False)的修飾器,返回的緩存頭包括了幾個配置參數。

HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 1804
Cache-Control: proxy-revalidate, no-cache, no-store, must-revalidate, max-age=0
Server: Werkzeug/0.10.4 Python/2.7.10
Date: Fri, 05 Aug 2016 03:51:28 GMT

3.2 ETag/Last-Modified的flask實現

ETag沒有flask擴展包,這里有一篇官方的文章介紹實現方法。對方法總結一下:

  • 給flask.response.set_etag()做一個猴子補丁(Monkeypatching)。
  • 猴子補丁的內容為,校驗請求包的“IF-MATCH”與“IF-NONE-MATCH”信息(即請求包的ETag字段),如果不合法則直接返回錯誤;如果合法,則執行校驗etag,將本次新生成的etag碼與請求包中的“IF-NONE-MATCH”碼相匹配,則拋出“NotModified”異常(執行304狀態碼及空包返回),如果不匹配,則進行全數據返回且包含了新的ETag信息。
_old_set_etag = werkzeug.ETagResponseMixin.set_etag
@functools.wraps(werkzeug.ETagResponseMixin.set_etag)
def _new_set_etag(self, etag, weak=False):
    # only check the first time through; when called twice
    # we're modifying
    if (hasattr(flask.g, 'condtnl_etags_start') and
                               flask.g.condtnl_etags_start):
        if flask.request.method in ('PUT', 'DELETE', 'PATCH'):
            if not flask.request.if_match:
                raise PreconditionRequired
            if etag not in flask.request.if_match:
                flask.abort(412)
        elif (flask.request.method == 'GET' and
              flask.request.if_none_match and
              etag in flask.request.if_none_match):
            raise NotModified
        flask.g.condtnl_etags_start = False
    _old_set_etag(self, etag, weak)
werkzeug.ETagResponseMixin.set_etag = _new_set_etag
  • 校驗ETag的行為在API的響應代碼中執行。
app = flask.Flask(__name__)
d = {'a': 'This is "a".\n', 'b': 'This is "b".\n'}
@app.route('/<path>',
           methods = ['GET', 'PUT', 'DELETE', 'PATCH'])
@conditional
def view(path):
    try:
        # SHA1 should generate well-behaved etags
        etag = hashlib.sha1(d[path]).hexdigest()
        if flask.request.method == 'GET':
            response = flask.make_response(d[path])
            response.set_etag(etag)
        else:
            response = flask.Response(status=204)
            del response.headers['content-type']
            response.set_etag(etag)
            if flask.request.method == 'DELETE':
                del d[path]
                del response.headers['etag']
            else:
                if flask.request.method == 'PUT':
                    d[path] = flask.request.data
                else: # (PATCH)
                    # lame PATCH technique
                    d[path] += flask.request.data
             response.set_etag(hashlib.sha1(d[path])
                                      .hexdigest())
        return response
    except KeyError:
        flask.abort(404)
app.run()

4 總結一下

緩存是一個減緩服務端壓力的手段。對于一些很少改變的且不敏感的資源,可以用開放式緩存,讓CDN等中間環節也幫我們存信息。而對于一些少改變且稍為敏感的資源,則可以使用私有式緩存,讓客戶端瀏覽器執行緩存。甚至于更新很頻繁的還可設置為ETag校驗或者數據十分敏感,不能緩存的也有no-store機制。

采用ETag可能是比較折衷的辦法,在減緩帶寬壓力上十分有效,但在減緩服務器計算壓力(甚至數據庫壓力)上仍然沒有太大意義(ETag要求服務端先獲取了數據之后,再生成ETag,再用ETag與請求包的ETag驗證)。

參考:
https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
http://www.tuicool.com/articles/YBbeM33
https://github.com/twiebe/Flask-CacheControl
https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn
http://blog.csdn.net/salmonellavaccine/article/details/42734183
http://flask.pocoo.org/snippets/95/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,327評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,996評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,316評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,406評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,128評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,524評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,576評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,759評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,310評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,065評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,249評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,821評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,479評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,909評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,140評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,984評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,228評論 2 375

推薦閱讀更多精彩內容

  • 淺談瀏覽器Http的緩存機制 ? ? ? ? ? ? ? ? 針對瀏覽器的http緩存的分析也算是老生常談了,每隔...
    meng_philip123閱讀 1,030評論 0 10
  • 網絡特有的延遲以及數據傳輸的成本,制約互聯網快速獲取Web資源。為此,HTTP協議引入緩存以空間換時間,使瀏覽器緩...
    大頭8086閱讀 3,083評論 2 12
  • 針對瀏覽器的http緩存的分析也算是老生常談了,每隔一段時間就會冒出一篇不錯的文章,其原理也是各大公司面試時幾乎必...
    全端玩法閱讀 895評論 0 9
  • 針對瀏覽器的http緩存的分析也算是老生常談了,每隔一段時間就會冒出一篇不錯的文章,其原理也是各大公司面試時幾乎必...
    單純的土豆閱讀 400評論 0 2
  • 本文內容大多參考《圖解HTTP》一書 一. 認識代理服務器 所以講緩存為什么要先扯代理服務器?別急,讓我們看一下一...
    流光號船長閱讀 1,953評論 0 10