HTTP clients can send a number of headers to tell the server about copies of a
resource that they have already seen. This is commonly used when retrieving a
web page (using an HTTP GET
request) to avoid sending all the data for
something the client has already retrieved. However, the same headers can be
used for all HTTP methods (POST
, PUT
, DELETE
, etc.).
针对每个从 Django 视图返回的页面(响应),它可能提供了两种HTTP headers:ETag
header 和 Last-Modified
header。这些 headers 是 HTTP 的可选项。他们可以在视图函数里设置,或者依赖 ConditionalGetMiddleware
中间件来设置 ETag
header 。
当客户端下一次请求相同资源时,它可能会发送header,如 If-modified-since 或 If-unmodified-since ,包含它发送后最后一次修改时间,或者 If-match 或者 If-none-match ,包含它发送的最后一个 ETag
。如果页面的当前版本与客户端发送的 ETag
匹配,或者如果资源没有被修改,那么就会返回一个304状态,告诉客户端:“资源没有任何变化”,而不是返回所有资源响应。根据这个 header,如果页面发生了修改或者客户端没有匹配 ETag
,会返回 412 状态码。
当你需要更多的控制,你可以使用针对每个视图的条件处理函数。
Sometimes (in fact, quite often) you can create functions to rapidly compute the ETag value or the last-modified time for a resource, without needing to do all the computations needed to construct the full view. Django can then use these functions to provide an "early bailout" option for the view processing. Telling the client that the content has not been modified since the last request, perhaps.
这两个函数被当做参数传递到 django.views.decorators.http.condition
装饰器。这个装饰器使用两个函数(你只需要支持其中一个,如果你不能很快计算这两个数量)来判断 HTTP 请求的 headers 和这些资源是否匹配。如果它们没有匹配,会计算一份资源的副本,并调用视图。
条件装饰器如下:
condition(etag_func=None, last_modified_func=None)
The two functions, to compute the ETag and the last modified time, will be
passed the incoming request
object and the same parameters, in the same
order, as the view function they are helping to wrap. The function passed
last_modified_func
should return a standard datetime value specifying the
last time the resource was modified, or None
if the resource doesn't
exist. The function passed to the etag
decorator should return a string
representing the ETag for the resource, or None
if it doesn't exist.
如果它们没有通过视图设置并且请求的方法是安全的(GET
和 HEAD
),那么装饰器就会在请求上设置 ETag
和 Last-Modified
headers 。
用一个例子来解释如何有效地使用这个功能。假设你已经有了这些模型,代表一个博客系统:
import datetime
from django.db import models
class Blog(models.Model):
...
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
published = models.DateTimeField(default=datetime.datetime.now)
...
如果在首页正在显示最新的博客文章,只会在有新博客文章时改变,你可以很快地计算最后修改时间。你需要与该博客关联的每篇文章的最新发布时间。一种方法是这样做的:
def latest_entry(request, blog_id):
return Entry.objects.filter(blog=blog_id).latest("published").published
然后你可以使用这个函数预先为首页视图提供未变动页面的检测:
from django.views.decorators.http import condition
@condition(last_modified_func=latest_entry)
def front_page(request, blog_id):
...
小心装饰器的顺序
当 condition()
返回一个条件响应,在其下面的任何装饰器将被忽略并不会应用于响应。因此,任何需要同时应用于常规视图响应和条件响应的装饰器必须在 condition()
上面。特别是,vary_on_cookie()
, vary_on_headers()
和 cache_control()
应该首先出现,因为 RFC 7232 要求设置的 headers 需要出现在 304 响应中。
作为常用规则,如果你可以提供函数去同时计算 ETag 和最后的修改时间,你应该这样做。你不知道任何给定的 HTTP 客户端将向你发送哪一个headers,因此需要同时处理这两个headers。然而,有时候只有一个值易于计算,Django 提供的装饰器处理只有 ETag 或 只有 last-modified 的计算。
django.views.decorators.http.etag
和 django.views.decorators.http.last_modified
装饰器和 condition
装饰器一样传递相同的函数类型。它们的签名是这样的:
etag(etag_func)
last_modified(last_modified_func)
我们可以使用其中一个装饰器来编写更早一些的那个只使用last-modified函数的例子:
@last_modified(latest_entry)
def front_page(request, blog_id):
...
...或:
def front_page(request, blog_id):
...
front_page = last_modified(latest_entry)(front_page)
condition
¶如果你想测试两个先决条件,那么试着链接 etag
和 last_modified
装饰器可能看起来更好。然而,这会导致错误的行为。
# Bad code. Don't do this!
@etag(etag_func)
@last_modified(last_modified_func)
def my_view(request):
# ...
# End of bad code.
第一个装饰器不知道关于第二个装饰器的任何信息,而且可能回答“这个响应没有被修改”,即使第二个装饰器确定不是那样。condition
装饰器同时使用两个回调函数来执行正确的动作。
condition
装饰器不仅仅用于 GET
和 HEAD
请求(在这个解决方案里 HEAD``请求和 ``GET
类似)。它也可以被用于提供 POST
, PUT
和 DELETE
请求的检查。在这些情况下,不会返回一个“未修改”的响应,但会告诉客户端它们尝试修改的资源在这期间已被修改。
例如,在客户端和服务端之间考虑下面的交换:
"abcd1234"
的 ETag 响应一些内容。PUT
请求到 /foo/
来更新一些资源。它也发送 If-Match: "abcd1234"
header 来指定它准备更新的版本。GET
请求计算的方式相同,使用相同的函数)来检查资源是否被修改。如果资源被改变,它将返回 412 状态码,意思是 "先决条件失败" 。GET
请求到 /foo/
,在更新它之前用来寻找内容更新的版本。重要的是这个例子显示的是相同函数在所有情形下可以被用来计算ETag和最后一次修改。事实上,你应该使用相同的函数,这样相同的值会被实时返回。
具有不安全请求方法的验证 headers
condition
装饰器只为安全的HTTP方法设置验证器headers(ETag
和 Last-Modified
)。如果你想在其他案例中返回它们,那就在你的视图中设置它们。查看 RFC 7231#section-4.3.4 来了解关于为响应 PUT
和 POST
发出的请求而设置验证器 header 之间的区别。
Django 通过 django.middleware.http.ConditionalGetMiddleware
提供了有条件的 GET
处理。虽然易于使用,但是中间件在高级用法上是有限制的:
GET
请求。你应该为你的特殊需求选择最合适的工具。如果你有方法可以很迅速地计算 ETags 和 修改时间,并且如果一些视图花了一些时间去生成内容,那么你应该在这个文档中考虑使用 condition
装饰器描述。如果所有事务都运行的非常快了,那么坚持使用中间件。如果视图没有变动,那么发送回客户端的网络流量将仍然会减少。
12月 13, 2021