• 3.1
  • 3.2
  • 5.0
  • Версия документации: 6.1

Обработка условного представления

HTTP-клиенты могут отправлять несколько заголовков, чтобы сообщить серверу о копиях ресурса, которые они уже видели. Это обычно используется при получении веб-страницы (с использованием HTTP-запроса GET), чтобы избежать отправки всех данных для того, что клиент уже получил. Однако одни и те же заголовки могут использоваться для всех методов HTTP («POST», «PUT», «DELETE» и т. д.).

Для каждой страницы (ответа), которую Django отправляет обратно из представления, он может предоставлять два HTTP-заголовка: заголовок ETag и заголовок Last-Modified. Эти заголовки не являются обязательными для HTTP-ответов. Они могут быть установлены вашей функцией просмотра или вы можете положиться на промежуточное программное обеспечение 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, должна возвращать стандартное значение даты и времени, указывающее время последнего изменения ресурса, или 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)
    ...

Если главная страница, отображающая последние записи блога, меняется только при добавлении новой записи блога, вы можете очень быстро вычислить время последнего изменения. Вам нужна последняя дата публикации для каждой записи, связанной с этим блогом. Один из способов сделать это:

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. В таких ситуациях идея состоит не в том, чтобы вернуть ответ «не изменено», а в том, чтобы сообщить клиенту, что ресурс, который он пытается изменить, за это время был изменен.

Например, рассмотрим следующий обмен между клиентом и сервером:

  1. Клиент запрашивает /foo/.

  2. Сервер отвечает некоторым содержимым с ETag "abcd1234".

  3. Клиент отправляет HTTP-запрос PUT в /foo/ для обновления ресурса. Он также отправляет заголовок «If-Match: «abcd1234»», чтобы указать версию, которую он пытается обновить.

  4. Сервер проверяет, изменился ли ресурс, вычисляя ETag так же, как и для запроса GET (с использованием той же функции). Если ресурс изменен, он вернет код состояния 412, что означает «предварительное условие не выполнено».

  5. Клиент отправляет запрос GET в /foo/ после получения ответа 412, чтобы получить обновленную версию контента перед его обновлением.

Важная вещь, которую показывает этот пример, заключается в том, что одни и те же функции могут использоваться для вычисления значений ETag и последней модификации во всех ситуациях. Фактически, вам следует использовать одни и те же функции, чтобы каждый раз возвращались одни и те же значения.

Заголовки валидатора с небезопасными методами запроса

Декоратор Condition устанавливает только заголовки валидатора (ETag и Last-Modified) для безопасных методов HTTP, то есть GET и HEAD. Если вы хотите вернуть их в других случаях, установите их на свое усмотрение. См. RFC 9110 Section 9.3.4, чтобы узнать о разнице между установкой заголовка валидатора в ответ на запросы, сделанные с помощью PUT и POST.

Сравнение с условной обработкой промежуточного программного обеспечения

Django обеспечивает условную обработку GET через django.middleware.http.ConditionalGetMiddleware. Хотя промежуточное программное обеспечение подходит для многих ситуаций, оно имеет ограничения для расширенного использования:

  • Оно применяется глобально ко всем представлениям в вашем проекте.

  • Это не избавляет вас от необходимости генерировать ответ, который может оказаться дорогостоящим.

  • Это подходит только для HTTP-запросов GET.

Здесь вам следует выбрать наиболее подходящий инструмент для вашей конкретной проблемы. Если у вас есть способ быстро вычислить ETags и время модификации, и если для создания содержимого какого-либо представления требуется некоторое время, вам следует рассмотреть возможность использования декоратора «condition», описанного в этом документе. Если все уже работает достаточно быстро, придерживайтесь использования промежуточного программного обеспечения, и объем сетевого трафика, отправляемого обратно клиентам, все равно будет уменьшен, если представление не изменилось.

Back to Top