Django-rest-framework: Легко добавлять / проверять etags.

Созданный на 29 июн. 2011  ·  24Комментарии  ·  Источник: encode/django-rest-framework

В Django есть отличный декоратор etag / condition / last_modified. Он не работает с представлениями на основе классов drf, так как вы не можете украсить ими "получить". Поскольку get возвращает объект, который не является HTTP-ответом, нет возможности добавить в ответ заголовок etag.

Я хотел бы увидеть способ сделать это из drf. Я думаю о чем-то вроде переопределяемого метода ресурса или представления (или миксина), которые можно использовать для генерации etag.

Другой способ сделать это в django - использовать промежуточное программное обеспечение, но оно не может сократить запуск тела представления в целом, как это делает декоратор.

Enhancement

Самый полезный комментарий

К сожалению, реализация по умолчанию и документация по функциональности Etag в drf-extension просто неверны и содержат опасные ошибки. Он изменяет Etag при изменении _request_, но не при изменении _response_. Это именно то, что вам нужно для кеширования на стороне сервера, и именно то, что вам не нужно для Etag.

Все 24 Комментарий

Хорошо, у меня есть предварительный патч: https://github.com/schinckel/django-rest-framework/commit/cc3a88edc6be21347a9b35929d158b8831ba9bd3

Тем не менее, я очень рад обратной связи по этому поводу.

Итак, изначально я написал следующее ....

Круто, да, я бы очень хотел это увидеть.

Coupla мысли - вы должны иметь возможность просто использовать View.add_header, а не View._ETAG, который у вас есть в настоящее время.
(И похоже, что .add_header, вероятно, следует переместить в класс ResponseMixin.)

Во-вторых, я бы хотел, чтобы украшения @condition , @etag и @last_modified могли быть в значительной степени прямым клоном https://github.com/django/django/blob/master/django/views/decorators /http.py , просто заменив пару add_header и ErrorResponses

Но смотрел на вещи немного больше ...

И, возможно, это не совсем правильный путь в конце концов ...

Фактически вы можете возвращать HttpResponses из представлений фреймворка REST, они просто не применяют все обычные вещи для согласования / сериализации контента. Декораторы @last_modified , @etag и @condition всегда возвращают только пустые HttpResponses, так что это не проблема.

Итак, я думаю, что если бы мы просто добавили __setitem__ __getitem__ и has_header в класс Response, тогда я думаю, что существующий @last_modified Django, Декораторы @etag и @condition должны нормально работать в представлении фреймворка REST _ пока_ в представлении используется стиль return Response(status, data) а не стиль return data .

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

Что вы думаете?

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

Это может быть не так, и в этом случае это решение звучит лучше.

Сказав это, я возвращаю стиль возвращаемых данных, поскольку он менее шаблонный, и я обычно возвращаю только те объекты, которые хочу сериализовать как json. Возможно, мы сможем заставить его работать обоими способами.

Другим вариантом может быть миксин, который добавляет их к классу View.

Мне также приходит в голову, что декораторы django могут делать неправильные вещи в отношении условных запросов PUT, POST и DELETE. Просто отправил в sinatra патч, чтобы исправить эту проблему.

Не обращайте внимания на последний бит: очевидно, я неправильно прочитал код.

На самом деле в этом есть смысл: эти декораторы могут не работать в банкоматах с _methods_, поскольку у них есть дополнительный аргумент «я». Я, возможно, изучу это и, возможно, отправлю билет в Django, так как они тоже должны работать с CBV ...

А, хорошо - теперь я вижу @method_decorator ... https://docs.djangoproject.com/en/dev/topics/class-based-views/#decorating -class-based-views
Итак, я предполагаю, что для закрытия этого нам понадобится:

  1. Небольшая настройка Response
  2. Некоторая легкая документация

Я уверен, что смотрел в тот документ, но я не видел этого декоратора!

Я попытался использовать декоратор django, и первоначальный результат был действительно странным, с информацией, которая должна принадлежать только представлениям, появляющимся в несвязанных функциях etag.

После нескольких часов попыток сделать его немного лучше, я нашел решение, которое разрешило мои проблемы и кажется достаточно общим. Что вы думаете об этом:

https://bitbucket.org/vitormazzi/django-rest-framework/changeset/6f8de4500c6f

Надеюсь вдохнуть новую жизнь в эту проблему теперь, когда мы находимся на выпуске 2.x.

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

Я вижу две области, в которых DRF необходимо учитывать ETags - использование в представлениях и способ получения уникального представления версии экземпляра.

Взгляды

ПОЛУЧИТЬ
Запросы GET просто должны обслуживать объекты ETag в соответствующем заголовке. Изменение одной строки на RetrieveModelMixin может легко добавить это:

def retrieve(self, request, *args, **kwargs):
    self.object = self.get_object()
    serializer = self.get_serializer(self.object)
    headers = {'ETag': self.object.etag}
    return Response(serializer.data, headers=headers)

ВСТАВИТЬ, ПАТЧ, УДАЛИТЬ
Общая проверка обновления HTTP-глаголов может быть выполнена в представлении dispatch или, возможно, вытащена другим методом, так как ему нужно будет проверить, включены ли ETags (см. Раздел параметров ниже):

    header_etag = request.META.get('HTTP_IF_MATCH')
    if header_etag is None:
        return Response({'error': 'IF_MATCH header is required'}, status=400)

