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 。
When the client next requests the same resource, it might send along a header
such as either If-Modified-Since or
If-Unmodified-Since, containing the date of the
last modification time it was sent, or either If-Match or If-None-Match,
containing the last ETag
it was sent. If the current version of the page
matches the ETag
sent by the client, or if the resource has not been
modified, a 304 status code can be sent back, instead of a full response,
telling the client that nothing has changed. Depending on the header, if the
page has been modified or does not match the ETag
sent by the client, a 412
status code (Precondition Failed) may be returned.
当你需要更多的控制,你可以使用针对每个视图的条件处理函数。
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):
...
小心装饰器的顺序
When condition()
returns a conditional response, any decorators below
it will be skipped and won't apply to the response. Therefore, any
decorators that need to apply to both the regular view response and a
conditional response must be above condition()
. In particular,
vary_on_cookie()
,
vary_on_headers()
, and
cache_control()
should come first
because RFC 9110 requires that the headers
they set be present on 304 responses.
作为常用规则,如果你可以提供函数去同时计算 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
The condition
decorator only sets validator headers (ETag
and
Last-Modified
) for safe HTTP methods, i.e. GET
and HEAD
. If you
wish to return them in other cases, set them in your view. See
RFC 9110#section-9.3.4 to learn about the distinction between setting a
validator header in response to requests made with PUT
versus POST
.
Django 通过 django.middleware.http.ConditionalGetMiddleware
提供了有条件的 GET
处理。虽然易于使用,但是中间件在高级用法上是有限制的:
GET
请求。你应该为你的特殊需求选择最合适的工具。如果你有方法可以很迅速地计算 ETags 和 修改时间,并且如果一些视图花了一些时间去生成内容,那么你应该在这个文档中考虑使用 condition
装饰器描述。如果所有事务都运行的非常快了,那么坚持使用中间件。如果视图没有变动,那么发送回客户端的网络流量将仍然会减少。
12月 05, 2023