Обработка условного отображения
Клиенты 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 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, означающий «условие не выполнено». - Клиент отправляет запрос
GETна/foo/, после получения ответа 412, чтобы получить обновлённую версию содержимого перед его обновлением.
Важное, что демонстрирует этот пример, заключается в том, что те же функции могут использоваться для вычисления значений ETag и последнего изменения во всех ситуациях. Фактически, вы **должны** использовать те же функции, чтобы каждый раз возвращались одинаковые значения.
Заголовки проверки с небезопасными методами запроса
Декоратор condition устанавливает заголовки проверки (ETag и Last-Modified ) только для безопасных методов HTTP, то есть GET и HEAD. Если вы хотите их вернуть в других случаях, установите их в представлении. Обратитесь к RFC 9110#section-9.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/5.0/topics/conditional-view-processing/