Обработка условного отображения
Клиенты HTTP могут отправлять ряд заголовков, чтобы сообщить серверу о копиях ресурса, которые они уже видели. Это обычно используется при получении веб-страницы (используя HTTP GET запрос), чтобы избежать отправки всех данных для чего-то, что клиент уже получил. Однако те же заголовки могут использоваться для всех методов HTTP (POST, PUT, DELETE, и т.д.).
Для каждой страницы (ответа), которую Django отправляет обратно из представления, она может предоставить два HTTP-заголовка: заголовок ETag и заголовок Last-Modified. Эти заголовки являются необязательными в HTTP-ответах. Они могут быть установлены вашей функцией представления, или вы можете положиться на ConditionalGetMiddleware middleware для установки заголовка 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запросов.
Вам следует выбрать наиболее подходящий инструмент для вашей конкретной проблемы здесь. Если у вас есть способ быстро вычислить ETags и время изменения, и если для генерации содержимого некоторого представления требуется некоторое время, следует рассмотреть использование декоратора condition, описанного в этом документе. Если все уже работает достаточно быстро, придерживайтесь использования промежуточного слоя, и объем сетевого трафика, отправляемого обратно клиентам, по-прежнему будет уменьшен, если представление не изменилось.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.1/topics/conditional-view-processing/