web-dev-qa-db-fra.com

Évitez les POST en double avec REST

J'utilise POST dans une API REST pour créer des objets. De temps en temps, le serveur crée l'objet, mais le client est déconnecté) avant de recevoir le 201 Created réponse. Le client ne voit qu'une requête POST POST échouée, et réessaye plus tard, et le serveur crée joyeusement un objet en double ...

D'autres ont dû avoir ce problème, non? Mais je google autour, et tout le monde semble simplement l'ignorer.

J'ai 2 solutions:

A) Utilisez PUT à la place et créez l'ID (GU) sur le client.

B) Ajoutez un GUID à tous les objets créés sur le client et demandez au serveur d'appliquer leur UNIQUE- ness.

A ne correspond pas très bien aux frameworks existants et B ressemble à un hack. Comment les autres résolvent-ils cela, dans le monde réel?

Éditer:

Avec Backbone.js, vous pouvez définir un GUID comme identifiant lorsque vous créez un objet sur le client. Lorsqu'il est enregistré, Backbone fera une demande PUT. Faites votre REST backend handle PUT to id inexistant, et vous êtes prêt.

37
geon

J'utilise toujours B - détection des doublons en raison de tout problème du côté serveur.

8
Kyle Banerjee

Une autre solution qui a été proposée pour cela est POST Once Exactly (POE) , dans laquelle le serveur génère des URI à usage unique POST URI qui, lorsqu'ils sont utilisés plusieurs fois, oblige le serveur à renvoyer une réponse 405.

Les inconvénients sont que 1) le brouillon POE a pu expirer sans autre progrès sur la normalisation, et donc 2) sa mise en œuvre nécessite des changements aux clients pour utiliser les nouveaux en-têtes POE, et du travail supplémentaire par les serveurs pour implémenter la sémantique POE.

En recherchant sur Google, vous pouvez trouver quelques API qui l'utilisent.

Une autre idée que j'ai eue pour résoudre ce problème est celle d'un POST conditionnel, que j'ai décrit et demandé des commentaires sur ici .

Il ne semble pas y avoir de consensus sur la meilleure façon d'empêcher la création de ressources en double dans les cas où la génération d'URI unique ne peut pas être mise sur le client et donc POST est nécessaire.

8
Chris Toomey

La détection des doublons est compliquée et peut devenir très compliquée. De véritables requêtes distinctes mais similaires peuvent arriver en même temps, peut-être parce qu'une connexion réseau est rétablie. Et les demandes répétées peuvent arriver des heures ou des jours d'intervalle si une connexion réseau est interrompue.

Toute la discussion sur les identifiants dans les autres réponses vise à donner une erreur en réponse à des demandes en double, mais cela incitera normalement un client à obtenir ou à générer un nouvel identifiant et à réessayer.

Un modèle simple et robuste pour résoudre ce problème est le suivant: les applications serveur doivent stocker toutes les réponses aux demandes non sécurisées, puis, si elles voient une demande en double, elles peuvent répéter la réponse précédente et ne faites rien d'autre. Faites cela pour toutes les demandes dangereuses et vous résoudrez un tas de problèmes épineux. "Dupliquer" est déterminé par un identifiant au niveau de l'application, soit un GUID ou un numéro de séquence généré par le serveur généré par le client. Dans ce deuxième cas, une demande-réponse doit être dédiée uniquement à l'échange l'id. J'aime cette solution parce que l'étape dédiée fait penser aux clients qu'ils obtiennent quelque chose de précieux dont ils doivent s'occuper. S'ils peuvent générer leurs propres identifiants, ils sont plus susceptibles de mettre cette ligne dans la boucle et chaque sanglant La demande aura un nouvel identifiant.

En utilisant ce schéma, tous les POST sont vides et POST est utilisé uniquement pour récupérer un identifiant d'action. Tous les PUT et DELETE sont entièrement idempotents: les requêtes successives obtiennent la même réponse (stockée et rejouée) et la cause rien de plus. La meilleure chose à propos de ce modèle est sa qualité Kung-Fu (Panda). Il prend une faiblesse: la propension des clients à répéter une demande chaque fois qu'ils obtiennent une réponse inattendue, et la transforme en force: - )

J'ai un petit document google ici si quelqu'un s'en soucie.

5
bbsimonbb

Vous pouvez essayer une approche en deux étapes. Vous demandez la création d'un objet qui renvoie un jeton. Ensuite, dans une deuxième demande, demandez un statut à l'aide du jeton. Jusqu'à ce que le statut soit demandé à l'aide du jeton, vous le laissez dans un état "intermédiaire".

Si le client se déconnecte après la première demande, il n'aura pas le jeton et l'objet restera "mis en scène" indéfiniment ou jusqu'à ce que vous le supprimiez avec un autre processus.

Si la première demande réussit, vous disposez d'un jeton valide et vous pouvez récupérer l'objet créé autant de fois que vous le souhaitez sans qu'il ne recrée quoi que ce soit.

Il n'y a aucune raison pour que le jeton ne puisse pas être l'ID de l'objet dans le magasin de données. Vous pouvez créer l'objet lors de la première demande. La deuxième demande met à jour le champ "par étapes".

