Обработка условных представлений

Клиенты 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

Sometimes (in fact, quite often) you can create functions to rapidly compute the ETag value or the last-modified time for a resource, without needing to do all the computations needed to construct the full view. Django can then use these functions to provide an «early bailout» option for the view processing. Telling the client that the content has not been modified since the last request, perhaps.

Эти две функции передаются в качестве параметров декоратору django.views.decorators.http.condition. Этот декоратор использует эти две функции (вам нужно передать только одну, если вы не можете легко и быстро вычислить оба значения), чтобы определить, совпадают ли заголовки в HTTP-запросе с заголовками ресурса. Если они не совпадают, должна быть вычислена новая копия ресурса и вызвано ваше обычное представление.

Сигнатура декоратора condition выглядит следующим образом:

condition(etag_func=None, last_modified_func=None)

The two functions, to compute the ETag and the last modified time, will be passed the incoming request object and the same parameters, in the same order, as the view function they are helping to wrap. The function passed last_modified_func should return a standard datetime value specifying the last time the resource was modified, or None if the resource doesn’t exist. The function passed to the etag decorator should return a string representing the ETag for the resource, or None if it doesn’t exist.

Декоратор устанавливает заголовки 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 или только last-modified.

Декораторам django.views.decorators.http.etag и django.views.decorators.http.last_modified передаются функции того же типа, что и декоратору condition. Их сигнатуры следующие:

etag(etag_func)
last_modified(last_modified_func)

Мы могли бы написать предыдущий пример, в котором используется только функция last-modified, используя один из этих декораторов:

@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 7231#section-4.3.4, чтобы узнать о различии между установкой заголовка валидатора в ответ на запросы, сделанные с помощью PUT и POST.

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

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

  • Он применяется глобально ко всем представлениям в вашем проекте.
  • Это не избавит вас от необходимости генерировать ответ, что может стоить дорого.
  • Он подходит только для HTTP GET запросов.

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

Вернуться на верх