Código de status HTTP para "Ainda em processamento"

47

Estou criando uma API RESTful que oferece suporte a tarefas de longa execução de enfileiramento para manipulação eventual.

O fluxo de trabalho típico para esta API seria:

  1. O usuário preenche o formulário
  2. O cliente publica dados na API
  3. A API retorna 202 Aceito
  4. O cliente redireciona o usuário para um URL exclusivo para essa solicitação ( /results/{request_id})
  5. ~ eventualmente ~
  6. O cliente visita o URL novamente e vê os resultados nessa página.

Meu problema está na etapa 6. Sempre que um usuário visita a página, arquivo uma solicitação na minha API ( GET /api/results/{request_id}). Idealmente, a tarefa já estará concluída e eu retornaria 200 OK com os resultados da tarefa.

Mas os usuários são insistentes e espero muitas atualizações excessivamente zelosas, quando o resultado ainda não tiver terminado o processamento.

Qual é a minha melhor opção para um código de status para indicar que:

  • esta solicitação existe,
  • ainda não está pronto,
  • mas também não falhou.

Não espero que um único código comunique tudo isso, mas gostaria de algo que me permita transmitir metadados em vez de fazer com que o cliente espere conteúdo.

Poderia fazer sentido retornar um 202, já que isso não teria outro significado aqui: é uma GETsolicitação, portanto nada está sendo "aceito". Seria uma escolha razoável?

A alternativa óbvia a tudo isso - que funciona, mas derrota um objetivo dos códigos de status - seria sempre incluir os metadados:

200 OK

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

...ou...

200 OK

{
    status: "pending"
}

Em seguida, do lado do cliente, gostaria de (suspiro) switchem response.data.statusdeterminar se o pedido foi concluído.

É isso que eu deveria estar fazendo? Ou existe uma alternativa melhor? Isso parece tão Web 1.0 para mim.

Matthew Haugen
fonte
1
Os códigos 1xx não são feitos exatamente para esse fim?
19416 Andy
@ Andy eu estava olhando para 102, mas isso é para coisas WebDAV. Além disso, não ... Eles são principalmente para comunicações em trânsito. Útil para mudar para Web Sockets e outros.
Matthew Haugen
Que tipo de atraso você está falando? 10 segundos? Ou 6 horas? Se os atrasos são curtos e geralmente dentro da mesma visita ao navegador, você pode fazer pesquisas longas ou soquetes da Web em vez de pesquisas periódicas.
GrandmasterB
@GrandmasterB São horas, potencialmente. Eu não sou responsável pelo processamento do trabalho, por isso não tenho uma estimativa muito boa, mas vai demorar um pouco. Caso contrário, deixaria o primeiro POSTpedido em aberto. O principal problema com pesquisas longas ou soquetes da Web é que o usuário pode fechar o navegador e voltar. Eu poderia abri-los novamente naquele momento (e é isso que eu faço), mas parece mais limpo ter uma única API para chamar antes de abrir esses soquetes, já que é um caso delicado que esse problema ocorra.
Matthew Haugen

Respostas:

48

HTTP 202 aceito (HTTP / 1.1)

Você está procurando HTTP 202 Acceptedstatus. Veja RFC 2616 :

A solicitação foi aceita para processamento, mas o processamento não foi concluído.

Processamento HTTP 102 (WebDAV)

A RFC 2518 sugere o uso de HTTP 102 Processing:

O código de status 102 (Processamento) é uma resposta provisória usada para informar o cliente que o servidor aceitou a solicitação completa, mas ainda não a concluiu.

mas tem uma ressalva:

O servidor DEVE enviar uma resposta final após a solicitação ter sido concluída.

Não sei como interpretar a última frase. O servidor deve evitar enviar algo durante o processamento e responder somente após a conclusão? Ou apenas força o encerramento da resposta somente quando o processamento termina? Isso pode ser útil se você deseja relatar o progresso. Envie HTTP 102 e libere resposta byte a byte (ou linha por linha).

Por exemplo, para um processo longo mas linear, você pode enviar cem pontos, liberando após cada caractere. Se o lado do cliente (como um aplicativo JavaScript) souber que deve esperar exatamente 100 caracteres, poderá combiná-lo com uma barra de progresso para mostrar ao usuário.

Outro exemplo diz respeito a um processo que consiste em várias etapas não lineares. Após cada etapa, você pode liberar uma mensagem de log que eventualmente será exibida ao usuário, para que o usuário final saiba como está o processo.

Problemas com descarga progressiva

