网上大多数介绍都是对于flask中不带表单数据的GET请求的缓存,即一般的GET请求;而有时候,对于带有表单数据的GET请求进行缓存也是很有必要的,如对于特定的表单数据,GET请求返回的结果完全一样,这样加上缓存后效果会好很多。

但flask,即使是马上要用到的Flask-Cache扩展,也没有原生支持带表单数据的GET请求的缓存,需要自定义函数来实现。

Flask-Cache

Flask-Cache是比较常用的flask缓存扩展,这个扩展当然没有重写flask的cache部分,还是用到的werkzeug的Cache模块。

Flask-Cache安装很容易:

pip install Flask-Cache

其简单应用如下:

from flask import Flask
import time
from flask.ext.cache import Cache

app = Flask(__name__)
cache = Cache(app, config={'CACHE_TYPE': 'simple'})


@app.route('/')
@cache.cached()
def root():
    t = time.time()
    return str(t)

if __name__ == '__main__':
    app.debug = True
    app.run()

带表单数据的GET请求遇到的问题

使用默认cache选项:

from flask import Flask, request
from flask.ext.cache import Cache

app = Flask(__name__)
cache = Cache(app, config={'CACHE_TYPE': 'simple'})


@app.route('/')
@cache.cached()
def root():
    t = request.args['s']
    return t

if __name__ == '__main__':
    app.debug = True
    app.run()

这样当访问此app时:

http://127.0.0.1:5000/?s=aaa
http://127.0.0.1:5000/?s=bbb
http://127.0.0.1:5000/?s=ccc

3次访问获得的返回值将全都是aaa

原因在于使用cached()缓存时,默认情况下使用请求路径(request.path)作为cache_key,即缓存时忽略了GET请求后面带的表单数据,所以上述三次访问对于cached而言是完全一样的访问;所以只会将第一次访问结果缓存,而后面两次访问都直接返回缓存结果。

使用自定义函数解决

cached()提供了参数key_prefix来自定义cache_key,所以只需要自定义request.path加上了表单数据的值,作为cache_key就好了。

自定义函数:

def cache_key():
    args = request.args
    key = request.path + '?' + urllib.parse.urlencode([
        (k, v) for k in sorted(args) for v in sorted(args.getlist(k))
    ])
    return key

这样写比较复杂,但好处是即使表单数据顺序打乱,在转换为cache_key时也sorted()过的,不会出现冗余cache的情况

结合起来就是:

from flask import Flask, request
import time
from flask.ext.cache import Cache
import urllib.parse

app = Flask(__name__)
cache = Cache(app, config={'CACHE_TYPE': 'simple'})


def cache_key():
    args = request.args
    key = request.path + '?' + urllib.parse.urlencode([
        (k, v) for k in sorted(args) for v in sorted(args.getlist(k))
    ])
    return key


@app.route('/')
@cache.cached(key_prefix=cache_key)
def root():
    t = request.args['s']
    return t + '   ' + str(time.time())

if __name__ == '__main__':
    app.debug = True
    app.run()

这样就实现了带表单数据GET请求的缓存,至于timeout之类的自己再去调整咯。


另一种简单一些但可能产生冗余的自定义函数

如果能够控制GET表单数据的顺序,可以使用下面这个:

def cache_key():
    key = request.path + request.url.rsplit('/', 1)[-1]
    return key

甚至,如果能够确定请求不会产生干扰,可以考虑:

def cache_key():
    key = request.url.rsplit('/', 1)[-1]
    return key

使用文件系统缓存

注意到上述'CACHE_TYPE': 'simple',一般是作为测试环境下的缓存,实际生产环境下不要使用。

werkzeug支持的缓存类别有NullCacheSimpleCacheMemcachedCacheGAEMemcachedCacheRedisCacheFileSystemCache

其中FileSystemCache就是将每个cache作为文件缓存到磁盘,如果应对查询类app,且大多数查询速度明显不及访问磁盘,且服务器内存较小,不足以支持内存类缓存的话,可以考虑FileSystemCache

示例:

from flask import Flask, request
import time
from flask.ext.cache import Cache
import urllib.parse

app = Flask(__name__)
filesystem = {
    'CACHE_TYPE': 'filesystem',
    'CACHE_DIR': './flask_cache',
    'CACHE_DEFAULT_TIMEOUT': 922337203685477580,
    'CACHE_THRESHOLD': 922337203685477580
}
cache = Cache(app, config=filesystem)


def cache_key():
    args = request.args
    key = request.path + '?' + urllib.parse.urlencode([
        (k, v) for k in sorted(args) for v in sorted(args.getlist(k))
    ])
    return key


@app.route('/')
@cache.cached(key_prefix=cache_key)
def root():
    t = request.args['s']
    return t + '   ' + str(time.time())

if __name__ == '__main__':
    app.debug = True
    app.run()

关键点在于config中的配置,其中CACHE_DEFAULT_TIMEOUTCACHE_THRESHOLD设置的相当于无穷大,是因为每次GET请求查询返回的数据都是确定的,而且磁盘空间相对cache而言是无穷大,所以可以这样配置作为高速缓存。


参考:
Flask-Cache Flask-Cache 0.12 documentation
Cache Werkzeug Documentation (0.10)
Incoming Request Data API Flask Documentation (0.10)