Обработка условных представлений
Клиенты HTTP могут отправлять ряд заголовков, чтобы сообщить серверу о копиях ресурса, которые они уже видели. Это обычно используется при получении веб-страницы (используя запрос HTTP GET), чтобы избежать отправки всех данных для чего-то, что клиент уже получил. Однако те же самые заголовки могут использоваться для всех методов HTTP (POST, PUT, DELETE, и т.д.).
Для каждой страницы (ответа), которую Django отправляет обратно из представления, она может предоставить два HTTP-заголовка: заголовок ETag и заголовок Last-Modified. Эти заголовки являются необязательными в ответах HTTP. Их можно установить в вашей функции представления, или вы можете положиться на middleware ConditionalGetMiddleware, чтобы установить заголовок ETag.
Когда клиент в следующий раз запрашивает тот же ресурс, он может отправить заголовок, такой как If-modified-since или If-unmodified-since, содержащий дату последнего времени изменения, или If-match или If-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, должна возвращать стандартное значение 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 7232 требует, чтобы установленные ими заголовки были присутствовать в ответах 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, что означает «условие не выполнено». - Клиент отправляет запрос
GETв/foo/, после получения ответа 412, чтобы получить обновлённую версию содержимого перед его обновлением.
Важный момент, который показывает этот пример, заключается в том, что те же функции могут использоваться для вычисления значений ETag и последнего изменения во всех ситуациях. На самом деле, вы **должны** использовать те же функции, чтобы каждый раз возвращались одинаковые значения.
Заголовки валидатора с методами запроса, не являющимися безопасными
Декоратор condition устанавливает заголовки валидатора (ETag и Last-Modified) только для безопасных методов HTTP, т.е. GET и HEAD. Если вы хотите их вернуть в других случаях, установите их в представлении. Смотрите RFC 7231#section-4.3.4, чтобы узнать о различии между установкой заголовка валидатора в ответ на запросы, выполненные с помощью PUT по сравнению с POST.
Сравнение с обработкой условных представлений через middleware
Django предоставляет обработку условных GET через django.middleware.http.ConditionalGetMiddleware. Хотя он подходит для многих ситуаций, middleware имеет ограничения для расширенного использования:
- Он применяется глобально ко всем представлениям вашего проекта.
- Он не освобождает вас от генерации ответа, что может быть дорогостоящим.
- Он подходит только для запросов HTTP
GET.
Здесь вы должны выбрать наиболее подходящий инструмент для вашей конкретной задачи. Если у вас есть способ быстро вычислить ETag и время изменения, и если какое-то представление занимает много времени для генерации содержимого, вы должны рассмотреть использование декоратора condition, описанного в этом документе. Если всё уже работает достаточно быстро, придерживайтесь использования middleware, и объём сетевого трафика, отправляемого клиентам, всё равно будет уменьшен, если представление не изменилось.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.0/topics/conditional-view-processing/