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',
]