Observe que, embora essa técnica tenha seus méritos, eu não a recomendaria . Uma das razões é que força a conexão a permanecer aberta, o que pode prejudicar em termos de disponibilidade do serviço e não é bem dimensionado.

Uma abordagem melhor é responder HTTP 202 Acceptede permitir que o usuário retorne mais tarde para determinar se o processamento terminou (por exemplo, chamando repetidamente um determinado URI, como o /process/resultque responderia com o HTTP 404 Not Found ou o HTTP 409 Conflict até o processo termina e o resultado está pronto) ou notifique o usuário quando o processamento estiver concluído, se você puder chamar o cliente de volta, por exemplo, por meio de um serviço de fila de mensagens ( exemplo ) ou WebSockets.

Exemplo prático

Imagine um serviço da web que converte vídeos. O ponto de entrada é:

POST /video/convert

que pega um arquivo de vídeo da solicitação HTTP e faz alguma mágica com ele. Vamos imaginar que a mágica consome muita CPU, portanto não pode ser feita em tempo real durante a transferência da solicitação. Isso significa que, depois que o arquivo for transferido, o servidor responderá com um HTTP 202 Acceptedconteúdo JSON, o que significa “Sim, peguei o seu vídeo e estou trabalhando nele; estará pronto em algum lugar no futuro e estará disponível através do ID 123. "

O cliente tem a possibilidade de se inscrever em uma fila de mensagens para ser notificado quando o processamento terminar. Quando terminar, o cliente pode baixar o vídeo processado, acessando:

GET /video/download/123

o que leva a um HTTP 200.

O que acontece se o cliente consultar esse URI antes de receber a notificação? Bem, o servidor responderá uma HTTP 404vez que, de fato, o vídeo ainda não existe. Pode estar preparado atualmente. Pode nunca ter sido solicitado. Pode existir algum tempo no passado e ser removido mais tarde. Tudo o que importa é que o vídeo resultante não esteja disponível.

Agora, e se o cliente se importar não apenas com o vídeo final, mas também com o progresso (o que seria ainda mais importante se não houver serviço de fila de mensagens ou mecanismo semelhante)?

Nesse caso, você pode usar outro ponto de extremidade:

GET /video/status/123

o que resultaria em uma resposta semelhante a esta:

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

Fazer a solicitação repetidamente mostrará o progresso até que seja:

HTTP 200
{
    "id": 123,
    "status": "done",
    "progress-percent": 100,
    "submitted-utc-time": "2016-04-19T13:59:22"
}

É crucial fazer a diferença entre esses três tipos de solicitações:

  • POST /video/convertenfileira uma tarefa. Deve ser chamado apenas uma vez: chamá-lo novamente colocaria em fila uma tarefa adicional.
  • GET /video/download/123diz respeito ao resultado da operação: o recurso é o vídeo. O processamento - foi o que aconteceu sob o capô para preparar o resultado real antes da solicitação e independentemente da solicitação - é irrelevante aqui. Pode ser chamado uma vez ou várias vezes.
  • GET /video/status/123diz respeito ao processamento em si . Não enfileira nada. Não se importa com o vídeo resultante. O recurso é o próprio processamento. Pode ser chamado uma vez ou várias vezes.
Arseni Mourzenko
fonte
1
Um 202 faz sentido em resposta a um GET? Essa é certamente a escolha correta para a inicial POST, e é por isso que a estou usando. Mas parece semanticamente suspeito que alguém GETdiga "aceito" quando não aceita nada desse pedido em particular.
21716 Matthew Haugen
2
@MainMa Como eu disse, eu levanto POSTo trabalho para a fila de espera, depois GETos resultados, potencialmente depois que o cliente encerra a sessão. Um 404 é algo que eu considerei também, mas parece errado, desde que a solicitação foi encontrada, ela simplesmente não foi concluída. Isso indicaria para mim que o trabalho na fila não foi encontrado, o que é uma situação muito diferente.
Matthew Haugen
1
@ MatthewHaugen: quando você faz a GETparte, não pense nisso como uma solicitação incompleta , mas como uma solicitação para obter o resultado da operação . Por exemplo, se eu lhe disser para converter um vídeo e você levar cinco minutos para fazê-lo, solicitar um vídeo convertido dois minutos depois deve resultar em HTTP 404, porque o vídeo simplesmente ainda não está lá. Requerente para o progresso da operação propriamente dita, por outro lado, irá provavelmente resultar em um HTTP 200 que contém o número de bytes convertido, a velocidade, etc
Arseni Mourzenko
5
O código de status HTTP do recurso ainda não disponível sugere retornar uma resposta de conflito 409 ("A solicitação não pôde ser concluída devido a um conflito com o estado atual do recurso."), Em vez de uma resposta 404, no caso de um recurso não existe porque está no meio de ser gerado.
21716 Brian As
1
@ Brian Seu comentário daria uma resposta razoável a esta pergunta. Embora eu respondesse com "[t] o código dele é permitido apenas em situações em que é esperado que o usuário consiga resolver o conflito e reenvie a solicitação", o que não é rigorosamente verdadeiro no meu caso, mas isso parece menos errado que "não encontrado". Uma parte de mim está inclinada para uma 409 com um cabeçalho Retry-After preso. A única questão é que parece estranho devolver um 409 para um GET, mas posso viver com essa estranheza - é improvável que seja definido de outra forma no futuro.
Matthew Haugen
5

