Conséquences du fait que POST n'est pas idempotent (RESTful API))

je me demande si mon approche actuelle a du sens ou s'il y a une meilleure façon de le faire.

j'ai plusieurs situations où je veux créer de nouveaux objets et laisser le serveur assigner un ID à ces objets. Envoyer une demande de courrier semble être la façon la plus appropriée de le faire. Cependant, comme POST n'est pas idempotent, la requête peut se perdre et l'Envoyer à nouveau peut créer un second objet. De plus, les requêtes perdues peuvent être assez courantes puisque l'API est souvent accessible via les réseaux mobiles.

par conséquent, j'ai décidé de diviser le tout en un processus en deux étapes. Envoyer d'abord une requête POST pour créer un nouvel objet qui renvoie l'URI du nouvel objet dans l'en-tête Location. Deuxièmement, exécuter une requête idempotent PUT à l'emplacement fourni pour remplir le nouvel objet avec des données. Si un nouvel objet n'est pas rempli dans les 24 heures, le serveur peut le supprimer par une sorte de traitement par lots.

est-ce que sembler raisonnable ou est-il un une meilleure approche?

Merci

27
demandé sur mibollma 2012-12-21 18:26:16

6 réponses

le seul avantage de la POST-création par rapport à la PUT-création est la génération serveur D'IDs. Je ne pense pas qu'il mérite le manque d'idempotence (et ensuite la nécessité de supprimer les doublons ou les objets vides).

a la place, j'utiliserais un PUT avec un UUID dans L'URL. Grâce aux générateurs UUID vous êtes presque certain que L'ID que vous générez côté client sera côté serveur unique.

32
répondu Aurélien 2012-12-28 13:24:56

eh bien tout dépend, pour commencer, vous devriez parler plus sur URIs, ressources et représentations et ne vous souciez pas des objets.

la méthode POST est conçue pour les requêtes non-idempotent, ou les requêtes avec des effets secondaires, mais elle peut être utilisé pour les requêtes idempotent.

sur le POST de données de formulaire /some_collection/

normalize the natural key of your data (Eg. "lowercase" the Title field for a blog post)
calculate a suitable hash value (Eg. simplest case is your normalized field value)
lookup resource by hash value
if none then
    generate a server identity, create resource
        Respond =>  "201 Created", "Location": "/some_collection/<new_id>" 
if found but no updates should be carried out due to app logic
        Respond => 302 Found/Moved Temporarily or 303 See Other 
        (client will need to GET that resource which might include fields required for updates, like version_numbers)
if found but updates may occur
   Respond => 307 Moved Temporarily, Location: /some_collection/<id> 
   (like a 302, but the client should use original http method and might do automatically) 

une fonction de hachage appropriée pourrait être aussi simple que certains champs concaténés, ou pour les grands champs OU VALEURS une fonction md5 tronquée pourrait être utilisée. Voir [fonction de hachage] pour plus de détails2.

j'ai supposé que vous:

  • besoin d'une identité différente de la valeur qu'une valeur de hachage
  • champs de données utilisés pour l'identité ne peut être changé
14
répondu bdargan 2013-01-02 12:57:48

votre méthode de génération d'ids sur le serveur, dans l'application, dans une requête-réponse dédiée, est très bonne! L'unicité est très importante, mais les clients, comme les prétendants, vont continuer à répéter la demande jusqu'à ce qu'ils réussissent, ou jusqu'à ce qu'ils obtiennent un échec qu'ils sont prêts à accepter (peu probable). Donc vous devez obtenir l'unicité de quelque part, et vous avez seulement deux options. Soit le client, avec un GUID comme Aurélien le suggère, soit le serveur, comme vous le suggérez. J'aime bien le serveur option. Les colonnes d'amorçage dans le DBs relationnel sont une source facilement disponible d'unicité avec un risque zéro de collisions. En 2000, j'ai lu un article préconisant cette solution appelée quelque chose comme "simple Reliable Messaging with HTTP", donc c'est une approche établie à un vrai problème.

