Обработка условных представлений

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-заголовка: заголовок ETag и заголовок Last-Modified. Эти заголовки являются необязательными для HTTP-ответов. Они могут быть установлены вашей функцией представления, или вы можете положиться на промежуточное программное обеспечение ConditionalGetMiddleware для установки заголовка ETag.

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.

Если вам нужен более тонкий контроль, вы можете использовать функции условной обработки для каждого вида.

Декоратор 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 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-запросе с заголовками ресурса. Если они не совпадают, должна быть вычислена новая копия ресурса и вызвано ваше обычное представление.

Сигнатура декоратора condition выглядит следующим образом:

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.

Декоратор устанавливает заголовки ETag и Last-Modified в ответ, если они еще не установлены представлением и если метод запроса безопасен (GET или HEAD).

Полезное использование этой функции, вероятно, лучше всего объяснить на примере. Предположим, у вас есть пара моделей, представляющих небольшую систему блогов:

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

Будьте осторожны с заказом декораторов

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-клиент, поэтому будьте готовы обрабатывать оба. Однако иногда легко вычислить только одно значение, и 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 использует обе функции обратного вызова одновременно, чтобы выработать правильное действие.

Использование декораторов с другими методами HTTP

Декоратор condition полезен не только для запросов GET и HEAD (запросы HEAD в этой ситуации то же самое, что и GET). Его также можно использовать для обеспечения проверки запросов POST, PUT и DELETE. В этих ситуациях идея заключается не в том, чтобы вернуть ответ «не изменено», а в том, чтобы сообщить клиенту, что ресурс, который он пытается изменить, был изменен за прошедшее время.

Например, рассмотрим следующий обмен между клиентом и сервером:

  1. Клиент запрашивает /foo/.
  2. Сервер отвечает некоторым содержимым с ETag "abcd1234".
  3. Клиент отправляет HTTP PUT запрос на /foo/ для обновления ресурса. Он также посылает заголовок If-Match: "abcd1234", чтобы указать версию, которую он пытается обновить.
  4. Сервер проверяет, не изменился ли ресурс, вычисляя ETag так же, как это делается для запроса GET (используя ту же функцию). Если ресурс изменился, он вернет код состояния 412, означающий «предварительное условие не выполнено».
  5. Клиент посылает запрос GET на /foo/, получив ответ 412, чтобы получить обновленную версию содержимого перед его обновлением.

Этот пример показывает, что для вычисления значений ETag и последней модификации во всех ситуациях можно использовать одни и те же функции. На самом деле, вы должны использовать одни и те же функции, чтобы каждый раз возвращались одни и те же значения.

Заголовки валидатора с небезопасными методами запроса

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 обеспечивает условную обработку GET через django.middleware.http.ConditionalGetMiddleware. Хотя эта промежуточная программа подходит для многих ситуаций, она имеет ограничения для расширенного использования:

  • Он применяется глобально ко всем представлениям в вашем проекте.
  • Это не избавит вас от необходимости генерировать ответ, что может стоить дорого.
  • Он подходит только для HTTP GET запросов.

Здесь вы должны выбрать наиболее подходящий инструмент для решения вашей конкретной задачи. Если у вас есть способ быстро вычислить ETags и время модификации, и если для создания содержимого какого-либо представления требуется некоторое время, вам следует рассмотреть возможность использования декоратора condition, описанного в этом документе. Если все и так работает достаточно быстро, придерживайтесь использования промежуточного программного обеспечения, и количество сетевого трафика, отправляемого обратно клиентам, все равно будет уменьшено, если представление не изменилось.

Вернуться на верх