Je construis une API RESTful qui prend en charge les tâches de longue durée en file d'attente pour une gestion éventuelle.
Le flux de travail typique pour cette API serait:
/results/{request_id}
)Mon problème est à l'étape 6. Chaque fois qu'un utilisateur visite la page, je dépose une demande auprès de mon API (GET /api/results/{request_id}
). Idéalement, la tâche sera terminée maintenant, et je retournerais un 200 OK avec les résultats de leur tâche.
Mais les utilisateurs sont arrogants, et je m'attends à beaucoup de rafraîchissements trop zélés, lorsque le résultat n'est pas encore terminé.
Quelle est ma meilleure option pour un code d'état pour indiquer que:
Je ne m'attends pas à ce qu'un seul code communique tout cela, mais j'aimerais quelque chose qui me permette de transmettre des métadonnées au lieu que le client attende du contenu.
Il pourrait être judicieux de renvoyer un 202, car cela n'aurait aucun autre sens ici: c'est une demande GET
, donc rien n'est "accepté". Serait-ce un choix raisonnable?
L'alternative évidente à tout cela - qui fonctionne, mais va à l'encontre d'un objectif des codes d'état - serait de toujours inclure les métadonnées:
200 OK
{
status: "complete",
data: {
foo: "123"
}
}
...ou...
200 OK
{
status: "pending"
}
Côté client, je (soupirais) switch
sur response.data.status
pour déterminer si la demande a été traitée.
Est-ce ce que je devrais faire? Ou existe-t-il une meilleure alternative? Cela me semble tellement Web 1.0.
Vous recherchez HTTP 202 Accepted
état. Voir RFC 2616 :
La demande a été acceptée pour traitement, mais le traitement n'est pas terminé.
RFC 2518 suggère d'utiliser HTTP 102 Processing
:
Le code d'état 102 (Traitement) est une réponse provisoire utilisée pour informer le client que le serveur a accepté la demande complète, mais ne l'a pas encore terminée.
mais il a une mise en garde:
Le serveur DOIT envoyer une réponse finale une fois la demande terminée.
Je ne sais pas comment interpréter la dernière phrase. Le serveur doit-il éviter d'envoyer quoi que ce soit pendant le traitement et ne répondre que après la fin? Ou cela ne force à fin la réponse qu'à la fin du traitement? Cela pourrait être utile si vous souhaitez signaler les progrès. Envoyer HTTP 102 et vider la réponse octet par octet (ou ligne par ligne).
Par exemple, pour un processus long mais linéaire, vous pouvez envoyer cent points en rinçant après chaque caractère. Si le côté client (comme une application JavaScript) sait qu'il doit attendre exactement 100 caractères, il peut le faire correspondre avec une barre de progression à afficher à l'utilisateur.
Un autre exemple concerne un processus qui comprend plusieurs étapes non linéaires. Après chaque étape, vous pouvez vider un message de journal qui sera éventuellement affiché pour l'utilisateur, afin que l'utilisateur final puisse savoir comment le processus se déroule.
Notez que bien que cette technique ait ses mérites, je ne la recommanderais pas . L'une des raisons est qu'elle oblige la connexion à rester ouverte, ce qui pourrait nuire à la disponibilité des services et ne pas évoluer correctement.
Une meilleure approche consiste à répondre par HTTP 202 Accepted
et laissez l'utilisateur vous recontacter ultérieurement pour déterminer si le traitement s'est terminé (par exemple en appelant à plusieurs reprises un URI donné tel que /process/result
qui répondrait avec HTTP 404 Introuvable ou Conflit HTTP 409 jusqu'à la fin du processus et le résultat est prêt), ou avertir l'utilisateur lorsque le traitement est terminé si vous êtes en mesure d'appeler le client par exemple via un service de file d'attente de messages ( exemple ) ou WebSockets.
Imaginez un service Web qui convertit les vidéos. Le point d'entrée est:
POST /video/convert
qui prend un fichier vidéo de la requête HTTP et fait de la magie avec. Imaginons que la magie soit gourmande en CPU, elle ne peut donc pas se faire en temps réel lors du transfert de la requête. Cela signifie qu'une fois le fichier transféré, le serveur répondra par un HTTP 202 Accepted
avec du contenu JSON, ce qui signifie "Oui, j'ai votre vidéo et je travaille dessus; il sera prêt quelque part dans le futur et sera disponible via l'ID 123. "
Le client a la possibilité de s'abonner à une file d'attente de messages pour être averti de la fin du traitement. Une fois celle-ci terminée, le client peut télécharger la vidéo traitée en allant sur:
GET /video/download/123
ce qui conduit à un HTTP 200
.
Que se passe-t-il si le client interroge cet URI avant de recevoir la notification? Eh bien, le serveur répondra par HTTP 404
puisque, en effet, la vidéo n'existe pas encore. Il peut être en cours de préparation. Cela peut ne jamais avoir été demandé. Il peut exister dans le passé et être supprimé ultérieurement. Tout ce qui compte, c'est que la vidéo qui en résulte n'est pas disponible.
Maintenant, que se passe-t-il si le client se soucie non seulement de la vidéo finale, mais aussi de la progression (ce qui serait encore plus important s'il n'y a pas de service de file d'attente de messages ou tout autre mécanisme similaire)?
Dans ce cas, vous pouvez utiliser un autre point de terminaison:
GET /video/status/123
ce qui entraînerait une réponse similaire à ceci:
HTTP 200
{
"id": 123,
"status": "queued",
"priority": 2,
"progress-percent": 0,
"submitted-utc-time": "2016-04-19T13:59:22"
}
Faire la demande encore et encore montrera le progrès jusqu'à ce qu'il soit:
HTTP 200
{
"id": 123,
"status": "done",
"progress-percent": 100,
"submitted-utc-time": "2016-04-19T13:59:22"
}
Il est essentiel de faire la différence entre ces trois types de demandes:
POST /video/convert
met une tâche en file d'attente. Il ne doit être appelé qu'une seule fois: le rappeler à nouveau entraînerait la mise en file d'attente d'une tâche supplémentaire.GET /video/download/123
concerne le résultat de l'opération: la ressource est la vidéo. Le traitement - c'est ce qui s'est passé sous le capot pour se préparer le résultat réel avant la demande et indépendamment de la demande - n'est pas pertinent ici. Il peut être appelé une ou plusieurs fois.GET /video/status/123
concerne le traitement en soi. Il ne fait rien en file d'attente. Il ne se soucie pas de la vidéo résultante. La ressource est le traitement lui-même. Il peut être appelé une ou plusieurs fois.L'alternative évidente à tout cela - qui fonctionne, mais va à l'encontre d'un objectif des codes d'état - serait de toujours inclure les métadonnées:
C'est la bonne façon de procéder. L'état dans lequel se trouvent les ressources par rapport au journal spécifique au domaine (ou logique métier) dépend du type de contenu de la représentation de la ressource.
Il y a ici deux concepts de différence qui sont en fait différents. L'un est l'état du transfert d'état entre le client et le serveur d'une ressource, et l'autre est l'état de la ressource elle-même dans quel que soit le contexte dans lequel le domaine métier considère les différents états de cette ressource. Ce dernier n'a rien à voir avec les codes de statut HTTP.
N'oubliez pas que les codes d'état HTTP correspondent au transfert d'état entre le client et le serveur de la ressource traitée, indépendamment de tous les détails de cette ressource. Lorsque vous GET
une ressource, votre client demande au serveur une représentation d'une ressource dans l'état où elle se trouve. Cela pourrait être une image d'un oiseau, ce pourrait être un document Word, ce pourrait être le température extérieure actuelle Le protocole HTTP s'en fiche. Le code d'état HTTP correspond au résultat de cette demande. Le POST
du client vers le serveur a-t-il transféré une ressource vers le serveur, où le serveur lui a ensuite donné une URL que le client peut afficher? Oui? Alors c'est un 201 Created
réponse.
La ressource pourrait être une réservation de compagnie aérienne actuellement dans l'état "à réviser". Ou il peut s'agir d'un bon de commande de produit à l'état "approuvé". Ces états sont spécifiques à un domaine et ne sont pas le sujet du protocole HTTP. Le protocole HTTP traite du transfert de ressources entre le client et le serveur.
Le point de REST et HTTP est que les protocoles ne se préoccupent pas des détails des ressources. C'est exprès, il ne se préoccupe pas des problèmes spécifiques au domaine de sorte qu'il peut être utilisé sans avoir à connaître les problèmes spécifiques au domaine. Vous ne réinterprétez pas ce que les codes de statut HTTP signifient dans chaque contexte différent (un système de réservation de compagnie aérienne, un système de traitement imaginé, un système de sécurité vidéo, etc.).
Le truc spécifique au domaine est pour le client et le serveur à comprendre entre eux sur la base du Content Type
de la ressource. Le protocole HTTP est agnostique à cela.
Quant à la façon dont le client détermine que la ressource de demande a changé d'état, l'interrogation est votre meilleur pari car elle garde le contrôle sur le client et ne suppose pas une connexion ininterrompue. Surtout si cela va potentiellement prendre des heures avant que l'état ne change. Même si vous avez dit au diable avec REST vous allez simplement garder la connexion ouverte, la maintenir ouverte pendant des heures et en supposant que rien ne va mal serait une mauvaise idée. Que faire si l'utilisateur ferme le Si le niveau de granularité est de plusieurs heures, le client peut simplement demander l'état toutes les quelques minutes jusqu'à ce que la demande passe de "en attente" à "terminée".
J'espère que cela clarifie les choses
J'ai trouvé les suggestions de ce blog raisonnables: REST et travaux de longue durée .
Résumer:
Le code d'état HTTP pour la ressource n'est pas encore disponible suggère de renvoyer une réponse de conflit 409, plutôt qu'une réponse 404, dans le cas où une ressource n'existe pas car elle est en cours de génération.
De la w3 spec :
10.4.10 409 Conflit
La demande n'a pas pu être traitée en raison d'un conflit avec l'état actuel de la ressource. Ce code n'est autorisé que dans les situations où l'on s'attend à ce que l'utilisateur puisse résoudre le conflit et soumettre à nouveau la demande. Le corps de réponse DEVRAIT inclure suffisamment
informations permettant à l'utilisateur de reconnaître la source du conflit. Idéalement, l'entité de réponse comprendrait suffisamment d'informations pour que l'utilisateur ou l'agent utilisateur puisse résoudre le problème; cependant, cela pourrait ne pas être possible et n'est pas requis.
Les conflits sont plus susceptibles de se produire en réponse à une demande PUT. Par exemple, si le contrôle de version était utilisé et que l'entité PUT incluait des modifications apportées à une ressource qui entrent en conflit avec celles apportées par une demande antérieure (tierce), le serveur peut utiliser la réponse 409 pour indiquer qu'il ne peut pas terminer la demande. . Dans ce cas, l'entité de réponse contiendrait probablement une liste des différences entre les deux versions dans un format défini par le type de contenu de la réponse.
Cela est légèrement gênant, car le code 409 est "autorisé uniquement dans les situations où il est prévu que l'utilisateur puisse résoudre le conflit et soumettre à nouveau la demande." Je suggère que le corps de la réponse comprenne un message (peut-être dans un format de réponse correspondant au reste de votre API) comme "Cette ressource est en cours de génération. Elle a été lancée à [TIME] et devrait se terminer à [TIME]. Veuillez Réessayez plus tard."
Notez que je ne suggérerais l'approche 409 que s'il est très probable que l'utilisateur qui demande la ressource soit également l'utilisateur qui a initié la génération de cette ressource. Les utilisateurs non impliqués dans la génération de la ressource trouveraient une erreur 404 moins déroutante.