Условная обработка представлений
HTTP-клиенты могут отправлять несколько заголовков, сообщая серверу о копиях ресурса, которые они уже получали. Это обычно используется при получении веб-страницы (с помощью HTTP-запроса GET), чтобы не отправлять все данные, которые клиент уже получил. Однако те же заголовки можно использовать со всеми методами HTTP (POST, PUT, DELETE и т. д.).
Для каждой страницы (ответа), которую Django отправляет из представления, он может добавить два HTTP-заголовка: заголовок ETag и заголовок Last-Modified. Эти заголовки в HTTP-ответах необязательны. Их может установить ваша функция представления, либо можно поручить это промежуточному слою ConditionalGetMiddleware, который установит заголовок ETag.
При следующем запросе того же ресурса клиент может отправить заголовок If-Modified-Since или If-Unmodified-Since, содержащий дату последнего изменения ресурса, или заголовок If-Match либо If-None-Match, содержащий последний отправленный ETag. Если текущая версия страницы соответствует ETag, отправленному клиентом, или если ресурс не изменился, вместо полного ответа можно отправить код состояния 304, сообщая клиенту, что ничего не изменилось. В зависимости от заголовка, если страница была изменена или не соответствует ETag, отправленному клиентом, может быть возвращен код состояния 412 (Precondition Failed).
Для более точного управления можно использовать функции условной обработки на уровне отдельных представлений.
Декоратор condition
Иногда (на самом деле, довольно часто) можно создать функции для быстрого вычисления значения ETag или времени последнего изменения ресурса, не выполняя все вычисления, необходимые для создания полного представления. Тогда Django сможет использовать эти функции для досрочного прекращения обработки представления. Например, чтобы сообщить клиенту, что содержимое не изменилось с момента последнего запроса.
Эти две функции передаются в качестве параметров декоратору django.views.decorators.http.condition. Декоратор использует обе функции (достаточно указать одну, если быстро и просто вычислить обе величины невозможно), чтобы определить, соответствуют ли заголовки HTTP-запроса заголовкам ресурса. Если они не соответствуют, необходимо вычислить новую копию ресурса и вызвать обычное представление.
Сигнатура декоратора condition выглядит так:
condition(etag_func=None, last_modified_func=None)
Функции для вычисления ETag и времени последнего изменения получат входящий объект request и те же параметры в том же порядке, что и оборачиваемая ими функция представления. Функция, переданная в last_modified_func, должна возвращать стандартное значение datetime, указывающее время последнего изменения ресурса, или None, если ресурс не существует. Функция, переданная декоратору etag, должна возвращать строку, представляющую ETag ресурса, или None, если ресурс не существует.
Декоратор устанавливает заголовки 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): ...
Будьте внимательны к порядку декораторов
Когда condition() возвращает условный ответ, все расположенные ниже декораторы пропускаются и не применяются к ответу. Поэтому декораторы, которые должны применяться как к обычному ответу представления, так и к условному ответу, необходимо размещать выше condition(). В частности, vary_on_cookie(), vary_on_headers() и cache_control() следует размещать первыми, поскольку RFC 9110 требует, чтобы устанавливаемые ими заголовки присутствовали в ответах 304.
Упрощённые варианты для вычисления только одного значения
Как правило, если вы можете предоставить функции для вычисления обоих значений — ETag и времени последнего изменения, — вам следует это сделать. Нельзя заранее знать, какие заголовки отправит тот или иной HTTP-клиент, поэтому нужно быть готовым обработать оба варианта. Однако иногда легко вычислить только одно значение, и Django предоставляет декораторы, которые обрабатывают только вычисление ETag или времени последнего изменения.
Декораторам django.views.decorators.http.etag и django.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 для проверки обоих условий
Некоторым может показаться удобнее объединить декораторы 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. В этих случаях цель состоит не в том, чтобы вернуть ответ «не изменено», а в том, чтобы сообщить клиенту, что ресурс, который он пытается изменить, за это время был изменён.
Например, рассмотрим следующий обмен данными между клиентом и сервером:
- Клиент запрашивает
/foo/. - Сервер отвечает некоторым содержимым с ETag
"abcd1234". - Клиент отправляет HTTP-запрос
PUTна/foo/, чтобы обновить ресурс. Он также отправляет заголовокIf-Match: "abcd1234", указывая версию, которую пытается обновить. - Сервер проверяет, изменился ли ресурс, вычисляя ETag тем же способом, что и для запроса
GET(с помощью той же функции). Если ресурс изменился, сервер возвращает код состояния 412, означающий «предварительное условие не выполнено». - Получив ответ 412, клиент отправляет запрос
GETна/foo/, чтобы получить обновлённую версию содержимого перед его изменением.
Этот пример показывает, что одни и те же функции можно использовать для вычисления ETag и времени последнего изменения во всех ситуациях. Более того, вам следует использовать одни и те же функции, чтобы каждый раз возвращались одинаковые значения.
Заголовки проверки для небезопасных методов запроса
Декоратор condition устанавливает заголовки проверки (ETag и Last-Modified) только для безопасных методов HTTP, то есть GET и HEAD. Если вы хотите возвращать их в других случаях, установите их в представлении. Подробнее о различиях между установкой заголовка проверки в ответ на запросы PUT и POST см. в разделе 9.3.4 RFC 9110.
Сравнение с условной обработкой в промежуточном слое
Django предоставляет обработку условных запросов GET с помощью django.middleware.http.ConditionalGetMiddleware. Хотя промежуточный слой подходит для многих ситуаций, при более сложном использовании у него есть ограничения:
- Он применяется глобально ко всем представлениям проекта.
- Он не избавляет от генерации ответа, которая может быть затратной.
- Он подходит только для HTTP-запросов
GET.
Выбирайте наиболее подходящий инструмент для конкретной задачи. Если вы можете быстро вычислять ETag и время изменения, а генерация содержимого каким-либо представлением занимает заметное время, рассмотрите возможность использования описанного в этом документе декоратора condition. Если всё и так выполняется достаточно быстро, используйте промежуточный слой: он всё равно уменьшит объём сетевого трафика, отправляемого клиентам, если представление не изменилось.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/6.0/topics/conditional-view-processing/