Spec-Zone.ru › Django 1.11

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

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 , если он не существует.

Изменено в Django 1.11:

В более старых версиях возвращаемое значение из etag_func() интерпретировалось как необрамлённая часть ETag. Это препятствовало использованию слабых ETag, у которых формат W/"<string>". Теперь ожидается, что возвращаемое значение будет ETag, как определено в спецификации (включая кавычки), хотя для обратной совместимости также принимается и необрамлённый формат.

Наиболее эффективное применение этой функции, вероятно, лучше всего объяснить на примере. Предположим, у вас есть эта пара моделей, представляющих простую систему блога:

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 и последнего изменения во всех ситуациях. На самом деле, вы **должны** использовать те же функции, чтобы каждый раз возвращались одинаковые значения.

Сравнение с условной обработкой через 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/1.11/topics/conditional-view-processing/

Spec-Zone.ru

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