Django-rest-framework: Agregue / valide fácilmente etags.

Creado en 29 jun. 2011  ·  24Comentarios  ·  Fuente: encode/django-rest-framework

Django tiene un increíble decorador etag / condition / last_modified. No funciona con las vistas basadas en clases drf, ya que no puede decorar 'get' con ellas. Debido a que get devuelve un objeto que no es una respuesta http, no hay forma de agregar el encabezado etag a la respuesta.

Me gustaría ver una forma de hacer esto desde dentro de drf. Estoy pensando en algo parecido a un método reemplazable en un recurso, o una vista (o un mixin) que se puede usar para generar el etag.

La otra forma de hacerlo en django es usar el middleware, pero no puede hacer un atajo para ejecutar el cuerpo de la vista como lo hace el decorador.

Enhancement

Comentario más útil

Desafortunadamente, la implementación predeterminada y la documentación sobre la funcionalidad Etag en drf-extensions son simplemente incorrectas y están peligrosamente llenas de errores. Cambia el Etag si el _request_ cambia, no si el _response_ cambia. Que es exactamente lo que desea para el almacenamiento en caché del lado del servidor y exactamente lo que no desea para un Etag.

Todos 24 comentarios

Sin embargo, estoy muy contento de recibir comentarios sobre esto.

Ok, entonces, inicialmente lo que escribí fue esto ...

Genial, sí, realmente me encantaría ver esto.

Coupla pensamientos: debería poder usar View.add_header, en lugar de View._ETAG que tiene actualmente.
(Y parece que .add_header probablemente debería moverse a la clase ResponseMixin).

En segundo lugar, me gustaría ver que las decoraciones @condition , @etag y @last_modified probablemente puedan ser más o menos un clon directo de https://github.com/django/django/blob/master/django/views/decorators /http.py , solo reemplazando un par de add_header y ErrorResponses

Pero estaba investigando las cosas un poco más ...

Y quizás esta no sea la manera correcta de hacerlo después de todo ...

De hecho, puede devolver HttpResponses desde las vistas del marco REST, simplemente no se aplican todas las cosas habituales de negociación / serialización de contenido. Los decoradores @last_modified , @etag y @condition solo devuelven HttpResponses vacías, así que eso no es realmente un problema.

Entonces, lo que estoy pensando es, si simplemente agregamos __setitem__ __getitem__ y has_header a la clase Response, entonces creo que Django ya existe @last_modified , @etag decoradores @condition deberían funcionar bien en una vista de marco REST _ siempre que_ la vista use el estilo return Response(status, data) lugar del estilo return data .

Obviamente, sería útil si documentamos eso, pero podría tener más sentido que tener que replicar algo que Django ya hace.

¿Qué piensas?

Puede haber un problema con el uso de los decoradores de django: no estoy seguro de que funcionen con métodos, solo funciones básicas. El decorador que escribí se basó en gran medida en uno que encontré a través de StackOverflow solo para este caso.

Puede que ese no sea el caso, en cuyo caso esta solución suena superior.

Habiendo dicho eso, he estado devolviendo el estilo de datos de retorno, ya que es menos repetitivo y, por lo general, solo devuelvo objetos que quiero serializar como json. Es posible que podamos hacer que funcione en ambos sentidos.

Otra opción puede ser un mixin que los agregue a la clase View.

También se me ocurre que los decoradores de django pueden no hacer lo correcto con respecto a las solicitudes condicionales PUT, POST y DELETE. Acabo de enviar un parche a Sinatra para solucionar ese problema.

Ignore el último bit: claramente no había leído el código correctamente.

En realidad, hay un punto ahí: esos decoradores podrían no funcionar en ATM en _methods_, ya que tienen el argumento adicional 'self'. Probablemente investigaré eso y posiblemente enviaré un ticket a Django, ya que también deberían funcionar con CBV ...

Ah, está bien, veo el @method_decorator ahora ... https://docs.djangoproject.com/en/dev/topics/class-based-views/#decorating -class-based-views
Así que supongo que para que esto se cierre necesitamos:

  1. Un poco de ajuste a la respuesta
  2. Alguna documentación ligera

Estoy seguro de que miré en ese documento, ¡pero no vi a ese decorador!

Traté de usar el decorador django y el resultado inicial fue realmente extraño, con información que debería pertenecer solo a las vistas que aparecen en funciones etag no relacionadas.

Después de unas horas tratando de mejorarlo un poco, encontré una solución que resolvió mis problemas y parece bastante general. ¿Cuáles son sus pensamientos sobre esto?

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

Con la esperanza de darle nueva vida a este problema ahora que estamos en las versiones 2.x.

Mis pensamientos aquí están en su mayoría improvisados ​​por tratar de agregar la funcionalidad en un proyecto y por leer esta publicación, definitivamente todavía es difícil.

Veo dos áreas en las que DRF debe considerar ETags: uso en vistas y cómo obtener la representación única de la versión de una instancia.

Puntos de vista

OBTENER
Las solicitudes GET simplemente necesitan servir los objetos ETag en el encabezado apropiado. Un cambio de una línea a RetrieveModelMixin puede agregar fácilmente esto:

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)

PONER, PARCHE, BORRAR
Se podría realizar una verificación general para actualizar los verbos HTTP en la vista dispatch o posiblemente extraerla en otro método, ya que deberá verificar si los ETags están activados (consulte la sección de opciones a continuación):

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

