CherryPy - 缓存静态文件

use*_*610 1 python caching cherrypy

我有一个服务器,提供大量的静态内容.只要支持gzip内容,就可以使用CherryPy工具tools.gzip压缩文件.

问题:CherryPy是否在每次请求时都会压缩静态文件,或者是否将内容gzip一次并将该gzip压缩文件提供给所有请求?

如果CherryPy目前每次请求时都会对文件进行压缩,那么启用tools.caching会阻止它,还是有更好的方法?

saa*_*aaj 7

首先,我想指出,尽管由于其广泛的广泛传播和每种语言的良好客户端库的存在而导致HTTP看似简单,但HTTP实际上是涉及多个交互层的复杂协议.缓存也不例外,RFC 2616第13节.以下是关于Last-Modified/的说法If-Modified-Since,因为ETag使用gzip是另一个故事.

建立

#!/usr/bin/env python
# -*- coding: utf-8 -*-


import os

import cherrypy


path   = os.path.abspath(os.path.dirname(__file__))
config = {
  'global' : {
    'server.socket_host' : '127.0.0.1',
    'server.socket_port' : 8080,
    'server.thread_pool' : 8
  },
  '/static' : {
    'tools.gzip.on'       : True,
    'tools.staticdir.on'  : True,
    'tools.staticdir.dir' : os.path.join(path, 'static')
  }
}


if __name__ == '__main__':
  cherrypy.quickstart(config = config)
Run Code Online (Sandbox Code Playgroud)

然后在static目录中放入一些纯文本或HTML文件.

实验

Firefox和Chromium不会在第一次请求时发送缓存相关的标头,即GET /static/some.html:

Accept-Encoding: gzip, deflate
Host: 127.0.0.1:8080
Run Code Online (Sandbox Code Playgroud)

响应:

Accept-Ranges: bytes
Content-Encoding: gzip
Content-Length: 50950
Content-Type: text/html
Date: Mon, 15 Dec 2014 12:32:40 GMT
Last-Modified: Wed, 22 Jan 2014 09:22:27 GMT
Server: CherryPy/3.6.0
Vary: Accept-Encoding
Run Code Online (Sandbox Code Playgroud)

在后续请求中,使用以下缓存信息(Firebug)可以避免任何网络连接:

Data Size: 50950
Device: disk
Expires: Sat Jan 17 2015 05:39:41 GMT
Fetch Count: 6
Last Fetched: Mon Dec 15 2014 13:19:45 GMT
Last Modified: Mon Dec 15 2014 13:19:44 GMT
Run Code Online (Sandbox Code Playgroud)

因为默认情况下CherryPy不提供到期时间(ExpiresCache-Control),Firefox(可能也是Chromium)根据RFC 2616第13.2.4节使用启发式:

如果响应中没有Expires,Cache-Control:max-age或Cache-Control:s-maxage(请参阅第14.9.3节),并且响应不包含对缓存的其他限制,则缓存可以计算新鲜度终身使用启发式......

此外,如果响应确实具有Last-Modified时间,则启发式到期值应该不超过自该时间以来的某个时间间隔.此分数的典型设置可能是10%.

这是证明Expires值的启发式性质的代码:

import email.utils
import datetime

s  = 'Wed, 22 Jan 2014 09:22:27 GMT'
lm = datetime.datetime(*email.utils.parsedate(s)[0:6])

print datetime.datetime.utcnow() + (datetime.datetime.utcnow() - lm) / 10
Run Code Online (Sandbox Code Playgroud)

刷新页面时,浏览器会附加Cache-Control到请求:

Accept-Encoding: gzip,deflate
Cache-Control: max-age=0
Host: 127.0.0.1:8080
If-Modified-Since: Wed, 22 Jan 2014 09:22:27 GMT
Run Code Online (Sandbox Code Playgroud)

如果文件尚未更改,CherryPy会回复304 Not Modified.以下是它的工作原理:

cherrypy.lib.static.serve_file

def serve_file(path, content_type=None, disposition=None, name=None, debug=False):
    # ...

    try:
        st = os.stat(path)
    except OSError:
        if debug:
            cherrypy.log('os.stat(%r) failed' % path, 'TOOLS.STATIC')
        raise cherrypy.NotFound()

    # ...

    # Set the Last-Modified response header, so that
    # modified-since validation code can work.
    response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime)
    cptools.validate_since()

    # ...
Run Code Online (Sandbox Code Playgroud)

cherrypy.lib.cptools.validate_since

def validate_since():
    """Validate the current Last-Modified against If-Modified-Since headers.

    If no code has set the Last-Modified response header, then no validation
    will be performed.
    """
    response = cherrypy.serving.response
    lastmod = response.headers.get('Last-Modified')
    if lastmod:  
        # ...

        since = request.headers.get('If-Modified-Since')
        if since and since == lastmod:
            if (status >= 200 and status <= 299) or status == 304:
                if request.method in ("GET", "HEAD"):
                    raise cherrypy.HTTPRedirect([], 304)
                else:
                    raise cherrypy.HTTPError(412)
Run Code Online (Sandbox Code Playgroud)

包起来

tools.staticdir对于带有有效If-Modified-Since标头的请求,使用CherryPy不会发送文件内容,也不会gzips它们,但只响应304 Not Modified要求文件系统修改时间.如果没有页面刷新,它甚至不会收到请求,因为当服务器没有提供过期时,浏览器会使用启发式过期时间.当然,使您的配置更具确定性,提供缓存生存时间不会受到影响,例如:

'/static' : {
  'tools.gzip.on'       : True,
  'tools.staticdir.on'  : True,
  'tools.staticdir.dir' : os.path.join(path, 'static'),
  'tools.expires.on'    : True,
  'tools.expires.secs'  : 3600 # expire in an hour
}
Run Code Online (Sandbox Code Playgroud)