Je construis une API où l'utilisateur peut demander au serveur d'effectuer plusieurs actions dans une seule requête HTTP. Le résultat est renvoyé sous forme de tableau JSON, avec une entrée par action.
Chacune de ces actions peut échouer ou réussir indépendamment les unes des autres. Par exemple, la première action peut réussir, l'entrée de la deuxième action peut être mal formatée et échouer à valider et la troisième action peut provoquer une erreur inattendue.
S'il y avait une demande par action, je retournerais les codes d'état 200, 422 et 500 respectivement. Mais maintenant, quand il n'y a qu'une seule demande, quel code d'état dois-je retourner?
Quelques options:
Puisque la demande parle d'exécuter la liste de tâches (les tâches sont la ressource dont nous parlons ici), alors si le groupe de tâches a été avancé vers l'exécution (c'est-à-dire, quel que soit le résultat de l'exécution ), il serait alors judicieux que le statut de réponse soit 200 OK
. Sinon, s'il y avait un problème qui empêcherait l'exécution du groupe de tâches, comme l'échec de la validation des objets de tâche, ou si un service requis n'est pas disponible par exemple, alors l'état de la réponse devrait indiquer que Erreur. Après cela, lorsque l'exécution des tâches commence, étant donné que les tâches à effectuer sont répertoriées dans le corps de la demande, je m'attends à ce que les résultats de l'exécution soient répertoriés dans le corps de la réponse.
Vous rencontrez ce dilemme parce que vous vous détournez de ce pour quoi HTTP a été conçu. Vous ne l'interagissez pas pour gérer les ressources, vous l'utilisez plutôt comme moyen d'invocation de méthode à distance (ce qui n'est pas très étrange, mais fonctionne mal sans schéma préconçu).
Cela étant dit, et sans courage pour transformer cette réponse en un long guide réfléchi, voici un schéma d'URI conforme à une approche de gestion des ressources:
/tasks
GET
liste toutes les tâches, paginéesPOST
ajoute une seule tâche/tasks/task/[id]
GET
répond avec un seul objet d'état de tâcheDELETE
annule/supprime une tâche/tasks/groups
GET
répertorie tous les groupes de tâches, paginésPOST
ajoute un groupe de tâches/tasks/groups/group/[id]
GET
répond avec l'état d'un groupe de tâchesDELETE
annule/supprime le groupe de tâchesCette structure parle de ressources, pas de quoi en faire. Ce qui se fait avec les ressources, c'est la préoccupation d'un autre service.
Un autre point important à souligner est qu'il est conseillé de ne pas bloquer très longtemps dans un gestionnaire de requêtes HTTP. Tout comme l'interface utilisateur, une interface HTTP doit être réactive - dans une échelle de temps qui est de quelques ordres de grandeur plus lente (car cette couche traite des E/S).
Passer à la conception d'une interface HTTP qui gère strictement les ressources est probablement aussi difficile que d'éloigner le travail d'un thread d'interface utilisateur lorsqu'un bouton est cliqué. Il nécessite que le serveur HTTP communique avec d'autres services pour exécuter des tâches plutôt que de les exécuter dans le gestionnaire de requêtes. Ce n'est pas une implémentation superficielle, c'est un changement de direction.
Exécution d'une seule tâche et suivi de la progression:
POST /tasks
avec la tâche d'exécuter GET /tasks/task/[id]
jusqu'à ce que l'objet de réponse completed
ait une valeur positive tout en affichant l'état/la progression en coursExécution d'une seule tâche et en attente de son achèvement:
POST /tasks
avec la tâche d'exécuter GET /tasks/task/[id]?awaitCompletion=true
jusqu'à ce que completed
ait une valeur positive (a probablement un délai d'expiration, c'est pourquoi cela devrait être bouclé)Exécution d'un groupe de tâches et suivi de la progression:
POST /tasks/groups
avec le groupe de tâches à exécuter GET /tasks/groups/group/[groupId]
jusqu'à ce que la propriété de l'objet de réponse completed
ait une valeur, affichant l'état de chaque tâche (3 tâches terminées sur 5, par exemple)Demander une exécution pour un groupe de tâches et attendre sa fin:
POST /tasks/groups
avec le groupe de tâches à exécuter GET /tasks/groups/group/[groupId]?awaitCompletion=true
jusqu'à ce qu'il réponde avec un résultat qui indique l'achèvement (a probablement un délai d'attente, c'est pourquoi il devrait être mis en boucle)Mon vote serait de diviser ces tâches en demandes distinctes. Cependant, si trop de voyages aller-retour sont un problème, je suis tombé sur code de réponse HTTP 207 - Multi-Status
Copiez/collez à partir de ce lien:
Une réponse multi-états transmet des informations sur plusieurs ressources dans des situations où plusieurs codes d'état peuvent être appropriés. Le corps de réponse multi-états par défaut est une entité HTTP text/xml ou application/xml avec un élément racine "multistatus". D'autres éléments contiennent des codes d'état des séries 200, 300, 400 et 500 générés lors de l'appel de la méthode. Les codes d'état de la série 100 NE DEVRAIENT PAS être enregistrés dans un élément XML "réponse".
Bien que "207" soit utilisé comme code d'état de réponse global, le destinataire doit consulter le contenu du corps de réponse multi-états pour plus d'informations sur le succès ou l'échec de l'exécution de la méthode. La réponse PEUT être utilisée en cas de succès, de succès partiel et également dans des situations d'échec.
Bien que le multi-statut soit une option, je retournerais 200 (tout va bien) si toutes les demandes aboutissaient et une erreur (500 ou peut-être 207) sinon.
Le cas standard devrait généralement être de 200 - tout fonctionne. Et les clients ne devraient que vérifier cela. Et seulement si le cas d'erreur s'est produit, vous pouvez retourner un 500 (ou un 207). Je pense que le 207 est un choix valable dans le cas d'au moins une erreur, mais si vous voyez l'ensemble du package comme une transaction, vous pouvez également envoyer 500. - Le client voudra interpréter le message d'erreur dans les deux cas.
Pourquoi ne pas toujours envoyer 207? - Parce que les cas standards devraient être faciles et standard. Alors que les cas exceptionnels peuvent être exceptionnels. Un client ne devrait avoir à lire le corps de la réponse et à prendre d'autres décisions complexes que si une situation exceptionnelle le justifie.
Une option serait de toujours renvoyer un code d'état 200, puis de renvoyer des erreurs spécifiques dans le corps de votre document JSON. C'est exactement ainsi que certaines API sont conçues (elles renvoient toujours un code d'état 200 et diffusent l'erreur dans le corps). Pour plus de détails sur les différentes approches, voir http://archive.oreilly.com/pub/post/restful_error_handling.html
Je pense que neilsimp1 est correct, mais je recommanderais une refonte des données envoyées de telle manière que vous puissiez envoyer un 206 - Accepted
et traiter les données ultérieurement. Peut-être avec des rappels.
Le problème avec essayer d'envoyer plusieurs actions dans une seule demande est exactement le fait que chaque action doit avoir son propre "statut"
En regardant l'importation d'un CSV (je ne sais pas vraiment de quoi parle l'OP mais c'est une version simple). POST le CSV et récupérer un 206. Ensuite, le CSV peut être importé et vous pouvez obtenir le statut de l'importation avec un GET (200) par rapport à une URL qui affiche les erreurs par ligne.
POST /imports/ -> 206
GET /imports/1 -> 200
GET /imports/1/errors -> 200 -> Has a list of errors
Ce même modèle peut être appliqué à de nombreuses opérations par lots
POST /operations/ -> 206
GET /operations/1 -> 200
GET /operations/1/errors -> 200 - > Has a list of errors.
Le code qui gère le POST suffit de vérifier que le format des données d'opérations est valide. Ensuite, un peu plus tard, les opérations peuvent être exécutées. Dans un travailleur de terrain, vous pouvez donc évoluer plus facilement , par exemple. Ensuite, vous pouvez vérifier le statut des opérations quand vous le souhaitez. Vous pouvez utiliser l'interrogation ou les rappels, ou les flux ou quoi que ce soit pour répondre au besoin de savoir quand un ensemble d'opérations se termine.
Déjà de nombreuses bonnes réponses ici, mais un aspect manque:
Quel est le contrat que vos clients attendent?
Les codes de retour HTTP devraient indiquer au moins une distinction succès/échec et jouer ainsi le rôle d '"exceptions du pauvre". Ensuite, 200 signifie "contrat entièrement rempli", et 4xx ou 5xx indiquent un manquement.
Naïvement, je m'attendrais à ce que le contrat de votre demande à actions multiples soit "fais toutes mes tâches", et si l'une d'elles échoue, alors la demande n'a pas (complètement) abouti. Typiquement, en tant que client, je comprendrais 200 comme "tout va bien", et les codes de la famille 400 et 500 m'obligent à réfléchir aux conséquences d'un échec (partiel). Donc, utilisez 200 pour "toutes les tâches effectuées" et 500 plus une réponse descriptive en cas d'échec partiel.
Un autre contrat hypothétique pourrait être "essayez de faire toutes les actions". Ensuite, il est complètement conforme au contrat si (certaines) des actions échouent. Ainsi, vous renvoyez toujours 200 plus un document de résultats où vous trouverez les informations de réussite/d'échec pour les tâches individuelles.
Alors, quel est le contrat que vous souhaitez suivre? Les deux sont valides, mais le premier (200 seulement au cas où tout a été fait) est plus intuitif pour moi, et mieux en ligne avec les modèles de logiciels typiques. Et pour la majorité (espérons-le) des cas où le service a terminé toutes les tâches, il est simple pour le client de détecter ce cas.
Un dernier aspect important: comment communiquez-vous votre décision contractuelle à vos clients? Par exemple. en Java, j'utiliserais des noms de méthode comme "doAll ()" ou "tryToDoAll ()". Dans HTTP, vous pouvez nommer les URL de point de terminaison en conséquence, en espérant que vos développeurs clients voient, lisent et comprennent le nommage (je ne parierais pas sur cela). Une raison de plus pour choisir le contrat de moindre surprise.
Réponse:
Utilisez simplement une demande par action et acceptez les frais supplémentaires.
Un code d'état décrit l'état d'une opération. Par conséquent, il est logique d'avoir une opération par demande.
Plusieurs opérations indépendantes rompent le principe sur lequel le modèle de demande-réponse et les codes d'état sont basés. Vous combattez la nature.
HTTP/1.1 et HTTP/2 ont considérablement réduit les frais généraux des requêtes HTTP. J'estime qu'il y a très peu de situations où le regroupement de demandes indépendantes est conseillé.
Cela dit,
(1) Vous pouvez effectuer plusieurs modifications avec une demande PATCH ( RFC 5789 ). Cependant, cela nécessite que les changements ne soient pas indépendants; ils sont appliqués atomiquement (tout ou rien).
(2) D'autres ont signalé le code 207 Multi-Status. Cependant, cela n'est défini que pour WebDAV ( RFC 4918 ), une extension de HTTP.
Le code d'état 207 (multi-états) fournit l'état de plusieurs opérations indépendantes (voir la section 13 pour plus d'informations).
...
Une réponse multi-états transmet des informations sur plusieurs ressources dans des situations où plusieurs codes d'état peuvent être appropriés. L'élément racine [XML] "multistatus" contient zéro ou plusieurs éléments "réponse" dans n'importe quel ordre, chacun contenant des informations sur une ressource individuelle.
Une réponse XML WebDAV 207 serait aussi étrange qu'un canard dans une API non WebDAV. Ne fais pas ça.
Si vous avez vraiment besoin d'avoir plusieurs actions dans une seule demande, pourquoi ne pas envelopper toutes les actions dans une transaction dans le backend? De cette façon, ils réussissent tous ou échouent tous.
En tant que client utilisant l'API, je peux gérer un succès ou un échec complet lors d'un appel d'API. Le succès partiel est difficile à gérer, car je devrais gérer tous les états résultants possibles.