Django-rest-framework: Ajoutez/validez facilement des etags.

Créé le 29 juin 2011  ·  24Commentaires  ·  Source: encode/django-rest-framework

Django a un super décorateur etag/condition/last_modified. Cela ne fonctionne pas avec les vues basées sur les classes drf, car vous ne pouvez pas décorer « get » avec elles. Étant donné que get renvoie un objet qui n'est pas une réponse http, il n'y a aucun moyen d'ajouter l'en-tête etag à la réponse.

J'aimerais voir un moyen de le faire à partir de drf. Je pense à quelque chose du genre d'une méthode remplaçable sur une ressource, ou d'une vue (ou d'un mixin) qui peut être utilisée pour générer l'etag.

L'autre façon de le faire dans Django est d'utiliser le middleware, mais il ne peut pas raccourcir l'exécution du corps de la vue comme le peut le décorateur.

Enhancement

Commentaire le plus utile

Malheureusement, l'implémentation par défaut et la documentation sur la fonctionnalité Etag dans drf-extensions sont tout simplement erronées et dangereusement boguées. Il modifie l'Etag si la _request_ change, pas si la _response_ change. C'est exactement ce que vous voulez pour la mise en cache côté serveur, et exactement ce que vous ne voulez pas pour un Etag.

Tous les 24 commentaires

Très heureux pour les commentaires à ce sujet, cependant.

Ok, donc, au départ, ce que j'ai écrit était ceci ....

Cool, ouais, j'aimerais vraiment voir ça dedans.

Coupla pensées - vous devriez pouvoir utiliser simplement View.add_header, plutôt que le View._ETAG que vous avez actuellement.
(Et il semble que .add_header devrait probablement être déplacé dans la classe ResponseMixin.)

Deuxièmement, j'aimerais voir les décors @condition , @etag et @last_modified peuvent probablement être à peu près un clone direct de https://github.com/django/django/blob/master/django/views/decorators /http.py , remplaçant juste quelques add_header et ErrorResponses

Mais j'examinais un peu plus les choses...

Et peut-être que ce n'est pas tout à fait la bonne voie à suivre après tout...

Vous pouvez en fait renvoyer des HttpResponses à partir des vues du framework REST, elles n'obtiennent tout simplement pas tous les éléments habituels de négociation/sérialisation de contenu appliqués. Les décorateurs @last_modified , @etag et @condition ne renvoient que des HttpResponses vides, ce n'est donc pas vraiment un problème.

Donc, ce que je pense, c'est que si nous ajoutons simplement __setitem__ __getitem__ et has_header à la classe Response, alors je pense que Django existe @last_modified , @etag décorateurs @condition devraient fonctionner correctement sur une vue de framework REST _tant que_ la vue utilise le style return Response(status, data) plutôt que le style return data .

Évidemment, ce serait utile si nous documentions cela, mais cela pourrait être plus logique que de devoir reproduire quelque chose que Django fait déjà.

Qu'en penses-tu?

Il peut y avoir un problème avec l'utilisation des décorateurs django : je ne suis pas sûr qu'ils fonctionnent avec des méthodes, uniquement des fonctions nues. Le décorateur que j'ai écrit était fortement basé sur celui que j'ai trouvé via StackOverflow juste pour ce cas.

Ce n'est peut-être pas le cas, auquel cas cette solution semble supérieure.

Cela dit, j'ai renvoyé le style de données de retour, car il est moins passe-partout, et je ne renvoie généralement que les objets que je souhaite sérialiser en tant que json. Nous pouvons peut-être le faire fonctionner dans les deux sens.

Une autre option peut être un mixin qui les ajoute à la classe View.

Il me vient également à l'esprit que les décorateurs django peuvent ne pas faire ce qu'il faut en ce qui concerne les requêtes conditionnelles PUT, POST et DELETE. Je viens de soumettre un correctif à sinatra pour résoudre ce problème.

Ignorez le dernier bit : clairement je n'avais pas lu le code correctement.