A alternativa óbvia a tudo isso - que funciona, mas derrota um objetivo dos códigos de status - seria sempre incluir os metadados:

Este é o caminho correto a seguir. O estado em que os recursos estão em relação ao log específico do domínio (também conhecido como lógica comercial) é uma questão para o tipo de conteúdo da representação do recurso.

Existem aqui dois conceitos diferentes que são realmente diferentes. Um é o status da transferência de estado entre o cliente e o servidor de um recurso e o outro é o estado do próprio recurso, em qualquer contexto em que o domínio de negócios entenda os diferentes estados desse recurso. O último não tem nada a ver com códigos de status HTTP.

Lembre-se de que os códigos de status HTTP correspondem à transferência de estado entre o cliente e o servidor do recurso que está sendo tratado, independentemente de quaisquer detalhes desse recurso. Quando você GETutiliza um recurso, seu cliente está solicitando ao servidor a representação de um recurso no estado atual em que está. Isso pode ser uma imagem de um pássaro, um documento do Word, a temperatura externa atual. O protocolo HTTP não se importa. O código de status HTTP corresponde ao resultado dessa solicitação. O POSTdo cliente para o servidor transferiu um recurso para o servidor, onde o servidor forneceu uma URL que o cliente pode exibir? Sim? Então isso é uma 201 Createdresposta.

O recurso pode ser uma reserva de companhia aérea que está atualmente no estado 'a ser revisado'. Ou pode ser um pedido de compra de produto que está no estado 'aprovado'. Esses estados são específicos do domínio e não são sobre o que é o protocolo HTTP. O protocolo HTTP lida com a transferência de recursos entre cliente e servidor.

O ponto do REST e HTTP é que os protocolos não se preocupam com os detalhes dos recursos. Isso é proposital, não se preocupa com os problemas específicos do domínio, para que possa ser usado sem ter que saber nada sobre os problemas específicos do domínio. Você não reinterpreta o que significam os códigos de status HTTP em cada contexto diferente (um sistema de reservas de companhias aéreas, um sistema de processamento de imagens, um sistema de segurança de vídeo etc.).

O material específico do domínio é para o cliente e o servidor descobrirem entre si com base no Content Typerecurso. O protocolo HTTP é independente disso.

Quanto à forma como o cliente descobre que o recurso Request mudou de estado, a pesquisa é a melhor opção, pois mantém o controle no cliente e não assume conexão ininterrupta. Especialmente se for potencialmente horas até que o estado mude. Mesmo que você tenha dito ao inferno com o REST, você manterá a conexão aberta, mantê-la aberta por horas e assumindo que nada dará errado seria uma má idéia. E se o usuário fechar o cliente ou a rede sair. Se a granularidade for horas, o cliente poderá solicitar o estado a cada poucos minutos até que a Solicitação seja alterada de 'pendente' para 'concluída'.

Espero que ajude a esclarecer as coisas

