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

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

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

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

When the client next requests the same resource, it might send along a header such as either If-modified-since or If-unmodified-since, containing the date of the last modification time it was sent, or either If-match or If-none-match, containing the last ETag it was sent. If the current version of the page matches the ETag sent by the client, or if the resource has not been modified, a 304 status code can be sent back, instead of a full response, telling the client that nothing has changed. Depending on the header, if the page has been modified or does not match the ETag sent by the client, a 412 status code (Precondition Failed) may be returned.

Если вам нужен более детальный контроль, вы можете использовать функции условной обработки для каждого представления.

Декоратор 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)
    ...

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

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):
    ...

Будьте осторожны с порядком декораторов

When condition() returns a conditional response, any decorators below it will be skipped and won’t apply to the response. Therefore, any decorators that need to apply to both the regular view response and a conditional response must be above condition(). In particular, vary_on_cookie(), vary_on_headers(), and cache_control() should come first because RFC 7232 requires that the headers they set be present on 304 responses.

Ярлыки для вычисления только одного значения

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

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

The condition decorator only sets validator headers (ETag and Last-Modified) for safe HTTP methods, i.e. GET and HEAD. If you wish to return them in other cases, set them in your view. See RFC 7231 Section 4.3.4 to learn about the distinction between setting a validator header in response to requests made with PUT versus POST.

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

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

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

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

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

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

Back to Top