Luego, una verificación más detallada después de recuperar el objeto para ver si la solicitud cree que está mirando el correcto:

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

Representación única de una versión de instancia

No creo que la generación real de ETag de un objeto deba ser un problema de DRF. He estado probando usando el tiempo de época del campo updated de mi objeto, pero pude ver fácilmente que necesita ser más complejo.

Propongo que DRF busque obj.etag de forma predeterminada, pero es configurable usando el flujo CBV normal, por ejemplo, get_etag() y etag_var = 'get_my_objects_etag' .

También necesitaremos hacer cumplir los ETags que se recuperan de los objetos como una cadena, ya que estamos comparando con un encabezado y tratar de interpretar el tipo sería doloroso en el mejor de los casos.

Opciones

  • Configuración global (como con serializadores, etc.) para activar o desactivar el uso de ETag.
  • Dos configuraciones en Vistas:

    • use_etags (o algo similar) - un booleano

    • etag_var - cadena de un nombre de función que podemos getattr en el objeto en cuestión

@ghickman : me gustaría ver el comportamiento para determinar ETags y LastModified similar al de las otras clases conectables. Es decir. Tenga algo como:

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

El almacenamiento en caché de firmas debe tratar tanto con ETag como con LastModified, y hay dos cosas diferentes que queremos proporcionar:

  • Determine un etag y / o la última modificación dada una instancia de objeto.
  • Determine de forma preventiva un etag y / o la última modificación dada la solicitud entrante.

Habría un BaseCacheLookup , con dos firmas de método que podrían verse algo así:

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

Dado un objeto, devuelve dos tuplas de (etag, last modified) , cualquiera de las cuales puede ser simplemente ninguna.
Si la solicitud entrante contiene un encabezado If-Modified-Since o If-None-Match coincidente, se devolverá una respuesta 304 Not Modified. Si la respuesta entrante contiene un If-Match o If-Unmodified-Since coincidente, se devolverá una respuesta 412 Precondition Failed.

Esto permitiría que CacheLookupClass coincida con la implementación que ha descrito, pero también con otras variantes.

También puede aplicar varias clases de búsqueda de caché, con diferente granularidad modificada por última vez, por ejemplo,
incluya un GlobalLastModifiedLookup además de un ObjectETagLookup . Eso permitiría que la vista regrese de forma preventiva antes de realizar cualquier llamada a la base de datos si no se han realizado escrituras desde la copia en caché. (Incluso políticas realmente básicas como esa podrían marcar una gran diferencia si está utilizando el almacenamiento en caché del lado del servidor con Varnish)

¿Le parece razonable el lado de la clase enchufable?

No había pensado en LastModified porque no lo estoy usando en mi implementación actual, pero definitivamente tiene sentido incluirlo dado su propósito.

Las clases conectables suenan como una gran idea, especialmente si incluimos implementaciones de LastModified y ETag como ejemplos básicos. Me gusta la idea de que el almacenamiento en caché GET sería muy fácil de activar con cambios mínimos en un proyecto.

Preferiría dividir la generación etag y last_modified en dos métodos (de esos nombres) que, como sugirió, devuelven None cuando no están implementados. Los backends de CacheLookup podrían optar por implementar uno y / o el otro. Siempre podríamos proporcionar un método de utilidad por conveniencia ( cachable_obj_repr o unique_obj_repr ¿tal vez?) Que combinara los dos si creía que sería útil.

tl; dr sí, el lado de la clase conectable suena razonable y debería brindar una flexibilidad mucho mayor. Estoy feliz de comenzar a escribir el parche para esto.

Hola a todos. Si está interesado, he implementado un enfoque diferente para el soporte de etag en mi biblioteca de extensiones http://chibisov.github.io/drf-extensions/docs/

@chibisov Neato. Realmente deberíamos terminar el # 1019, así que tenemos algún lugar en los documentos para vincular a paquetes como este.

Cerrando esto como # 1019 se ha cerrado y el paquete de @chibisov aparece en la lista.

Este fue marcado como 3.3 a propósito, ya que me gustaría que le diéramos una dirección formal sobre esto en algún momento. No estoy muy preocupado si decidimos dejar esto cerrado, pero ha estado en mi hoja de ruta interna.

Desafortunadamente, la implementación predeterminada y la documentación sobre la funcionalidad Etag en drf-extensions son simplemente incorrectas y están peligrosamente llenas de errores. Cambia el Etag si el _request_ cambia, no si el _response_ cambia. Que es exactamente lo que desea para el almacenamiento en caché del lado del servidor y exactamente lo que no desea para un Etag.

@mbox, lo mejor sería abrir un problema sobre esto en drf-extensions o si cree que es un problema "central" de DRF abierto aquí. Tenga en cuenta que una prueba fallida sería un buen comienzo para que analicemos el problema.

@mbox @xordoquy
Acabo de enviar un PR a drf-extensions (https://github.com/chibisov/drf-extensions/pull/171) que permite un control de concurrencia optimista para manipular recursos a través de la API de DRF. Está usando un hash semántico de todos los campos de objeto e incluí una aplicación de prueba con fines de demostración. Ha sido probado contra DRF> = 3.3.1 y django> = 1.8 con Python 2.7, 3.4, 3.5.

Gracias

Solo una nota para los futuros lectores: he creado un paquete pequeño para usar decoradores condicionales de Django junto con DRF. Entonces, si estás interesado:
https://github.com/jozo/django-rest-framework-condition

¿Fue útil esta página
0 / 5 - 0 calificaciones