Spec-Zone.ru › Django 2.2

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

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 7232 требует, чтобы заголовки, которые они устанавливают, присутствовали в ответах со статусом 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 7231#section-4.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/2.2/topics/conditional-view-processing/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API