En fait, il y a un point ici : ces décorateurs pourraient ne pas fonctionner avec ATM sur _methods_, car ils ont l'argument supplémentaire 'self'. Je vais probablement examiner cela et soumettre un ticket à Django, car ils devraient également fonctionner avec les CBV ...

Ah, d'accord - je vois le @method_decorator maintenant... https://docs.djangoproject.com/en/dev/topics/class-based-views/#decorating -class-based-views
Donc, je suppose que ceci pour que cela soit fermé, nous avons besoin de :

  1. Un peu de peaufinage à la réponse
  2. Quelques documentations légères

Je suis sûr que j'ai regardé dans ce document, mais je n'ai pas vu ce décorateur !

J'ai essayé d'utiliser le décorateur django et le résultat initial était vraiment étrange, avec des informations qui ne devraient appartenir qu'aux vues apparaissant dans des fonctions etag sans rapport.

Après quelques heures à essayer de l'améliorer un peu, j'ai trouvé une solution qui a résolu mes problèmes et semble assez générale. Que pensez-vous de ceci:

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

En espérant donner un nouveau souffle à ce problème maintenant que nous sommes sur les versions 2.x.

Mes pensées ici sont pour la plupart bricolées en essayant d'ajouter la fonctionnalité dans un projet et en lisant ce post, donc définitivement encore approximatif.

Je vois deux domaines où DRF doit prendre en compte les ETags - l'utilisation dans les vues et comment obtenir la représentation unique de la version d'une instance.

Vues

AVOIR
Les requêtes GET doivent simplement servir les objets ETag dans l'en-tête approprié. Un changement d'une ligne en RetrieveModelMixin peut facilement ajouter ceci :

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)

METTRE, CORRIGER, SUPPRIMER
Une vérification générale de la mise à jour des verbes HTTP peut être effectuée dans le dispatch ou éventuellement extraite dans une autre méthode car elle devra vérifier si les ETags sont activés (voir la section options ci-dessous) :

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

Ensuite, une vérification plus détaillée après avoir récupéré l'objet pour voir si la requête pense qu'elle regarde le bon :

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

Représentation unique d'une version d'instance

Je ne pense pas que la génération réelle de l'ETag d'un objet devrait être le problème de DRF. J'ai testé en utilisant l'époque du champ updated de mon objet, mais je pouvais facilement voir que cela devait être plus complexe.

Je propose que DRF recherche obj.etag par défaut mais il est configurable en utilisant le flux CBV normal, par exemple get_etag() et etag_var = 'get_my_objects_etag' .

Nous devrons également imposer que les ETags soient récupérés à partir des objets sous forme de chaîne, car nous comparons avec un en-tête et essayer d'interpréter le type serait au mieux pénible.

Options

  • Paramètre global (comme avec les sérialiseurs, etc.) pour activer ou désactiver l'utilisation des ETags.
  • Deux paramètres sur les vues :

    • use_etags (ou quelque chose de similaire) - un booléen

    • etag_var - chaîne d'un nom de fonction que l'on peut getattr sur l'objet en question

@ghickman - J'aimerais que le comportement pour déterminer les ETags et LastModified ressemble à celui des autres classes enfichables. C'est à dire. Avoir quelque chose comme :

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

La mise en cache des signatures doit traiter à la fois les ETags et les LastModified, et nous voulons fournir deux choses différentes :

  • Déterminer un etag et/ou la dernière modification en fonction d'une instance d'objet.
  • Déterminer à titre préventif un etag et/ou une dernière modification compte tenu de la demande entrante.

Il y aurait un BaseCacheLookup , avec deux signatures de méthode qui pourraient ressembler à quelque chose comme :

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

Étant donné qu'un objet renvoie un double de (etag, last modified) , l'un ou l'autre pouvant être simplement aucun.
Si la demande entrante contient un en-tête If-Modified-Since ou If-None-Match correspondant, une réponse 304 Not Modified sera renvoyée. Si la réponse entrante contient une correspondance If-Match ou If-Unmodified-Since, une réponse 412 Precondition Failed sera renvoyée.

