Alternatives RESTful à DELETE Request Body

93

Alors que la spécification HTTP 1.1 semble autoriser les corps de message sur les requêtes DELETE , elle semble indiquer que les serveurs devraient l'ignorer car il n'y a pas de sémantique définie pour cela.

4.3 Corps du message

Un serveur DEVRAIT lire et transmettre un corps de message sur toute demande; si la méthode de demande n'inclut pas la sémantique définie pour un corps d'entité, alors le corps de message DEVRAIT être ignoré lors du traitement de la demande.

J'ai déjà passé en revue plusieurs discussions connexes sur ce sujet sur le SO et au-delà, telles que:

La plupart des discussions semblent s'accorder sur le fait que fournir un corps de message sur un DELETE peut être autorisé , mais n'est généralement pas recommandé.

De plus, j'ai remarqué une tendance dans diverses bibliothèques clientes HTTP où de plus en plus d'améliorations semblent être enregistrées pour que ces bibliothèques prennent en charge les corps de requête sur DELETE. La plupart des bibliothèques semblent obliger, bien que parfois avec un peu de résistance initiale.

Mon cas d'utilisation demande l'ajout de certaines métadonnées requises sur un DELETE (par exemple, la «raison» de la suppression, ainsi que d'autres métadonnées requises pour la suppression). J'ai examiné les options suivantes, dont aucune ne semble tout à fait appropriée et conforme aux spécifications HTTP et / ou aux meilleures pratiques REST:

  • Corps du message - La spécification indique que les corps de message sur DELETE n'ont aucune valeur sémantique; pas entièrement pris en charge par les clients HTTP; pas une pratique courante
  • En-têtes HTTP personnalisés - L'exigence d'en-têtes personnalisés est généralement contraire aux pratiques standard ; leur utilisation est incompatible avec le reste de mon API, dont aucune ne nécessite d'en-têtes personnalisés; de plus, aucune bonne réponse HTTP disponible pour indiquer de mauvaises valeurs d'en-tête personnalisées (probablement une question distincte)
  • En-têtes HTTP standard - Aucun en-tête standard n'est approprié
  • Paramètres de requête - L'ajout de paramètres de requête modifie en fait l'URI de la requête en cours de suppression; contre les pratiques standard
  • Méthode POST - (par exemple POST /resourceToDelete { deletemetadata }) POST n'est pas une option sémantique pour la suppression; POST représente en fait l' action opposée souhaitée (c'est-à-dire que POST crée des subordonnés de ressources; mais je dois supprimer la ressource)
  • Méthodes multiples - La division de la demande DELETE en deux opérations (par exemple PUT delete métadonnées, puis DELETE) divise une opération atomique en deux, laissant potentiellement un état incohérent. La raison de la suppression (et les autres métadonnées associées) ne font pas partie de la représentation de la ressource elle-même.

Ma première préférence serait probablement d'utiliser le corps du message, ensuite les en-têtes HTTP personnalisés; cependant, comme indiqué, ces approches présentent certains inconvénients.

Existe-t-il des recommandations ou des meilleures pratiques en ligne avec les normes REST / HTTP pour inclure ces métadonnées requises dans les demandes DELETE? Y a-t-il d'autres alternatives que je n'ai pas envisagées?

shelley
la source
2
Certaines implémentations comme Jerseyne permettent pas le corps des deleterequêtes.
basiljames

Réponses:

44

Malgré certaines recommandations de ne pas utiliser le corps du message pour les demandes DELETE, cette approche peut être appropriée dans certains cas d'utilisation. C'est l'approche que nous avons fini par utiliser après avoir évalué les autres options mentionnées dans la question / réponses, et après avoir collaboré avec les consommateurs du service.

Bien que l'utilisation du corps du message ne soit pas idéale, aucune des autres options ne convenait parfaitement non plus. Le corps de la requête DELETE nous a permis d'ajouter facilement et clairement une sémantique autour des données / métadonnées supplémentaires nécessaires pour accompagner l'opération DELETE.

Je serais toujours ouvert à d'autres réflexions et discussions, mais je voulais boucler la boucle sur cette question. J'apprécie les réflexions et les discussions de chacun sur ce sujet!

shelley
la source
12
C'est une mauvaise idée. Si vous décidez plus tard d'utiliser un service d'accélération HTTP comme Akamai EdgeConnect, cela vous posera des problèmes. Je sais pertinemment que EdgeConnect supprime les corps des requêtes HTTP DELETE (car ils consomment de la bande passante sont probablement invalides). Il est également probable que des services similaires fassent de même (voir la fonction d'accélération de Kindle et d'autres services de type CDN). Vous devriez probablement repenser pour ne pas utiliser de verbes HTTP pour votre service. La plupart des API n'ont pas de sens en utilisant les verbes HTTP / REST classique et les problèmes de transport des verbes HTTP sont très difficiles à résoudre.
Gabe
3
Je suis d'accord avec @Gabe, envoyer un corps avec des méthodes qui n'ont pas de corps par définition est un moyen infaillible de perdre des données au hasard lorsque vos bits traversent les tuyaux Internet, et vous aurez du mal à le déboguer.
Nicholas Shanks
3
Ces commentaires contre l'utilisation de DELETE ne sont pas pertinents tant qu'ils n'ont pas résolu les problèmes très valables du PO. Je fais de mon mieux pour adhérer à l'esprit de REST, mais une suppression sans concurrence optimiste et une suppression qui n'a pas d'opération par lots atomiques n'est pas pratique dans des situations réelles. Il s'agit d'une grave lacune du modèle REST.
Quarkly
Je suis avec @Quarkly à ce sujet. Je ne comprends pas quelle est l'idée RESTFUL sur la façon dont nous devrions vérifier la concurrence, etc. Les contrôles de concurrence n'appartiennent pas au client.
Dirk Wessels le
13

Ce que vous semblez vouloir est l'une des deux choses, dont aucune n'est pure DELETE:

  1. Vous avez deux opérations, une PUTde la raison de la suppression suivie par une DELETEde la ressource. Une fois supprimé, le contenu de la ressource n'est plus accessible à personne. La «raison» ne peut pas contenir de lien hypertexte vers la ressource supprimée. Ou,
  2. Vous essayez de modifier une ressource de state=activeà state=deleteden utilisant la DELETEméthode. Les ressources avec état = supprimé sont ignorées par votre API principale mais peuvent toujours être lisibles par un administrateur ou une personne ayant accès à la base de données. Ceci est autorisé - il DELETEn'est pas nécessaire d'effacer les données de sauvegarde d'une ressource, mais uniquement de supprimer la ressource exposée à cet URI.

Toute opération qui nécessite un corps de message sur une DELETErequête peut être décomposée en, au plus général, unPOST pour effectuer toutes les tâches nécessaires avec le corps du message, et a DELETE. Je ne vois aucune raison de casser la sémantique de HTTP.

Nicholas Shanks
la source
2
Que se passe-t-il si la PUTraison réussit et que la DELETEressource échoue? Comment éviter un état incohérent?
Lightman
1
@Lightman the PUT spécifie uniquement l'intention. Il peut exister sans DELETE correspondant, ce qui indiquerait que quelqu'un voulait supprimer mais que cela a échoué ou qu'il a changé d'avis. L'inversion de l'ordre des appels permettrait également aux SUPPRESSIONS de se produire sans raison - la fourniture d'une raison serait alors considérée comme une simple annotation. C'est pour ces deux raisons que je recommanderais d'utiliser l'option 2 ci-dessus, c'est-à-dire changer l'état de l'enregistrement sous-jacent de manière à faire disparaître la ressource HTTP de son URL actuelle. Un garbage collector / admin peut ensuite purger les enregistrements
Nicholas Shanks
7

Compte tenu de la situation dans laquelle vous vous trouvez, j'adopterais l'une des approches suivantes:

  • Envoyer un PUT ou un PATCH : je déduis que l'opération de suppression est virtuelle, par la nature de la nécessité d'une raison de suppression. Par conséquent, je pense que la mise à jour de l'enregistrement via une opération PUT / PATCH est une approche valide, même si ce n'est pas une opération DELETE en soi.
  • Utilisez les paramètres de requête : l'URI de la ressource n'est pas modifiée. Je pense en fait que c'est aussi une approche valable. La question que vous avez liée parlait de ne pas autoriser la suppression si le paramètre de requête était manquant. Dans votre cas, j'aurais juste une raison par défaut si la raison n'est pas spécifiée dans la chaîne de requête. La ressource sera toujours resource/:id. Vous pouvez le rendre détectable avec des en-têtes de lien sur la ressource pour chaque raison (avec une relbalise sur chacun pour identifier la raison).
  • Utilisez un point de terminaison distinct par raison : en utilisant une URL comme resource/:id/canceled. Cela modifie réellement l'URI de demande et n'est certainement pas RESTful. Encore une fois, les en-têtes de lien peuvent rendre cela détectable.

N'oubliez pas que REST n'est ni une loi ni un dogme. Pensez-y davantage comme un guide. Donc, s'il est judicieux de ne pas suivre les instructions pour votre domaine de problème, ne le faites pas. Assurez-vous simplement que vos consommateurs d'API sont informés de l'écart.

codeprogression
la source
En ce qui concerne l'utilisation des paramètres de requête, je crois comprendre que la requête fait partie de Request-URI conformément à la section 3.2 , et donc l'utilisation de cette approche (ou de même, des points de terminaison séparés) va à l'encontre de la définition de la méthode DELETE , de sorte que "resource identifié par Request-URI "est supprimé.
shelley
La ressource est identifiée par le chemin uri. Ainsi, un GET à /orders/:idrenverrait la même ressource que /orders/:id?exclude=orderdetails. La chaîne de requête ne donne que des indications au serveur - dans ce cas pour exclure les détails de la commande dans la réponse (si pris en charge). De même, si vous envoyez DELETE à /orders/:idou /orders/:id?reason=canceledou /orders/:id?reason=bad_credit, vous agissez toujours sur la même ressource sous-jacente. Pour conserver une «interface uniforme», j'aurais une raison par défaut pour que l'envoi du paramètre de requête ne soit pas nécessaire.
codeprogression
@shelley Vous avez raison dans vos préoccupations concernant les chaînes de requête. La chaîne de requête fait partie de l'URI. L'envoi d'une demande DELETE à /foo?123signifie que vous supprimez une ressource différente de celle si vous deviez envoyer DELETE à /foo?456.
Nicholas Shanks
@codeprogression Désolé, mais la plupart de ce que vous dites est faux. La ressource est identifiée par l'URI entier, pas seulement par le chemin. Différentes chaînes de requête sont des ressources différentes (au sens HTTP du mot «ressource»). De plus, une raison par défaut n'est pas requise pour une interface uniforme. Ce terme fait référence à l'utilisation de GET, PUT, POST, PATCH et DELETE de la manière dont HTTP les a définis. Le point commun se situe entre les fournisseurs (fournisseurs d'agents utilisateurs, fournisseurs d'API, fournisseurs de proxy de mise en cache, FAI, etc.) et non au sein de sa propre API (bien que cela aussi devrait être uniforme dans la conception pour la santé mentale de ses utilisateurs!).
Nicholas Shanks
@Nicholas Je ne comprends pas votre besoin d'argumenter une discussion qui s'est terminée il y a trois ans. La réponse et les commentaires que j'ai faits sont valides et corrects dans une vue centrée sur REST. REST n'est pas HTTP (ni aucune implémentation de fournisseur de HTTP). Dans le contexte de REST, les ressources sont les mêmes. Et comme je l'ai dit dans ma réponse, REST n'est pas une loi ou un dogme, mais une orientation.
codeprogression
0

Je vous suggère d'inclure les métadonnées requises dans le cadre de la hiérarchie URI elle-même. Un exemple (naïf):

Si vous devez supprimer des entrées en fonction d'une plage de dates, au lieu de transmettre la date de début et la date de fin dans le corps ou en tant que paramètres de requête, structurez l'URI de manière à transmettre les informations requises dans le cadre de l'URI.

par exemple

DELETE /entries/range/01012012/31122012 - Supprimer toutes les entrées entre le 01 janvier 2012 et le 31 décembre 2012

J'espère que cela t'aides.

Suresh Kumar
la source
5
Ne couvre pas les cas tels que l'envoi d'un motif de suppression, c'est-à-dire le champ de confirmation.
Kugel le
3
Sensationnel. c'est une idée terrible. Si vous avez trop de métadonnées, cela gonflera les restrictions de taille sur l'URI.
Balaji Boggaram Ramanarayan
1
Cette approche ne suit pas les pratiques RESTful et n'est pas recommandée car vous aurez une structure d'URI alambiquée. Les points de terminaison se confondent avec l'identification des ressources imbriquée par rapport aux métadonnées et, avec le temps, deviendront un cauchemar de maintenance à mesure que votre API change. Il est beaucoup plus préférable d'avoir le rangespécifié dans les paramètres de requête ou la charge utile qui est la viande de cette question: pour comprendre l'approche des meilleures pratiques au problème que je dirais n'est pas ceci.
digitaldreamer
@digitaldreamer - Je ne comprends pas ce que vous entendez par structure URI alambiquée? En outre, il s'agit d'un DELETE HTTP, donc la charge utile n'est pas une option mais les paramètres de requête oui.
Suresh Kumar