Code de statut HTTP pour «Still Processing»

47

Je construis une API RESTful qui prend en charge la mise en file d'attente de tâches longues pour une gestion éventuelle.

Le flux de travail typique de cette API serait:

  1. L'utilisateur remplit le formulaire
  2. Le client publie des données sur l'API
  3. L'API retourne 202 Accepté
  4. Le client redirige l'utilisateur vers une URL unique pour cette requête ( /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, j'envoie une demande à mon API ( GET /api/results/{request_id}). Idéalement, la tâche sera terminée à présent et je vous renverrais 200 OK avec les résultats de leur tâche.

Mais les utilisateurs sont envahissants et j'attends de nombreux rafraîchissements trop zélés lorsque le résultat n'est pas encore terminé.

Quelle est ma meilleure option pour un code de statut pour indiquer que:

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

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 un contenu.

Il pourrait être logique de renvoyer un 202 car cela n’aurait aucune autre signification ici: c’est une GETdemande, donc rien n’est peut-être «accepté». Serait-ce un choix raisonnable?

L’alternative évidente à tout cela - qui fonctionne, mais fait échec à un des codes d’état - serait d’inclure toujours les métadonnées:

200 OK

{
    status: "complete",
    data: {
        foo: "123"
    }
}

...ou...

200 OK

{
    status: "pending"
}

Ensuite , côté client, je (soupir) switchsur response.data.statuspour déterminer si la demande a été complétée.

Est-ce ce que je devrais faire? Ou existe-t-il une meilleure alternative? Cela me semble tellement Web 1.0.

Matthew Haugen
la source
1
Les codes 1xx ne sont-ils pas conçus exactement à cette fin?
Andy
@Andy Je cherchais à 102, mais c'est pour WebDAV. Au-delà, non ... Ils sont principalement destinés aux communications en transit. Utile pour passer aux sockets Web et autres.
Matthew Haugen
Quel genre de retard parlez-vous? 10 secondes? Ou 6 heures? Si les délais sont courts et généralement dans la même visite du navigateur, vous pouvez effectuer des interrogations longues ou des sockets Web plutôt que des interrogations périodiques.
GrandmasterB
@GrandmasterB C'est potentiellement des heures. Je ne suis pas responsable du traitement du travail lui-même, alors je n'ai pas une très bonne estimation, mais ça va prendre du temps. Sinon, je laisserais simplement la première POSTdemande ouverte. Le principal problème avec les longues interrogations ou les sockets Web est que l'utilisateur peut fermer le navigateur et revenir. Je pourrais les ouvrir à nouveau à ce moment-là (et c'est ce que je fais), mais il semble plus simple de disposer d'une seule API à appeler avant d'ouvrir ces sockets, car c'est un cas extrême pour lequel ce problème se pose.
Matthew Haugen

Réponses:

48

HTTP 202 accepté (HTTP / 1.1)

Vous recherchez un HTTP 202 Acceptedstatut. Voir RFC 2616 :

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

Traitement HTTP 102 (WebDAV)

La RFC 2518 suggère d'utiliser HTTP 102 Processing:

Le code de statut 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 y a une mise en garde:

Le serveur DOIT envoyer une réponse finale une fois la demande complété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 qu’après l’achèvement? Ou cela oblige-t-il uniquement à mettre fin à la réponse lorsque le traitement est terminé? Cela peut être utile si vous souhaitez signaler les progrès. Envoyez HTTP 102 et purgez la réponse octet par octet (ou ligne par ligne).

Par exemple, pour un processus long mais linéaire, vous pouvez envoyer cent points, après chaque caractère. Si le côté client (tel qu'une application JavaScript) sait qu'il doit s'attendre à exactement 100 caractères, il peut le faire correspondre à 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é à l'utilisateur, afin que l'utilisateur final puisse savoir comment le processus se déroule.

Problèmes avec le rinçage progressif

Notez que si cette technique a ses avantages, je ne la recommanderais pas . L'une des raisons est que cela force la connexion à rester ouverte, ce qui pourrait nuire à la disponibilité du service et ne pas être à la hauteur.

Une meilleure approche consiste à répondre avec HTTP 202 Accepted, soit à laisser l'utilisateur de vous contacter ultérieurement pour déterminer si le traitement est terminé (par exemple, en appelant de manière répétée un URI donné tel que celui /process/resultqui répondrait avec HTTP 404 non trouvé ou HTTP 409 en conflit jusqu'au traitement se termine et que le résultat est prêt), ou informe l'utilisateur lorsque le traitement est terminé si vous êtes en mesure de rappeler le client, par exemple, via un service de file d'attente de messages ( exemple ) ou WebSockets.

