条件视图处理

HTTP客户端可以发送一些标题来告诉服务器有关他们已经看到的资源的副本。 这在检索Web页面(使用HTTP GET请求)时经常使用,以避免发送客户端已经检索到的所有数据。 但是,相同的头文件可以用于所有HTTP方法(POSTPUTDELETE等)。

对于Django从视图返回的每个页面(响应),它可以提供两个HTTP头文件:ETag头文件和Last-Modified头文件。 这些标头在HTTP响应中是可选的。 They can be set by your view function, or you can rely on the ConditionalGetMiddleware middleware to set the 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. 如果当前版本的页面与客户端发送的ETag匹配,或者资源没有被修改,则可以发回304状态码,而不是完整的响应,告诉客户端什么也没有变。 根据标题,如果页面已被修改或与客户端发送的ETag不匹配,可能会返回412状态码(Precondition Failed)。

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

condition装饰器

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可以使用这些函数为视图处理提供“早期救援”选项。 告诉客户端,自上次请求以来,内容还没有被修改。

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

condition装饰者的签名看起来像这样:

condition(etag_func=None, last_modified_func=None)

计算ETag和上次修改时间的两个函数将按照与它们正在帮助包装的视图函数相同的顺序传递传入的request对象和相同的参数。 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. 传递给etag修饰符的函数应返回一个表示资源的ETag的字符串,如果不存在,则返回None

The decorator sets the ETag and Last-Modified headers on the response if they are not already set by the view and if the request’s method is safe (GET or HEAD).

在Django 1.11中更改:

在旧版本中,来自etag_func()的返回值被解释为ETag的未加引号部分。 这阻止了使用弱ETags,其格式为W/"<string>" 现在预期返回值是由规范(包括引号)定义的ETag,尽管未加引号的格式也被接受用于向后兼容。

有用地使用这个功能可能是最好的例子。 假设你有这一对模型,代表一个简单的博客系统:

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()之上。 In particular, vary_on_cookie(), vary_on_headers(), and cache_control() should come first because RFC 7232 requires that the headers they set be present on 304 responses.

仅计算一个值的快捷方式

一般来说,如果你可以提供函数来计算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修饰符可能会更好。 但是,这会导致不正确的行为。

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

# End of bad code.

第一个装饰器对第二个装饰器什么都不知道,可能会回答,即使第二个装饰器会另外决定,响应也不会被修改。 condition装饰器同时使用两个回调函数来制定正确的动作。

将装饰器与其他HTTP方法一起使用

condition装饰器对于GETHEAD请求(HEAD请求与GET在这种情况下)。 它也可以用来检查POSTPUTDELETE请求。 在这种情况下,这个想法不是返回一个“未修改”的响应,而是告诉客户他们试图改变的资源在此期间已经被改变了。

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

  1. 客户端请求/foo/
  2. Server responds with some content with an ETag of "abcd1234".
  3. 客户端向/foo/发送一个HTTP PUT请求来更新资源。 它也发送一个 如果-比赛: “ABCD1234” 头指定它正在尝试更新的版本。
  4. 服务器通过计算ETag的方式来检查资源是否已经改变,就像它对于一个GET请求(使用相同的函数)一样。 如果资源已经改变,它将返回一个412状态码,意思是“前提条件失败”。
  5. 客户端在接收到412响应之后向/foo/发送GET请求,以在更新内容之前检索更新版本的内容。

这个例子显示的重要的事情是相同的函数可以用来计算ETag和所有情况下的最后修改值。 In fact, you should use the same functions, so that the same values are returned every time.

带有非安全请求方法的Validator头文件

condition修饰符只为安全的HTTP方法设置验证头(ETagLast-Modified),即GET HEAD 如果您希望在其他情况下退回,请在您的视图中进行设置。 请参阅 RFC 7231#section-4.3.4以了解设置验证程序标头与响应PUTPOST

与中间件条件处理比较

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

  • 它在全球范围内适用于您的项目中的所有视图。
  • 它不会让您无法生成响应,这可能很昂贵。
  • 它只适用于HTTP GET请求。

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