Qual código de status HTTP a ser retornado se várias ações terminarem com status diferentes?

72

Estou criando uma API em que o usuário pode solicitar ao servidor que execute várias ações em uma solicitação HTTP. O resultado é retornado como uma matriz JSON, com uma entrada por ação.

Cada uma dessas ações pode falhar ou ter sucesso independentemente uma da outra. Por exemplo, a primeira ação pode ter êxito, a entrada para a segunda ação pode estar mal formatada e falhar na validação e a terceira ação pode causar um erro inesperado.

Se houvesse uma solicitação por ação, eu retornaria os códigos de status 200, 422 e 500, respectivamente. Mas agora, quando há apenas uma solicitação, qual código de status devo retornar?

Algumas opções:

  • Sempre retorne 200 e forneça informações mais detalhadas no corpo.
  • Talvez siga a regra acima apenas quando houver mais de uma ação na solicitação?
  • Talvez retorne 200 se todas as solicitações forem bem-sucedidas, caso contrário 500 (ou algum outro código)?
  • Basta usar uma solicitação por ação e aceitar a sobrecarga extra.
  • Algo completamente diferente?
Anders
fonte
3
Sua pergunta me fez pensar em outra: programmers.stackexchange.com/questions/309147/…
AilurusFulgens
7
Ligeiramente relacionado, bem como: programmers.stackexchange.com/questions/305250/... (veja a resposta aceita sobre a separação entre códigos de status HTTP e códigos de aplicação)
4
Qual é a vantagem que você obtém agrupando essas solicitações? É sobre lógica de negócios, como uma transação com vários recursos, ou é sobre desempenho? Ou alguma outra coisa?
Luc Franken
5
Ok, nesse caso, eu sugeriria fortemente melhorar esse desempenho em outras áreas. Experimente coisas como interface otimista, solicite lotes, cache etc. antes de implementar essa complexidade em sua camada de negócios. Você tem uma visão clara de onde você perde mais tempo?
Luc Franken
4
... não tenha muita esperança de que as pessoas olhem corretamente para esses status. A maioria dos programas verifica apenas os mais comuns e falha ou se comporta mal se obtiverem um código de status inesperado. (Lembro que também houve uma apresentação na DefCon sobre a proteção de seu site contra rastreadores, enviando status de saída aleatórios que o navegador ignora e simplesmente exibe por que os rastreadores às vezes consideram erros e, portanto, param de rastrear essa parte do seu site).
Bakuriu 29/08/16

Respostas:

21

A resposta curta e direta

Como a solicitação fala da execução da lista de tarefas (as tarefas são o recurso de que estamos falando aqui), se o grupo de tarefas foi movido para a execução (ou seja, independentemente do resultado da execução), seria sensato que o status da resposta será 200 OK. Caso contrário, se houver um problema que impeça a execução do grupo de tarefas, como falha na validação dos objetos da tarefa ou algum serviço necessário não esteja disponível, por exemplo, o status da resposta deverá indicar esse erro. Depois que, quando a execução das tarefas começa, visto que as tarefas a serem executadas estão listadas no corpo da solicitação, eu esperaria que os resultados da execução fossem listados no corpo da resposta.


A resposta longa e filosófica

Você está enfrentando esse dilema porque está se desviando para o que o HTTP foi projetado. Você não está interagindo para gerenciar recursos; pelo contrário, está usando-o como meio de invocação remota de método (o que não é muito estranho, mas funciona mal sem um esquema pré-concebido).

Com o exposto acima, e sem coragem de transformar essa resposta em um longo guia opinativo, a seguir é apresentado um esquema de URI compatível com uma abordagem de gerenciamento de recursos:

  • /tasks
    • GET lista todas as tarefas, paginadas
    • POST adiciona uma única tarefa
  • /tasks/task/[id]
    • GET responde com o objeto de estado de uma única tarefa
    • DELETE cancela / exclui uma tarefa
  • /tasks/groups
    • GET lista todos os grupos de tarefas, paginados
    • POST adiciona um grupo de tarefas
  • /tasks/groups/group/[id]
    • GET responde com o estado de um grupo de tarefas
    • DELETE cancela / exclui o grupo de tarefas

