Django-декоратор обработки условного представления добавляет несвежий Etag

Я пытался использовать функцию обработки условного представления Django. По сути, я хочу запретить операции обновления над сущностью, если с тех пор она была изменена другим пользователем, и это, похоже, хорошо работает с декоратором @ condition, предоставленным Django.

Однако есть одна проблема, которую я заметил во время тестирования, и позже я проверил в источниках Django и обнаружил, что я думаю, что это может быть ошибкой, но просто хотел сначала подтвердить это, прежде чем отправлять отчет об ошибках в Django и исправление.

Декоратор вызывается при поступлении нового запроса, сначала он вычисляет метку времени ETag и Last Modified на основе функций, переданных декоратору, а затем передает управление get_conditional_response() функция. Здесь будет выполнена проверка ETag и Last Modified, и если они не соответствуют тому, что указано в запросе, запрос будет отклонен. Все идет нормально.

Если проверки пройдены, запрос разрешается и вызывается представление для обработки запроса и генерации ответа. При обработке запроса, если это был небезопасный метод, например PUT или же PATCHэто обновит сущность, которая, скорее всего, изменит значения ETag и Last Modified.

Тем не менее, я заметил, что успешный ответ на PUT или же PATCH отправляется обратно с меткой времени ETag или Last Modified, рассчитанной до фактического выполнения обновления, и к настоящему времени эти значения недействительны или устарели. Это мне кажется неправильным. Делать свежий GET на том же объекте затем предоставляет пользователю обновленные значения ETag и Last Modified в ответе.

Ты не думаешь, condition() декоратор должен проверить, небезопасен ли метод запроса, затем он должен выполнить новый расчет ETag и Last Modified после обработки представления, а затем добавить свежие значения в ответ?

2 ответа

Решение

Я согласен, что здесь есть ошибка, хотя я думаю, что она немного отличается от того, что вы описываете.

Условные запросы определены в RFC 7232, но, к сожалению, этот документ не очень четко описывает, когда именно условные заголовки должны использоваться в ответе. Это говорит:

2,4. Когда использовать теги сущностей и даты последнего изменения

В 200 (ОК) ответах на GET или HEAD, сервер происхождения...

Это может привести к предположению, что использование заголовков не определено в других ответах.

Тем не менее, RFC 7231 явно позволяет использовать ETag в ответ на PUT в соответствии с новым представлением (как и ваша интуиция). Однако обратите внимание на это предостережение:

Исходный сервер НЕ ДОЛЖЕН отправлять поле заголовка валидатора (раздел 7.2), такое как поле ETag или Last-Modified, в успешном ответе на PUT если данные представления запроса не были сохранены без каких-либо преобразований, примененных к телу...

Таким образом, клиент будет использовать наличие или отсутствие ETag, чтобы определить, является ли его представление (которое он просто отправил как тело для PUT) был фактически сохранен. (См. Этот вопрос для более подробной информации по этому вопросу.)

Однако API условных запросов Django не позволяет проводить это различие. В частности, пользователь не может указать, сохранил ли представление представление без "преобразования, примененного к телу". Так что нет возможности для condition() декоратор, чтобы узнать, оправдано ли добавление ETag.

Поэтому единственное, что нужно сделать, это быть консервативным и вообще не возвращать условные заголовки в этом случае. Не стесняйтесь создавать билет (или я могу это сделать).

Создайте пользовательское промежуточное ПО для обработки etag в запросе GET/HEAD. Следующий код (Django 1.10) показывает, как создавать и обрабатывать etag с использованием промежуточного программного обеспечения.

Примечание: не включайте USE_ETAGS в файле настроек

from django.utils.cache import get_conditional_response, set_response_etag
from django.utils.http import unquote_etag


class ETag(object):
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        # before view

        response = self.get_response(request)

        # after view
        try:
            if request.method in ('GET', 'HEAD'):
                if not response.has_header('ETag'):
                    set_response_etag(response)
                etag = response.get('ETag')
                return get_conditional_response(
                    request,
                    etag=unquote_etag(etag),
                    last_modified=None,
                    response=response,
                )
        except Exception, e:
            pass

        return response

Я использую Django 1.10. Если вы используете более низкие версии, переопределите process_response(self, request, response) метод с логикой, реализованной внутри __call__ метод. И не забудьте добавить это в MIDDLEWARE/MIDDLEWARE_CLASSES в файле настроек

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',


    # myapp contains middleware.py file and 
    # ETag class is implemented inside the middleware.py file 
    'myapp.middleware.Etag',
]
Другие вопросы по тегам