ETag 支持#

CacheControl 对 ETag 的支持与 httplib2 略有不同。在 httplib2 中,当缓存被认为已过期时,使用缓存响应时会考虑 ETag。当缓存响应已过期且具有 ETag 头时,httplib2 会使用适当的 If-None-Match 头发出下一个请求。我们将这种行为称为时间优先级缓存,因为只有在时间过期时,ETag 支持才会生效。

在 CacheControl 中,当服务器发送 ETag 时的默认行为是缓存响应。我们将此模式称为相等优先级缓存,因为缓存的决定是基于时间还是 ETag 的存在。

该规范没有明确说明在使用 ETag 和基于时间的头进行缓存时,哪个优先。因此,CacheControl 在可能的情况下通过配置支持不同的机制。

关闭相等优先级缓存#

相等优先级缓存的危险在于,为每个请求返回 ETag 头的服务器可能会填满你的缓存。你可以禁用相等优先级缓存,并像 httplib2 一样使用时间优先级算法。

import requests
from cachecontrol import CacheControl

sess = CacheControl(requests.Session(), cache_etags=False)

这将仅在基于时间的缓存头中存在 ETag 时使用 ETag。如果响应具有有效的基于时间的缓存头和 ETag,我们仍将尝试处理 304 未修改,即使缓存值已过期。这是一个简单的示例。

# Server response
GET /foo.html
Date: Tue, 26 Nov 2013 00:50:49 GMT
Cache-Control: max-age=3000
ETag: JAsUYM8K

在后续请求中,如果缓存已过期,下一个请求仍将包含 If-None-Match 头。缓存响应将保留在缓存中,等待响应。

# Client request
GET /foo.html
If-None-Match: JAsUYM8K

如果服务器返回 304 未修改,它将使用过期的缓存值,从最近的请求中更新头。

# Server response
GET /foo.html
Date: Tue, 26 Nov 2013 01:30:19 GMT
Cache-Control: max-age=3000
ETag: JAsUYM8K

如果服务器返回 200 确定,缓存将相应更新。

相等优先级缓存的好处#

相等优先级缓存的好处是,你有两种正交的方式来引入缓存。基于时间的缓存提供了一种有效的方法来减少最终一致的请求的负载。静态资源是基于时间的缓存有效的一个很好的例子。

基于 ETag 的缓存适用于处理较大且/或需要在更改后立即更正的文档。例如,如果你从大型数据库中导出了一些数据,该文件可能为 10 GB。能够使用此类请求发送 ETag 并知道你本地的版本有效,可以节省大量带宽和时间。

同样,如果你有一个想要更新的资源,你可以确信不会有 丢失更新,因为你有一个过时的本地版本。

端点特定缓存#

应该指出的是,有时端点是专门针对不同的缓存技术定制的。如果你有一个 RESTful 服务,可能有一些端点专门用于通过基于时间的缓存技术进行缓存,而其他端点应该专注于使用 ETag。在这种情况下,建议你直接使用 CacheControlAdapter

import requests
from cachecontrol import CacheControlAdapter
from cachecontrol.caches import RedisCache

# using django for an idea on where you might get a
# username/password.
from django.conf import settings

# a function to return a redis connection all the instances of the
# app may use. this allows updates to the API (ie PUT) to invalidate
# the cache for other users.
from myapp.db import redis_connection


# create our session
client = sess.Session(auth=(settings.user, settings.password))

# we have a gettext like endpoint. this doesn't get updated very
# often so a time based cache is a helpful way to reduce many small
# requests.
client.mount('http://myapi.foo.com/gettext/',
             CacheControlAdapter(cache_etags=False))


# here we have user profile endpoint that lets us update information
# about users. we need this to be consistent immediately after a user
# updates some information because another node might handle the
# request. It uses the global redis cache to coordinate the cache and
# uses the equal priority caching to be sure etags are used by default.
redis_cache = RedisCache(redis_connection())
client.mount('http://myapi.foo.com/user_profiles/',
             CacheControlAdapter(cache=redis_cache))

希望这个更深入的示例揭示了如何配置 requests.Session 以更好地利用基于 ETag 的缓存与基于时间优先级的缓存。