Essa estrutura fala sobre recursos, não o que fazer com eles. O que está sendo feito com recursos é a preocupação de outro serviço.

Outro ponto importante a destacar é que é aconselhável não bloquear por muito tempo em um manipulador de solicitações HTTP. Assim como a interface do usuário, uma interface HTTP deve ser responsiva - em uma escala de tempo que é algumas ordens de magnitude mais lenta (porque essa camada lida com E / S).

Tomar a decisão de projetar uma interface HTTP que gerencia estritamente os recursos provavelmente é tão difícil quanto afastar o trabalho de um thread da interface do usuário quando um botão é clicado. Requer que o servidor HTTP se comunique com outros serviços para executar tarefas, em vez de executá-las no manipulador de solicitações. Esta não é uma implementação superficial, é uma mudança de direção.


Alguns exemplos de como esse esquema de URI seria usado

Executando uma única tarefa e acompanhando o progresso:

  • POST /tasks com a tarefa de executar
    • GET /tasks/task/[id]até que o objeto de resposta completedtenha um valor positivo enquanto mostra o status / progresso atual

Executando uma única tarefa e aguardando sua conclusão:

  • POST /tasks com a tarefa de executar
    • GET /tasks/task/[id]?awaitCompletion=trueaté que completedtenha valor positivo (provavelmente tem tempo limite, motivo pelo qual isso deve ser repetido)

Executando um grupo de tarefas e acompanhando o progresso:

  • POST /tasks/groups com o grupo de tarefas para executar
    • GET /tasks/groups/group/[groupId]até que a completedpropriedade do objeto de resposta tenha valor, mostrando o status da tarefa individual (3 tarefas concluídas em 5, por exemplo)

Solicitando uma execução para um grupo de tarefas e aguardando sua conclusão:

  • POST /tasks/groups com o grupo de tarefas para executar
    • GET /tasks/groups/group/[groupId]?awaitCompletion=true até responder com resultado que indica conclusão (provavelmente tem tempo limite, e é por isso que deve ser feito um loop)
Ben Barkay
fonte
Eu acho que falar sobre o que faz sentido semanticamente é o caminho certo para abordar isso. Obrigado!
Anders
2
Eu ia propor essa resposta se ela já não estivesse lá. É impossível fazer várias solicitações em uma única solicitação HTTP. Por outro lado, é perfeitamente possível fazer uma única solicitação HTTP que diz "execute as seguintes ações e informe-me quais são os resultados". E é isso que está acontecendo aqui.
Martin Kochanski 30/08
Aceitarei esta resposta mesmo que ela esteja longe de ter mais votos. Embora outras respostas também sejam boas, acho que essa é a única que raciocina sobre a semântica do HTTP.
Anders
87

Meu voto seria dividir essas tarefas em solicitações separadas. No entanto, se há muitas viagens de ida e volta, encontrei o código de resposta HTTP 207 - Multi-Status

Copie / cole a partir deste link:

Uma resposta de vários status transmite informações sobre vários recursos em situações em que vários códigos de status podem ser apropriados. O corpo da resposta Multi-Status padrão é uma entidade HTTP de texto / xml ou aplicativo / xml com um elemento raiz 'multistatus'. Outros elementos contêm códigos de status das séries 200, 300, 400 e 500 gerados durante a invocação do método. Os códigos de status da série 100 NÃO DEVEM ser registrados em um elemento XML de 'resposta'.

Embora '207' seja usado como o código de status de resposta geral, o destinatário precisa consultar o conteúdo do corpo de resposta de vários estados para obter mais informações sobre o sucesso ou falha da execução do método. A resposta pode ser usada no sucesso, sucesso parcial e também em situações de falha.

