条件视图处理

HTTP客户机可以发送许多头,告诉服务器他们已经看到的资源的副本。这通常在检索网页(使用HTTP)时使用 GET 请求)以避免发送客户端已检索到的所有数据。但是,相同的头可以用于所有HTTP方法 (POSTPUTDELETE 等)。

对于Django从视图返回的每个页面(响应),它可能提供两个HTTP头:一个是 ETag 报头和 Last-Modified 标题。这些头对于HTTP响应是可选的。它们可以由视图函数设置,也可以依赖 ConditionalGetMiddleware 中间件设置 ETag 标题。

当客户端下一次请求相同的资源时,它可能会沿着一个头(如 If-modified-sinceIf-unmodified-since ,包含上次发送修改的日期,或者 If-matchIf-none-match ,包含最后一个 ETag 它被送来了。如果页面的当前版本与 ETag 由客户机发送,或者如果资源没有被修改,则可以返回304状态代码,而不是完整响应,告诉客户机没有任何更改。如果页面已被修改或与 ETag 由客户端发送,可能返回412状态代码(前提条件失败)。

当需要更细粒度的控制时,可以使用每个视图的条件处理函数。

这个 condition 装饰符

有时(实际上,非常经常)您可以创建函数来快速计算 ETag 资源的值或上次修改的时间, 没有 需要完成构建完整视图所需的所有计算。然后Django可以使用这些函数为视图处理提供“早期救援”选项。告诉客户机内容可能自上次请求以来没有被修改过。

这两个函数作为参数传递给 django.views.decorators.http.condition 装饰符。这个修饰符使用两个函数(如果不能轻松快速地计算两个数量,只需提供一个)来计算HTTP请求中的头是否与资源中的头匹配。如果它们不匹配,则必须计算资源的新副本,并调用普通视图。

这个 condition 修饰符的签名如下:

condition(etag_func=None, last_modified_func=None)

计算ETag和最后修改时间的两个函数将通过传入的 request 对象和相同的参数,顺序与它们帮助包装的视图函数相同。传递的函数 last_modified_func 应返回指定上次修改资源的时间的标准日期时间值,或 None 如果资源不存在。传递给 etag decorator应返回一个表示 ETag 对于资源,或 None 如果它不存在。

装饰师设置 ETagLast-Modified 如果视图尚未设置响应头,并且请求的方法安全,则返回响应头。 (GETHEAD

有效地使用这个特性可能最好用一个例子来解释。假设您有这对模型,表示一个简单的博客系统:

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)
    ...

如果显示最新日志的首页仅在添加新日志时发生更改,则可以快速计算上次修改的时间。你需要最新的 published 与该日志关联的每个条目的日期。一种方法是:

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 要求它们设置的头在304个响应上存在。

只计算一个值的快捷方式

作为一般规则,如果可以提供函数来计算 both ETag和上次修改的时间,应该这样做。您不知道任何给定的HTTP客户机将向您发送哪个头,因此请准备处理这两个头。然而,有时只有一个值很容易计算,Django提供的修饰符只处理etag或最后修改的计算。

这个 django.views.decorators.http.etagdjango.views.decorators.http.last_modified 修饰符传递的函数类型与 condition 装饰符。他们的签名是:

etag(etag_func)
last_modified(last_modified_func)

我们可以编写前面的示例,它只使用最后一个修改过的函数,使用以下修饰符之一:

@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 当测试两种条件时

对某些人来说,尝试将 etaglast_modified 如果要测试这两个前提条件,请使用decorator。但是,这会导致不正确的行为。

# Bad code. Don't do this!
@etag(etag_func)
@last_modified(last_modified_func)
def my_view(request):
    # ...

# End of bad code.

第一个修饰符对第二个修饰符一无所知,可能会回答响应没有被修改,即使第二个修饰符会决定是否修改。这个 condition decorator同时使用两个回调函数来计算要采取的正确操作。

将decorator与其他HTTP方法一起使用

这个 condition decorator不仅对 GETHEAD 请求 (HEAD 请求与 GET 在这种情况下)。它还可用于检查 POSTPUTDELETE 请求。在这些情况下,这个想法并不是返回“未修改”的响应,而是告诉客户他们试图更改的资源在这段时间内已被更改。

例如,考虑客户机和服务器之间的以下交换:

  1. 客户端请求 /foo/ .
  2. 服务器用ETag响应一些内容 "abcd1234" .
  3. 客户端发送HTTP PUT 请求到 /foo/ 更新资源。它还发送一个 If-Match: "abcd1234" 头指定要更新的版本。
  4. 服务器通过计算etag来检查资源是否已更改,方法与对 GET 请求(使用相同的功能)。如果资源 has 更改后,返回412状态码,表示“前提条件失败”。
  5. 客户端发送一个 GET 请求到 /foo/ 在收到412个响应后,在更新内容之前检索内容的更新版本。

这个例子说明的重要一点是,在所有情况下,可以使用相同的函数来计算etag和最后的修改值。事实上,你 应该 使用相同的函数,以便每次返回相同的值。

具有非安全请求方法的验证程序头

这个 condition decorator只设置验证器头 (ETagLast-Modified )对于安全的HTTP方法,即 GETHEAD . 如果您希望在其他情况下返回它们,请在视图中设置它们。见 RFC 7231#section-4.3.4 了解设置验证程序头以响应使用 PUT 对战 POST .

与中间件条件处理的比较

Django提供了简单和直接的条件 GET 通过通道处理 django.middleware.http.ConditionalGetMiddleware . 虽然中间件易于使用且适用于许多情况,但它对高级使用有限制:

  • 它已全局应用于项目中的所有视图。
  • 这不会节省您生成响应的时间,这可能很昂贵。
  • 它只适用于HTTP GET 请求。

您应该在这里为您的特定问题选择最合适的工具。如果您有一种快速计算etags和修改时间的方法,并且某些视图生成内容需要一段时间,那么您应该考虑使用 condition 本文档中描述的修饰符。如果一切都已经运行得相当快,那么坚持使用中间件,如果视图没有更改,那么发送回客户机的网络流量仍然会减少。