Cormac Mulhall
fonte
"O POST do cliente para o servidor transferiu um recurso para o servidor, onde o servidor forneceu uma URL que o cliente pode exibir? Sim? Então essa é uma resposta 201 Criada." 202 Accepted também é aceitável como resposta a isso se o servidor não puder agir imediatamente para processar o recurso, que é o que o OP está fazendo.
21416 Andy
1
O problema é que o servidor está agindo imediatamente. Ele cria o recurso com uma URL imediatamente. É apenas o estado do recurso "Pendente" (ou algo assim). Esse é um estado de domínio comercial. No que diz respeito ao Protocolo HTTP, o servidor agiu assim que criou o recurso e forneceu ao cliente a URL do recurso. Você pode obter esse recurso. A solicitação POST em si não está pendente. É isso que quero dizer com manter os dois domínios conceituais diferentes separados. Se o cliente estava enviando um incêndio e esquecesse que a solicitação POST não foi atendida por horas, então 202 seria aplicável.
Cormac Mulhall
Ninguém se importa se o URL existe, mas você não pode obter os dados que o recurso representa porque ele ainda está sendo processado. NÃO pode criar o URL até que ele possa ser usado para obter o vídeo.
22416 Andy
O recurso é criado, está apenas no estado "pendente". Esses dados são em si relevantes. Em algum momento no futuro, o servidor poderá alterar o estado dos recursos para "concluído" (ou "com falha"), mas esse é um conceito diferente da tarefa específica do domínio HTTP de "criar o recurso". Pendente pode ser um estado perfeitamente válido para um recurso de "Solicitação", e o cliente obviamente deseja saber que o servidor criou o recurso nesse estado, pois deixa de solicitar ao servidor que crie o recurso para saber a pesquisa para descobrir se o estado mudou.
Cormac Mulhall
4

Eu achei as sugestões deste blog razoáveis: REST e trabalhos de longa duração .

Para resumir:

  1. O servidor retorna o código "202 Aceito" com o cabeçalho "Local" definido como um URI para o cliente verificar o status, por exemplo, "/ fila / 12345".
  2. Até o término do processamento, o servidor responde às consultas de status com "200 OK" e alguns dados de resposta mostrando o status do trabalho.
  3. Após o término do processamento, o servidor responde às consultas de status com "303 Consulte Outro" e "Local" contendo URI para o resultado final.
Xiangming Hu
fonte
2

O código de status HTTP para o recurso ainda não disponível sugere retornar uma resposta de conflito 409, em vez de uma resposta 404, no caso de um recurso não existir porque está no meio da geração.

A partir da especificação w3 :

10.4.10 409 Conflito

A solicitação não pôde ser concluída devido a um conflito com o estado atual do recurso. Esse código é permitido apenas em situações em que se espera que o usuário possa resolver o conflito e reenviar a solicitação. O corpo da resposta DEVE incluir o suficiente

informações para o usuário reconhecer a origem do conflito. Idealmente, a entidade de resposta incluiria informações suficientes para o usuário ou agente de usuário resolver o problema; no entanto, isso pode não ser possível e não é necessário.

É provável que os conflitos ocorram em resposta a uma solicitação PUT. Por exemplo, se o controle de versão estava sendo usado e a entidade que estava sendo PUT incluía alterações em um recurso que entra em conflito com aquelas feitas por uma solicitação anterior (de terceiros), o servidor pode usar a resposta 409 para indicar que não pode concluir a solicitação . Nesse caso, a entidade de resposta provavelmente conteria uma lista das diferenças entre as duas versões em um formato definido pela resposta Content-Type.

Isso é um pouco estranho, pois o código 409 "é permitido apenas em situações em que se espera que o usuário possa resolver o conflito e reenviar a solicitação". Sugiro que o corpo da resposta inclua uma mensagem (possivelmente em algum formato de resposta que corresponda ao restante da sua API) como "Este recurso está sendo gerado no momento. Foi iniciado em [TIME] e estima-se que seja concluído em [TIME]. Por favor, tente mais tarde."

Observe que eu sugeriria apenas a abordagem 409 se for altamente provável que o usuário que está solicitando o recurso também seja o usuário que iniciou a geração desse recurso. Os usuários não envolvidos na geração do recurso considerariam um erro 404 menos confuso.

Brian
fonte
Parece um trecho do que realmente se pretende com 409, que é uma resposta a uma venda.
21716 Andy
@ Andy: Verdade, mas assim é todas as outras alternativas. Por exemplo, 202 pretende realmente ser uma resposta à solicitação que iniciou o processamento, não a solicitação que solicitou os resultados do processamento. Realmente, a resposta mais compatível com as especificações é 404, pois o recurso não foi encontrado (porque ainda não existia). Não há nada que impeça a API de fornecer os dados da API relevantes na resposta 404. Lembre-se, as respostas 4xx / 5xx tendem a ser irritantes para consumir; alguns idiomas acionam uma exceção em vez de apenas fornecer um código de status diferente.
21716 Brian
2
Não, especialmente os últimos parágrafos da resposta da MainMa. Pontos finais separados para verificar o status da solicitação e obter o vídeo em si. O status não é o mesmo recurso que o vídeo e deve ser endereçável por si só.
21416 Andy