neilsimp1
fonte
22
207parece ser o que o OP quer, mas eu realmente quero enfatizar que provavelmente é uma má idéia ter essa abordagem de solicitação múltipla em um. Se a preocupação é o desempenho, então você deve ser arquitetar para estilo de nuvem sistemas escalável horizontalmente (que é algo que os sistemas baseados em HTTP são grandes em)
Restabelecer Monica
44
@DavidGrinberg Eu não poderia discordar mais. Se as ações individuais são baratas, a sobrecarga de lidar com uma solicitação pode ser muito mais cara que a própria ação. Sua sugestão pode levar a cenários em que a atualização de várias linhas em um banco de dados é feita usando uma transação separada por linha, porque cada linha é enviada como uma solicitação separada. Isso não é apenas terrivelmente ineficiente, mas também significa que não será possível atualizar várias linhas atomicamente, se necessário. A escala horizontal é importante, mas não substitui projetos eficientes.
precisa saber é
4
Bem dito e apontando um problema típico das implementações da API REST feitas por pessoas ignorantes quanto às realidades das necessidades de negócios, como desempenho e / ou atomicidade. É por isso que, por exemplo, a especificação OData REST possui um formato de lote para várias operações em uma chamada - há uma necessidade real disso.
TomTom
8
@ TomTom, o OP não quer atomicidade. Isso seria muito mais fácil de projetar, pois existe apenas um status de uma operação atômica. Além disso, a especificação HTTP permite operações de lote para desempenho, via multiplexação HTTP / 2 (naturalmente, o suporte a HTTP / 2 é outra questão, mas a especificação permite).
Paul Draper
2
@ David Depois de trabalhar com alguns problemas de HPC no passado, na minha experiência, o custo de enviar um único byte é praticamente o mesmo que enviar mil (meios de transferência diferentes têm despesas gerais diferentes certamente, mas raramente é melhor que isso). Portanto, se o desempenho é uma preocupação, não vejo como o envio de várias solicitações não teria uma sobrecarga grande. Agora, se você pudesse multiplexar várias solicitações pela mesma conexão, esse problema desapareceria, mas, pelo que entendi, essa é apenas uma opção com HTTP / 2 e o suporte a ela é bastante limitado. Ou eu estou esquecendo de alguma coisa?
Voo 29/08
24

Embora o multi-status seja uma opção, eu retornaria 200 (Tudo está bem) se todas as solicitações fossem bem-sucedidas e um erro (500 ou talvez 207) caso contrário.

O caso padrão normalmente deve ser 200 - tudo funciona. E os clientes só precisam verificar isso. E somente se o caso de erro aconteceu, você pode retornar um 500 (ou um 207). Eu acho que o 207 é uma opção válida no caso de pelo menos um erro, mas se você vir o pacote inteiro como uma transação, também poderá enviar 500. - O cliente desejará interpretar a mensagem de erro de qualquer maneira.

Por que nem sempre enviar 207? - Porque os casos padrão devem ser fáceis e padrão. Embora casos excepcionais possam ser excepcionais. Um cliente deve apenas ler o órgão de resposta e tomar decisões complexas adicionais, se uma situação excepcional o justificar.

Falco
fonte
6
Eu não concordo completamente. Se a sub-solicitação 1 e 3 tiver êxito, você obtém um recurso combinado e precisa verificar a resposta combinada de qualquer maneira. Você só tem mais um caso a considerar. Se resposta = 200 ou sub-resposta 1 = 200, a solicitação 1 foi bem-sucedida. Se resposta = 200 ou sub-resposta 2 = 200, solicite 2 com êxito e assim por diante, em vez de apenas testar a sub-resposta.
gnasher729
11
@ gnasher729 isso realmente depende da aplicação. Imagino uma ação orientada ao usuário, que simplesmente fluirá para a próxima etapa com (tudo ok) quando todas as solicitações forem bem-sucedidas. - Se algo der errado (estado global <= 200), será necessário exibir erros detalhados e alterar o fluxo de trabalho, e você só precisará de uma verificação única para cada sub-solicitação, pois está na função "handleMixedState" e não na função "handleAllOk" .
Falco
Realmente depende do que isso significa. Por exemplo, eu tenho um endpoint que controla estratégias de negociação. Você pode "iniciar" uma lista de identificadores em uma execução. O retorno 200 significa que a operação (processá-los) foi bem-sucedida - mas nem todos podem iniciar com êxito. O que, aliás, nem pode ser visto no resultado imediato (que será iniciado), pois a inicialização pode levar alguns segundos. A semântica nas chamadas de operação múltipla depende do cenário.
TomTom
Eu também provavelmente enviaria 500 se houvesse um problema geral (por exemplo, banco de dados inoperante) para que o servidor nem tente solicitações individuais, mas possa retornar um erro geral. - Como existem 3 resultados diferentes para o usuário 1. tudo OK, 2. problema geral, nada funciona, 3. Algumas solicitações falharam. -> O que geralmente leva a um fluxo de programa completamente diferente.
Falco
11
Ok, então uma abordagem seria: 207 = status individual para cada solicitação. Qualquer outra coisa: o status retornado se aplica a cada solicitação. Faz sentido para 200, 401, 500. ≥
gnasher729
13

