web-dev-qa-db-fra.com

Code d'état HTTP pour "Traitement en cours"

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:

  1. L'utilisateur remplit le formulaire
  2. Le client publie des données sur l'API
  3. L'API renvoie 202 Accepté
  4. Le client redirige l'utilisateur vers une URL unique pour cette demande (/results/{request_id})
  5. ~ éventuellement ~
  6. Le client visite à nouveau l'URL et voit les résultats sur cette page.

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:

  • cette demande existe,
  • ce n'est pas encore fait,
  • mais cela n'a pas échoué non plus.

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.

48
Matthew Haugen

HTTP 202 accepté (HTTP/1.1)

Vous recherchez HTTP 202 Accepted état. Voir RFC 2616 :

La demande a été acceptée pour traitement, mais le traitement n'est pas terminé.

Traitement HTTP 102 (WebDAV)

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.

Problèmes de rinçage progressif

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.

Exemple pratique

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.
53
Arseni Mourzenko

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

6
Cormac Mulhall

J'ai trouvé les suggestions de ce blog raisonnables: REST et travaux de longue durée .

Résumer:

  1. Le serveur renvoie le code "202 Accepté" avec l'en-tête "Location" défini sur un URI pour que le client vérifie l'état, par ex. "/ queue/12345".
  2. Jusqu'à la fin du traitement, le serveur répond aux requêtes d'état avec "200 OK" et certaines données de réponse indiquant l'état du travail.
  3. Une fois le traitement terminé, le serveur répond aux requêtes d'état avec "303 See Other" et "Location" contenant l'URI du résultat final.
5
Xiangming Hu

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.

2
Brian