en lisant des trucs de repos, vous pourriez être pardonné de penser qu'un tas d'adolescents viennent d'hériter du manoir D'Elvis. Ils discutent passionnément comment réorganiser les meubles, et ils sont hystériques à l'idée qu'ils pourraient avoir besoin d'apporter quelque chose dans la maison. L'utilisation de POST est recommandée parce que c'est là, sans jamais aborder les problèmes avec les requêtes non-idempotent.

Dans la pratique, vous aurez probablement voulez vous assurer que toutes les requêtes dangereuses de votre api sont idempotent, à l'exception nécessaire des demandes de génération d'identité, qui, comme vous le faites remarquer, n'ont pas d'importance. Générer des identités est bon marché et celles qui ne sont pas utilisées sont facilement écartées. Comme un clin d'œil à Reposez-vous, rappelez-vous d'obtenir votre nouvelle identité avec un POST, de sorte qu'il ne soit pas caché et répété partout.

Concernant le débat stérile sur ce idempotent signifie, je dis qu'il doit être tout. Les demandes successives ne doivent pas générer d'effets supplémentaires et doivent recevoir la même réponse que la première demande traitée. Pour mettre en œuvre ceci, vous voudrez stocker toutes les réponses du serveur afin qu'elles puissent être rejouées, et vos identifiants seront des actions d'identification, pas seulement ressources. Tu seras viré du manoir D'Elvis, mais tu auras une api à l'épreuve des bombes.

4
répondu bbsimonbb 2017-08-11 08:14:11

mais maintenant vous avez deux requêtes qui peuvent être perdues? Et le POST peut encore être répété, créant une autre instance de ressource. Ne pas trop penser à des trucs. Il suffit d'avoir le processus de fournée chercher des dupes. Peut-être avoir un certain "accès" Compter des statistiques sur vos ressources pour voir lequel des candidats dupe a été le résultat d'un poste abandonné.

une autre approche: filtrer les messages entrants par rapport à un journal pour voir s'il s'agit d'une répétition. Devrait être facile à trouver: si le contenu du corps d'une demande la même chose que celle d'une demande il y a x fois, considérez-la comme une répétition. Et vous pouvez vérifier des paramètres supplémentaires comme L'IP d'origine, même authentification ...

3
répondu Marjan Venema 2012-12-21 14:52:06

quelle que soit la méthode HTTP que vous utilisez, il est théoriquement impossible de faire une requête idempotent sans générer l'identifiant unique côté client, temporairement (dans le cadre d'un système de vérification des requêtes) ou comme identifiant permanent du serveur. Une requête HTTP perdue ne créera pas de duplicata, bien qu'il y ait une crainte que la requête réussisse à atteindre le serveur, mais que la réponse ne la renvoie pas au client.

Si le client final peut facilement supprimer les doublons et elles ne causent pas de conflits de données inhérents.ce n'est probablement pas assez important pour développer un système ad hoc de prévention de la duplication. Utilisez POST pour la requête et renvoyez au client un statut 201 dans L'en-tête HTTP et l'id unique généré par le serveur dans le corps de la réponse. Si vous avez des données qui montrent que les duplications sont fréquentes ou que tout duplicata cause des problèmes importants, J'utiliserais PUT et je créerais le côté client id unique. Utiliser l'id créé par le client comme id de la base de données - il n'y a aucun avantage à créer un id unique supplémentaire sur le serveur.

3
répondu Chris Broski 2012-12-31 19:26:08

je pense que vous pouvez également réduire la création et la mise à jour de la requête en une seule requête (upsert). Afin de créer une nouvelle ressource, le client affiche une ressource "factory", située par exemple à /factory-url-name. Puis le serveur renvoie l'URI de la nouvelle ressource.

2
répondu freedev 2012-12-21 14:37:07