Cela permettrait à une CacheLookupClass correspondant à l'implémentation que vous avez décrite, mais également à d'autres variantes.

Vous pouvez également appliquer plusieurs classes de recherche de cache, à différentes granularités de dernière modification, par exemple,
inclure un GlobalLastModifiedLookup en plus d'un ObjectETagLookup . Cela permettrait à la vue de revenir de manière préventive avant d'effectuer des appels à la base de données si aucune écriture n'avait été effectuée depuis la copie en cache. (Même des politiques vraiment basiques comme celle-ci pourraient faire une énorme différence si vous utilisez la mise en cache côté serveur avec Varnish)

Le côté classe enfichable de ce son vous semble-t-il raisonnable ?

Je n'avais pas pensé à LastModified car je ne l'utilise pas dans mon implémentation actuelle, mais il est tout à fait logique de l'inclure étant donné son objectif.

Les classes enfichables semblent être une excellente idée, surtout si nous incluons les implémentations LastModified et ETag comme exemples de base. J'aime l'idée que la mise en cache GET soit très facile à activer avec des modifications minimes d'un projet.

Je préférerais diviser la génération etag et last_modified en deux méthodes (de ces noms) qui, comme vous l'avez suggéré, renvoient None lorsqu'elles ne sont pas implémentées. Les backends CacheLookup pourraient alors choisir d'implémenter l'un et/ou l'autre. Nous pourrions toujours fournir une méthode utilitaire pour plus de commodité ( cachable_obj_repr ou unique_obj_repr peut-être ?)

tl;dr oui le côté classe enfichable semble raisonnable et devrait donner beaucoup plus de flexibilité. Je suis heureux de commencer à écrire le patch pour cela.

Bonjour à tous. Si vous êtes intéressé, j'ai implémenté une approche différente pour la prise en charge d'etag dans ma bibliothèque d'extensions http://chibisov.github.io/drf-extensions/docs/

@chibisov Neato. Nous devrions vraiment terminer le #1019, nous avons donc quelque part dans la documentation pour lier des packages comme celui-ci.

En fermant ceci car le #1019 a été fermé et le package de

Celui-ci a été volontairement classé 3.3, car j'aimerais en quelque sorte que nous donnions des directives formelles à ce sujet à un moment donné. Pas très inquiet si nous choisissons de laisser cela fermé, mais cela a été sur ma feuille de route interne.

Malheureusement, l'implémentation par défaut et la documentation sur la fonctionnalité Etag dans drf-extensions sont tout simplement erronées et dangereusement boguées. Il modifie l'Etag si la _request_ change, pas si la _response_ change. C'est exactement ce que vous voulez pour la mise en cache côté serveur, et exactement ce que vous ne voulez pas pour un Etag.

@mbox, la meilleure chose serait d'ouvrir un problème à ce sujet sur drf-extensions ou si vous pensez qu'il s'agit d'un problème "principal" DRF ouvert ici. Veuillez noter qu'un échec au test serait un bon début pour nous permettre d'examiner le problème.

@mbox @xordoquy
Je viens de soumettre un PR à drf-extensions (https://github.com/chibisov/drf-extensions/pull/171) qui permet un contrôle de simultanéité optimiste pour la manipulation des ressources via l'API DRF. Il utilise un hachage sémantique de tous les champs d'objet et j'ai inclus une application de test à des fins de démonstration. Il a été testé avec DRF>=3.3.1 et django>=1.8 avec Python 2.7, 3.4, 3.5.

Merci

Juste une note pour les futurs lecteurs - j'ai créé un petit package pour utiliser les décorateurs conditionnels de Django avec DRF. Alors si ça t'intéresse :
https://github.com/jozo/django-rest-framework-condition

Cette page vous a été utile?
0 / 5 - 0 notes