Uma opção seria sempre retornar um código de status 200 e retornar erros específicos no corpo do documento JSON. É exatamente assim que algumas APIs são projetadas (elas sempre retornam um código de status 200 e enviam o erro no corpo). Para mais detalhes sobre as diferentes abordagens, consulte http://archive.oreilly.com/pub/post/restful_error_handling.html

BigONotation
fonte
2
Nesse caso, eu gosto da ideia de usar o 200para indicar que tudo está bem, a solicitação é recebida e válida e, em seguida, use o JSON para fornecer detalhes sobre o que aconteceu nessa solicitação (ou seja, o resultado das transações).
Rickcnagy
4

Acho que o neilsimp1 está correto, mas eu recomendaria uma reformulação dos dados enviados de forma que você pudesse enviar 206 - Acceptede processar os dados posteriormente. Talvez com retornos de chamada.

O problema de tentar enviar várias ações em uma única solicitação é exatamente o fato de que cada ação deve ter seu próprio "status"

Olhando para importar um CSV (não sei realmente o que é o OP, mas é uma versão simples). POSTE o CSV e retorne um 206. Posteriormente, o CSV poderá ser importado e você poderá obter o status da importação com um GET (200) em um URL que mostra erros por linha.

POST /imports/ -> 206
GET  /imports/1 -> 200
GET  /imports/1/errors -> 200 -> Has a list of errors

Esse mesmo padrão pode ser aplicado a muitas opterações em lote

POST /operations/ -> 206
GET  /operations/1 -> 200
GET  /operations/1/errors -> 200 - > Has a list of errors.

O código que manipula o POST precisa apenas verificar se o formato dos dados de operações é válido. Em algum momento posterior, as operações podem ser executadas. Em um trabalhador em segundo plano, para que você possa escalar com mais facilidade, por exemplo. Depois, você pode verificar o status das operações sempre que desejar. Você pode usar pesquisas ou retornos de chamada, fluxos ou qualquer outra coisa para atender à necessidade de saber quando um conjunto de operações é concluído.

coteyr
fonte
2

Já existem muitas boas respostas aqui, mas um aspecto está faltando:

Qual é o contrato que seus clientes esperam?

Os códigos de retorno HTTP devem indicar pelo menos uma distinção de sucesso / falha e, assim, desempenhar o papel de "exceções do pobre homem". Então 200 significa "contrato completamente cumprido" e 4xx ou 5xx indicam falha no cumprimento.

Ingenuamente, eu esperaria que o contrato da sua solicitação de várias ações fosse "faça todas as minhas tarefas" e, se uma delas falhar, a solicitação não foi (completamente) bem-sucedida. Normalmente, como cliente, eu entendia 200 como "tudo bem" e os códigos das famílias 400 e 500 me forçam a pensar nas consequências de uma falha (parcial). Portanto, use 200 para "todas as tarefas concluídas" e 500, além de uma resposta descritiva em caso de falha parcial.

Um contrato hipotético diferente pode ser "tente executar todas as ações". Então, está completamente de acordo com o contrato se (algumas das) ações falharem. Portanto, você sempre retornaria 200 mais um documento de resultados, onde encontrará as informações de sucesso / falha para as tarefas individuais.

