Обработка условных представлений
HTTP-клиенты могут отправлять ряд заголовков, чтобы сообщить серверу о копиях ресурса, которые они уже видели. Это обычно используется при получении веб-страницы (используя HTTP-запрос) для предотвращения отправки всех данных для чего-то, что клиент уже получил. Однако те же заголовки могут использоваться для всех HTTP-методов (PUT, POST, DELETE и т. д.).
Для каждой страницы (ответа), которую Django отправляет обратно из представления, она может предоставить два HTTP-заголовка: заголовок Last-Modified и заголовок ETag. Эти заголовки являются необязательными в HTTP-ответах. Их можно установить в вашей функции представления, или вы можете положиться на middleware ConditionalGetMiddleware, чтобы установить заголовок ETag.
Когда клиент в следующий раз запрашивает тот же ресурс, он может отправить заголовок, такой как If-Modified-Since или If-Unmodified-Since, содержащий дату последнего времени изменения, или If-Match или If-None-Match, содержащий последний ETag, который был отправлен. Если текущая версия страницы соответствует ETag, отправленному клиентом, или если ресурс не был изменен, можно отправить код состояния 304 вместо полного ответа, сообщая клиенту, что ничего не изменилось. В зависимости от заголовка, если страница была изменена или не соответствует ETag, отправленному клиентом, может быть возвращен код состояния 412 (Ошибка условия).
Когда вам требуется более точный контроль, вы можете использовать функции условной обработки для каждого представления.
Декоратор условной обработки
Иногда (на самом деле, довольно часто) вы можете создавать функции для быстрого вычисления значения ETag или последнего времени изменения ресурса, не выполняя все вычисления, необходимые для построения полного представления. Django может затем использовать эти функции, чтобы предоставить опцию «быстрого выхода» для обработки представления. Сообщить клиенту, что содержимое не изменялось с момента последнего запроса.
Эти две функции передаются в качестве параметров декоратору условной обработки. Этот декоратор использует две функции (вам нужно предоставить только одну, если вы не можете легко и быстро вычислить оба значения) для проверки, соответствуют ли заголовки в запросе HTTP тем, которые указаны в ресурсе. Если они не совпадают, необходимо вычислить новую копию ресурса, и вызывается ваше обычное представление.
Подпись декоратора условной обработки выглядит следующим образом:
condition(etag_func=None, last_modified_func=None)
Две функции, для вычисления ETag и последнего времени изменения, будут переданы объект incoming request и те же параметры, в том же порядке, что и в функции представления, которую они помогают обернуть. Функция, переданная last_modified_func, должна возвращать стандартное значение datetime, указывающее последнее время изменения ресурса, или None, если ресурс не существует. Функция, переданная декоратору etag, должна возвращать строку, представляющую ETag для ресурса, или None, если он не существует.
Декоратор устанавливает заголовки Last-Modified и ETag в ответе, если они еще не установлены представлением и если метод запроса является безопасным (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)
...
Если главная страница, отображающая последние записи блога, изменяется только при добавлении новой записи блога, вы можете очень быстро вычислить последнее время изменения. Вам нужно последнее значение datetime каждой записи, связанной с этим блогом. Один из способов сделать это:
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 (запросы POST идентичны запросам GET в этой ситуации). Он также может использоваться для проверки запросов PUT, PATCH и DELETE. В этих ситуациях идея не заключается в возвращении ответа «не изменено», а в сообщении клиенту, что ресурс, который он пытается изменить, был изменён между тем.
Например, рассмотрим следующий обмен между клиентом и сервером:
- Клиент запрашивает ресурс.
- Сервер отвечает с некоторым содержимым с ETag
"abcd1234". - Клиент отправляет HTTP-запрос PUT на сервер для обновления ресурса. Он также отправляет заголовок If-Match, чтобы указать версию, которую он пытается обновить.
- Сервер проверяет, изменился ли ресурс, вычисляя ETag таким же способом, как и для запроса GET (используя ту же функцию). Если ресурс изменился, он вернет код состояния 412, что означает «условие не выполнено».
- Клиент отправляет запрос GET на сервер, после получения ответа 412, чтобы получить обновлённую версию содержимого перед обновлением.
Важное значение этого примера заключается в том, что те же функции могут использоваться для вычисления значений ETag и последнего изменения во всех ситуациях. Фактически, вы должны использовать те же функции, чтобы каждый раз возвращались одинаковые значения.
Заголовки проверки с методами запроса, отличными от безопасных
Декоратор condition устанавливает заголовки проверки (Last-Modified и ETag) только для безопасных HTTP-методов, т. е. GET и HEAD. Если вы хотите вернуть их в других случаях, установите их в вашем представлении. См. RFC 9110 Раздел 9.3.4, чтобы узнать о различиях между установкой заголовка проверки в ответ на запросы, сделанные с помощью GET, и запросами PUT.
Сравнение с условной обработкой через 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/5.2/topics/conditional-view-processing/