Затем более подробная проверка после получения объекта, чтобы увидеть, считает ли запрос, что смотрит на правильный:

    if self.object.etag != header_etag:
        return Response({'error': 'object has been updated since you last saw it'}, status=412)

Уникальное представление версии экземпляра

Я не думаю, что фактическая генерация ETag объекта должна быть проблемой DRF. Я тестировал, используя время эпохи поля updated моего объекта, но я легко видел, что это должно быть более сложным.

Я предлагаю, чтобы DRF по умолчанию выполнял поиск obj.etag , но его можно настроить с использованием обычного потока CBV, например get_etag() и etag_var = 'get_my_objects_etag' .

Нам также нужно будет обеспечить, чтобы ETags извлекались из объектов в виде строки, поскольку мы сравниваем с заголовком и попытка интерпретировать тип в лучшем случае будет болезненной.

Опции

  • Глобальная настройка (как в случае с сериализаторами и т. Д.) Для включения или выключения использования ETags.
  • Две настройки в просмотрах:

    • use_etags (или что-то подобное) - логическое значение

    • etag_var - строка имени функции, которую мы можем getattr на рассматриваемом объекте

@ghickman - я бы хотел, чтобы поведение определения ETags и LastModified было похоже на поведение других подключаемых классов. Т.е. Есть что-то вроде:

class MyView(views.APIView):
    cache_lookup_classes = []

Кэширование подписей должно иметь дело как с ETags, так и с LastModified, и мы хотим обеспечить две разные вещи:

  • Определите etag и / или последнее изменение для экземпляра объекта.
  • Предварительно определить etag и / или последнее изменение для входящего запроса.

Будет BaseCacheLookup с двумя сигнатурами метода, которые могут выглядеть примерно так:

.object_etag_and_last_modified(self, view, obj)
.preemptive_etag_and_last_modified(self, view, request, *view_kwargs, **view_kwargs)

Для данного объекта возвращается кортеж из двух элементов (etag, last modified) , любой из которых может просто отсутствовать.
Если входящий запрос содержит соответствующий заголовок If-Modified-Since или If-None-Match, то будет возвращен ответ 304 Not Modified. Если входящий ответ содержит совпадающие If-Match или If-Unmodified-Since, то будет возвращен ответ 412 Precondition Failed.

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

Вы также можете применить несколько классов поиска в кеше с разной степенью детализации, измененной последним, например,
добавьте GlobalLastModifiedLookup в дополнение к ObjectETagLookup . Это позволило бы представлению упреждающе возвращаться до выполнения каких-либо вызовов базы данных, если с момента кэшированной копии не было произведено никаких записей. (Даже такие действительно базовые политики могут иметь огромное значение, если вы используете кеширование на стороне сервера с Varnish)

Разумна ли для вас сторона подключаемого класса?

Я не думал о LastModified, поскольку я не использую его в своей текущей реализации, но определенно имеет смысл включить его с учетом его цели.

Подключаемые классы звучат как отличная идея, особенно если мы включим реализации LastModified и ETag в качестве базовых примеров. Мне нравится идея, что кеширование GET будет очень легко включить с минимальными изменениями в проекте.

Я бы предпочел разделить генерацию etag и last_modified на два метода (с этими именами), которые, как вы предложили, возвращают None когда они не реализованы. Затем серверы CacheLookup могут выбрать реализацию того и / или другого. Для удобства мы всегда могли бы предоставить служебный метод (может быть, cachable_obj_repr или unique_obj_repr ?), Который объединял бы их, если бы вы считали его полезным.

tl; dr да, подключаемый класс звучит разумно и должен давать гораздо большую гибкость. Я счастлив начать писать патч для этого.

Всем привет. Если вам интересно, я реализовал другой подход для поддержки etag в моей библиотеке расширений http://chibisov.github.io/drf-extensions/docs/

@chibisov Neato. Нам действительно нужно закончить # 1019, так что у нас есть где-то в документации, чтобы ссылаться на такие пакеты.

Закрытие, поскольку # 1019 было закрыто, и пакет @chibisov указан.

Эта версия была специально обозначена как 3.3, так как я хотел бы, чтобы мы дали некоторые формальные указания по этому поводу в какой-то момент. Не особо обеспокоен, если мы решим оставить это закрытым, но это было в моей внутренней дорожной карте.

К сожалению, реализация по умолчанию и документация по функциональности Etag в drf-extension просто неверны и содержат опасные ошибки. Он изменяет Etag при изменении _request_, но не при изменении _response_. Это именно то, что вам нужно для кеширования на стороне сервера, и именно то, что вам не нужно для Etag.

@mbox лучше всего было бы открыть вопрос об этом в drf-extension или если вы думаете, что это основная проблема DRF, открытая здесь. Обратите внимание, что неудачный тест станет для нас хорошим началом взглянуть на проблему.

@mbox @xordoquy
Я только что отправил PR в drf-extensions (https://github.com/chibisov/drf-extensions/pull/171), который позволяет оптимистично управлять параллелизмом для управления ресурсами через DRF API. Он использует семантический хеш всех полей объекта, и я включил тестовое приложение для демонстрационных целей. Он был протестирован против DRF> = 3.3.1 и django> = 1.8 с Python 2.7, 3.4, 3.5.

Благодарность

Замечание для будущих читателей - я создал небольшой пакет для использования условных декораторов из Django вместе с DRF. Итак, если вам интересно:
https://github.com/jozo/django-rest-framework-condition

Была ли эта страница полезной?
0 / 5 - 0 рейтинги