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