Exemple pratique

Imaginez un service Web qui convertit des vidéos. Le point d'entrée est:

POST /video/convert

qui prend un fichier vidéo de la requête HTTP et fait un peu de magie avec elle. Imaginons que la magie consomme beaucoup de ressources processeur et que cela ne puisse se faire en temps réel pendant le transfert de la requête. Cela signifie qu'une fois le fichier transféré, le serveur répondra par un HTTP 202 Acceptedcontenu 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 de messages pour être averti à la fin du traitement. Une fois l’opération terminée, le client peut télécharger la vidéo traitée en allant sur:

GET /video/download/123

qui mène à 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 avec, HTTP 404puisque la vidéo n’existe pas encore. Il peut être actuellement préparé. Cela n'a peut-être jamais été demandé. Il peut exister quelque temps dans le passé et être supprimé plus tard. Tout ce qui compte, c'est que la vidéo obtenue ne soit pas disponible.

Maintenant, que se passe-t-il si le client se soucie non seulement de la vidéo finale, mais également de la progression (ce qui serait encore plus important s'il n'y avait pas de service de file d'attente de messages ou un mécanisme similaire)?

Dans ce cas, vous pouvez utiliser un autre noeud final:

GET /video/status/123

ce qui entraînerait une réponse semblable à ceci:

HTTP 200
{
    "id": 123,
    "status": "queued",
    "priority": 2,
    "progress-percent": 0,
    "submitted-utc-time": "2016-04-19T13:59:22"
}

Si vous faites la demande encore et encore, vous verrez les progrès jusqu'à ce que:

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/convertmet en file d'attente une tâche. Il ne devrait être appelé qu'une seule fois: un nouvel appel mettrait en attente une tâche supplémentaire.
  • GET /video/download/123concerne 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 préparer le résultat réel avant la demande et indépendamment pour la demande — est sans importance ici. Il peut être appelé une ou plusieurs fois.
  • GET /video/status/123concerne le traitement en soi . Il ne fait rien faire la queue. Il se fiche de la vidéo résultante. La ressource est le traitement lui-même. Il peut être appelé une ou plusieurs fois.
Arseni Mourzenko
la source
1
Un 202 a-t-il un sens en réponse à un GET, cependant? C'est certainement le bon choix pour l'initiale POST, c'est pourquoi je l'utilise. Mais il semble sémantiquement suspect GETde dire "accepté" alors qu’il n’accepte rien de cette demande particulière.
Matthew Haugen
2
@MainMa Comme je l'ai dit, j'ai POSTmis le travail en file d'attente, puis j'ai affiché GETles résultats, éventuellement après la fermeture de la session par le client. Une 404 est aussi quelque chose que j'ai envisagé, mais cela semble faux, puisque la demande a été trouvée, elle n'a tout simplement pas été complétée. Cela m'indiquerait que le travail en file d'attente n'a pas été trouvé, ce qui est une situation très différente.
Matthew Haugen
1
@ MatthewHaugen: lorsque vous réalisez la GETpièce, ne la considérez pas comme une requête incomplète , mais comme une requête visant à obtenir le résultat de l'opération . Par exemple, si je vous dis de convertir une vidéo et que cela vous prend cinq minutes, demander une vidéo convertie deux minutes plus tard devrait aboutir à HTTP 404, car la vidéo n'y est tout simplement pas encore présente. En revanche, la demande de progression de l'opération elle-même aboutira probablement à un HTTP 200 contenant le nombre d'octets convertis, la vitesse, etc.
Arseni Mourzenko, le
5
Le code d'état HTTP pour la ressource non encore disponible suggère de renvoyer une réponse conflictuelle 409 ("La requête n'a pas pu être complétée en raison d'un conflit avec l'état actuel de la ressource."), Plutôt qu'une réponse 404, dans le cas où une ressource ne n'existe pas car il est en train d'être généré.
Brian
1
@ Brian Votre commentaire apporterait une réponse raisonnable à cette question. Bien que je répondrais ensuite avec "[c] e code n'est autorisé que dans les cas où l'on s'attend à ce que l'utilisateur puisse résoudre le conflit et resoumettre la demande", ce qui n'est pas strictement vrai dans mon cas, mais cela semble moins faux que "non trouvé". Une partie de moi-même penche vers un 409 avec un en-tête Retry-After épinglé. Le seul problème est qu’il semble étrange de renvoyer un 409 pour un EEG, mais je peux vivre avec cet étrangeté: il est peu probable qu’il soit défini autrement à l’avenir.
Matthew Haugen
5

L’alternative évidente à tout cela - qui fonctionne, mais fait échec à un des codes d’état - serait d’inclure toujours les métadonnées:

C'est la bonne façon de faire. L'état dans lequel se trouve une ressource par rapport à un journal spécifique à un domaine (ou logique métier) dépend du type de contenu de la représentation de la ressource.

Il y a deux concepts de différence regroupés ici qui sont en réalité différents. Le premier est le statut du transfert d'état entre le client et le serveur d'une ressource, et le second est l'état de la ressource elle-même, quel que soit le contexte dans lequel le domaine métier comprend les différents états de cette ressource. Ce dernier n’a rien à voir avec les codes d’état 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 des détails de cette ressource. Lorsque vous, GETune ressource, votre client demande au serveur la représentation d’une ressource dans son état actuel. Il peut s'agir d’une image d’un oiseau, d’un document Word ou d’un document externe. Le protocole HTTP s'en moque. Le code d'état HTTP correspond au résultat de cette demande. Du POSTclient au serveur, le serveur at-il transféré une ressource au serveur, où le serveur lui a ensuite donné une URL que le client peut afficher? Oui? Alors c'est une 201 Createdréponse.

La ressource pourrait être une réservation aérienne qui est actuellement dans l'état "à vérifier". Il peut également s'agir d'un bon de commande de produit dans l'état "approuvé". Ces états sont spécifiques à un domaine et ne concernent pas le protocole HTTP. Le protocole HTTP traite du transfert de ressources entre le client et le serveur.

Le point essentiel de REST et HTTP est que les protocoles ne se préoccupent pas des détails des ressources. Ceci est fait exprès, cela ne concerne pas les problèmes spécifiques à un domaine, de sorte qu'il puisse être utilisé sans avoir à rien savoir des problèmes spécifiques à un domaine. Vous ne réinterprétez pas la signification des codes de statut HTTP dans chaque contexte (système de réservation de transport aérien, système de traitement Imagine, système de sécurité vidéo, etc.).

Le domaine spécifique est que le client et le serveur déterminent entre eux en fonction Content Typede la ressource. Le protocole HTTP est agnostique à cela.

En ce qui concerne la façon dont le client détermine que la ressource de demande a changé d'état, l'interrogation est votre meilleur choix, car elle garde le contrôle sur le client et ne suppose pas une connexion ininterrompue. Surtout si cela risque de prendre des heures jusqu'à ce que l'état change. Même si vous disiez au diable REST, vous allez simplement garder la connexion ouverte, le laisser ouvert pendant des heures et présumer que rien ne se passera mal serait une mauvaise idée. Que se passe-t-il si l'utilisateur ferme le client ou si le réseau s'éteint? Si la granularité est exprimée en heures, le client peut simplement demander l'état toutes les quelques minutes jusqu'à ce que la requête passe de 'en attente' à 'terminée'.

J'espère que ça aide à clarifier les choses

Cormac Mulhall
la source
"Le POST du client au serveur a-t-il transféré une ressource sur le serveur, où le serveur lui a ensuite donné une URL que le client peut afficher? Oui? Il s’agit d’une réponse créée par 201". 202 Accepted est également acceptable en tant que réponse à cette question si le serveur ne peut pas agir immédiatement pour traiter la ressource, ce que fait le PO.
Andy
1
La chose est que le serveur agit immédiatement. Il crée la ressource avec une URL immédiatement. C'est juste que l'état de la ressource est "En attente" (ou quelque chose). C'est un état de domaine commercial. En ce qui concerne le protocole HTTP, le serveur a agi dès qu'il a créé la ressource et a fourni au client l'URL de la ressource. Vous pouvez obtenir cette ressource. La demande POST elle-même n'est pas en attente. C’est ce que je veux dire par la séparation des deux domaines conceptuels. Si le client envoyait un feu et oublie que la requête POST n'était pas traitée pendant des heures, le nombre 202 serait applicable.
Cormac Mulhall
Personne ne se soucie de savoir si l'URL existe, mais vous ne pouvez pas obtenir les données représentées par la ressource, car elles sont en cours de traitement. Peut également ne PAS créer l’URL tant qu’elle ne peut pas être utilisée pour obtenir la vidéo.
Andy
La ressource est créée, elle est juste à l'état "en attente". Ce sont en soi des données pertinentes. À un moment donné, le serveur peut changer l'état des ressources en "terminé" (ou "en échec"), mais il s'agit d'un concept différent de la tâche spécifique au domaine HTTP consistant à "créer la ressource". En attente peut être un état parfaitement valide pour une ressource "Demande", et le client veut évidemment savoir que le serveur a créé la ressource dans cet état car il ne lui demande pas de créer la ressource pour savoir comment l'interroger. si l'état a changé.
Cormac Mulhall
4

J'ai trouvé les suggestions de ce blog raisonnables: tâches de repos et de longue durée .

Résumer:

  1. Le serveur renvoie le code "202 Accepted" avec l'en-tête "Location" défini sur un URI permettant au client de vérifier l'état, par exemple "/ queue / 12345".
  2. Jusqu'à ce que le traitement soit terminé, le serveur répond aux requêtes sur l'état par "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 sur l'état avec "303 Voir autre" et "Emplacement" contenant l'URI du résultat final.
Xiangming Hu
la source
2

Le code d'état HTTP pour la ressource non 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 spécification w3 :

10.4.10 409 Conflit

La demande n'a pas pu être complétée en raison d'un conflit avec l'état actuel de la ressource. Ce code n'est autorisé que dans les cas où l'utilisateur devrait pouvoir 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 inclurait suffisamment d'informations pour permettre à l'utilisateur ou à l'agent d'utilisateur de 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 dans une ressource incompatibles avec celles effectuées par une requête précédente (tierce partie), le serveur pourrait utiliser la réponse 409 pour indiquer qu'il ne pouvait 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 la réponse Content-Type.

Cela est légèrement gênant, car le code 409 n’est "autorisé que dans les cas où il est prévu que l’utilisateur pourra peut-être résoudre le conflit et soumettre à nouveau la demande". Je suggère que le corps de la réponse contienne un message (éventuellement dans un format de réponse correspondant au reste de votre API), par exemple, "Cette ressource est en cours de génération. Elle a été lancée à [TIME] et devrait se terminer à [TIME]. S'il vous plaît Réessayez plus tard."

Notez que je ne suggérerais l'approche 409 que s'il est hautement probable que l'utilisateur qui demande la ressource est également l'utilisateur qui a initié la génération de cette ressource. Les utilisateurs ne participant pas à la génération de la ressource trouveraient une erreur 404 moins source de confusion.

Brian
la source
Cela ressemble vraiment à ce à quoi le 409 est vraiment destiné, ce qui correspond à un put.
Andy
@Andy: C'est vrai, mais toutes les autres alternatives le sont aussi. Par exemple, 202 est réellement censé être une réponse à la demande qui a initié le traitement, pas la demande qui a demandé les résultats du traitement. En réalité, la réponse la plus conforme aux spécifications est 404, car la ressource n'a pas été trouvée (car elle n'existait pas encore). Rien n'empêche l'API de fournir les données pertinentes de l'API dans la réponse 404. Remarquez, les réponses 4xx / 5xx ont tendance à être agaçantes à consommer; certaines langues déclenchent une exception plutôt que de simplement fournir un code d'état différent.
Brian
2
Non, surtout les derniers paragraphes de la réponse de MainMa. Séparez les extrémités pour vérifier l’état de la demande et obtenir la vidéo elle-même. Le statut n'est pas la même ressource que la vidéo et devrait pouvoir être adressé seul.
Andy