3
Travis Parks

Identifiants émis par le serveur

Si vous avez affaire au cas où c'est le serveur qui délivre les identificateurs, créez l'objet dans un état temporaire et intermédiaire. (Il s'agit d'une opération intrinsèquement non idempotente, donc elle doit être effectuée avec POST.) Le client doit ensuite effectuer une autre opération sur celui-ci pour le transférer de l'état intermédiaire à l'état actif/préservé (qui pourrait être un PUT de une propriété de la ressource, ou un POST approprié à la ressource).

Chaque client devrait être en mesure d'obtenir une liste de leurs ressources dans l'état intermédiaire d'une manière ou d'une autre (peut-être mélangé avec d'autres ressources) et devrait pouvoir SUPPRIMER les ressources qu'ils ont créées s'ils ne sont encore que intermédiaires. Vous pouvez également supprimer périodiquement les ressources intermédiaires qui sont inactives depuis un certain temps.

Vous n'avez pas besoin de révéler les ressources intermédiaires d'un client à un autre client; ils ne doivent exister au niveau mondial qu'après l'étape de confirmation.

Identifiants émis par le client

L'alternative est que le client émette les identifiants. Ceci est principalement utile lorsque vous modélisez quelque chose comme un magasin de fichiers, car les noms de fichiers sont généralement significatifs pour le code utilisateur. Dans ce cas, vous pouvez utiliser PUT pour créer la ressource comme vous pouvez le faire de manière idempotente.

L'inconvénient est que les clients sont capables de créer des ID, et donc vous n'avez aucun contrôle sur les ID qu'ils utilisent.

2
Donal Fellows

Il existe une autre variante de ce problème. Le fait qu'un client génère un identifiant unique indique que nous demandons à un client de résoudre ce problème pour nous. Prenons un environnement dans lequel nous avons des API exposées publiquement et comptons des centaines de clients intégrant ces API. Pratiquement, nous n'avons aucun contrôle sur le code client et l'exactitude de sa mise en œuvre de l'unicité. Par conséquent, il serait probablement préférable d'avoir une intelligence pour comprendre si une demande est en double. Une approche simple ici serait de calculer et de stocker la somme de contrôle de chaque demande en fonction des attributs d'une entrée utilisateur, de définir un certain seuil de temps (x minutes) et de comparer chaque nouvelle demande du même client avec celles reçues au cours des x dernières minutes. Si la somme de contrôle correspond, il pourrait s'agir d'une demande en double et ajouter un mécanisme de défi pour un client pour résoudre ce problème. Si un client fait deux requêtes différentes avec les mêmes paramètres en x minutes, il peut être utile de s'assurer que cela est intentionnel même s'il vient avec un identifiant de requête unique. Cette approche peut ne pas convenir à tous les cas d'utilisation, cependant, je pense que cela sera utile dans les cas où l'impact commercial de l'exécution du deuxième appel est élevé et peut potentiellement coûter un client. Considérons une situation de moteur de traitement des paiements où une couche intermédiaire finit par réessayer une demande ayant échoué OR un client double-cliqué, ce qui entraîne la soumission de deux demandes par couche client.

0
Neeraj

Conception

  • Automatique (sans avoir besoin de maintenir une liste noire manuelle)
  • Mémoire optimisée
  • Optimisé pour le disque

Algorithme [solution 1]

  1. REST arrive avec UUID
  2. Le serveur Web vérifie si l'UUID est dans le tableau de la liste noire du cache mémoire (si oui, répondez 409)
  3. Le serveur écrit la demande dans la base de données (si elle n'a pas été filtrée par ETS)
  4. DB vérifie si l'UUID est répété avant d'écrire
  5. Si oui, répondez 409 pour le serveur et mettez la liste noire dans Cache mémoire et disque
  6. Si ce n'est pas répété, écrivez dans DB et répondez 200

Algorithme [solution 2]

  1. REST arrive avec UUID
  2. Enregistrez l'UUID dans la table Cache mémoire (expirez pendant 30 jours)
  3. Le serveur Web vérifie si l'UUID est dans le tableau de la liste noire du cache mémoire [retourner HTTP 409]
  4. Le serveur écrit la demande dans la base de données [retourner HTTP 200]

Dans la solution 2, le seuil de création de la liste noire du cache mémoire est créé UNIQUEMENT en mémoire, donc DB ne sera jamais vérifié pour les doublons. La définition de "duplication" est "toute demande qui arrive dans une période de temps". Nous répliquons également la table Memory Cache sur le disque, nous la remplissons donc avant de démarrer le serveur.

Dans la solution 1, il n'y aura jamais de doublon, car nous archivons toujours le disque UNIQUEMENT une fois avant d'écrire, et s'il est dupliqué, les prochains allers-retours seront traités par le cache mémoire. Cette solution est meilleure pour Big Query, car les demandes ne sont pas imdépuissantes, mais elles sont également moins optimisées.

code de réponse HTTP pour POST lorsque la ressource existe déjà

0
Henry H.