Então, qual é o contrato que você deseja seguir? Ambos são válidos, mas o primeiro (200 apenas no caso de tudo ter sido feito) é mais intuitivo para mim e melhor alinhado aos padrões típicos de software. E para a (espero) maioria dos casos em que o serviço concluiu todas as tarefas, é fácil o cliente detectar esse caso.

Um aspecto final importante: como você comunica sua decisão de contrato a seus clientes? Por exemplo, em Java, eu usaria nomes de métodos como "doAll ()" ou "tryToDoAll ()". No HTTP, você pode nomear os URLs dos terminais adequadamente, esperando que os desenvolvedores de seus clientes vejam, leiam e entendam a nomeação (eu não apostaria nisso). Mais um motivo para escolher o contrato de menor surpresa.

Ralf Kleberhoff
fonte
0

Responda:

Basta usar uma solicitação por ação e aceitar a sobrecarga extra.

Um código de status descreve o status de uma operação. Portanto, faz sentido ter uma operação por solicitação.

Várias operações independentes quebram o princípio no qual o modelo de solicitação-resposta e os códigos de status se baseiam. Você está lutando contra a natureza.

HTTP / 1.1 e HTTP / 2 reduziram muito as solicitações de HTTP. Eu estimo que há muito poucas situações em que é aconselhável agrupar solicitações independentes.


Dito isto,

(1) Você pode fazer várias modificações com uma solicitação PATCH ( RFC 5789 ). No entanto, isso requer que as alterações não sejam independentes; eles são aplicados atomicamente (tudo ou nada).

(2) Outros apontaram o código 207 Multi-Status. No entanto, isso é definido apenas para o WebDAV ( RFC 4918 ), uma extensão do HTTP.

O código de status 207 (Multi-Status) fornece status para várias operações independentes (consulte a Seção 13 para obter mais informações).

...

Uma resposta de vários status transmite informações sobre vários recursos em situações em que vários códigos de status podem ser apropriados. O elemento raiz [XML] 'multistatus' contém zero ou mais elementos de 'resposta' em qualquer ordem, cada um com informações sobre um recurso individual.

Uma resposta de 207 WebDAV XML seria tão estranha quanto um pato em uma API não WebDAV. Não faça isso.

Paul Draper
fonte
11
Você está basicamente afirmando que o @Anders tem um problema XY . Você pode estar certo, mas, infelizmente, isso significa que você realmente não respondeu à pergunta que ele fez (qual código de status usar para a solicitação de várias ações).
Azuaron 29/08/16
2
@ Azuaron, que tipo de cinto funciona melhor para espancar crianças? Eu acho que "N / A" é uma resposta permitida. Além disso, Andres incluiu vários pedidos em sua lista de idéias. Apoiei sinceramente essa opção.
Paul Draper
De alguma forma, senti falta de que ele listasse isso. Nesse caso, afirmo que é uma pergunta boba, Meritíssima!
Azuaron
11
@ Azuaron Eu absolutamente acho que essa é uma resposta válida. Se eu estiver fazendo tudo errado, quero que alguém o diga e não me dê instruções sobre a melhor maneira de descer de um penhasco.
Anders
11
Nada proíbe enviar JSON na resposta 207, desde que o cabeçalho Content-Type esteja configurado corretamente e corresponda ao que o cliente pediu (cabeçalho Accept).
Dolmen
0

Se você realmente precisa ter várias ações em uma solicitação, por que não agrupar todas as ações em uma transação no back-end? Dessa forma, todos têm sucesso ou fracassam.

Como cliente que usa a API, posso lidar com sucesso ou falha completa em uma chamada de API. É difícil lidar com o sucesso parcial, pois eu teria que lidar com todos os possíveis estados resultantes.

reitor
fonte
2
Presumo que, se o pedido fosse atômico, ele não teria postado esta pergunta.
Andy
@ Andy Talvez, mas você não pode assumir que ele considerou todas as implicações desse projeto.
Dean
A solicitação não deve ser atômica - por exemplo, se o nº 2 falhar, as alterações feitas pelo nº 1 ainda deverão persistir. Portanto, agrupar tudo em uma transação não é uma opção.
Anders