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阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 浅谈浏览器Http的缓存机制 ✦ ✦ ✦ ✦ ✦ ✦ ✦ ✦ 针对浏览器的http缓存的分析也算是老生常谈了,每隔...
    meng_philip123阅读 998评论 0 10
  • 网络特有的延迟以及数据传输的成本,制约互联网快速获取Web资源。为此,HTTP协议引入缓存以空间换时间,使浏览器缓...
    大头8086阅读 3,056评论 2 12
  • 针对浏览器的http缓存的分析也算是老生常谈了,每隔一段时间就会冒出一篇不错的文章,其原理也是各大公司面试时几乎必...
    全端玩法阅读 873评论 0 9
  • 针对浏览器的http缓存的分析也算是老生常谈了,每隔一段时间就会冒出一篇不错的文章,其原理也是各大公司面试时几乎必...
    单纯的土豆阅读 381评论 0 2
  • 本文内容大多参考《图解HTTP》一书 一. 认识代理服务器 所以讲缓存为什么要先扯代理服务器?别急,让我们看一下一...
    流光号船长阅读 1,898评论 0 10