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

68

Estou criando uma API em que o usuário pode solicitar que o servidor execute várias ações em uma solicitação HTTP. O resultado é retornado como um array 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 ser bem-sucedida, 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, 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 somente quando houver mais de uma ação na solicitação?
  • Talvez devolver 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?
por Anders 29.08.2016 / 14:47
fonte

8 respostas

20

A resposta curta e direta

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

A longa resposta filosófica

Você está passando por esse dilema porque está se desviando do que o HTTP foi projetado. Você não está interagindo para gerenciar recursos, mas sim para invocar métodos remotos (o que não é muito estranho, mas funciona mal sem um esquema preconcebido).

Com o acima dito, e sem coragem para transformar esta resposta em um longo guia opinativo, o seguinte é um esquema URI que está em conformidade com uma abordagem de gestão de recursos:

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

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

Outro ponto importante a ser feito é 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 IO).

Fazer o movimento em direção à criação de uma interface HTTP que gerencia estritamente recursos provavelmente é tão difícil quanto mover o trabalho para longe de um thread da interface do usuário quando um botão é clicado. Isso 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

Execução de uma única tarefa e acompanhamento do progresso:

  • POST com a tarefa a executar
    • /tasks/groups/group/[id] até o objeto de resposta GET ter valor positivo ao mostrar status / progresso atual

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

  • DELETE com a tarefa a executar
    • POST /tasks até GET /tasks/task/[id] ter valor positivo (provavelmente tem tempo limite, e é por isso que isso deve ser repetido)

Execução de um grupo de tarefas e acompanhamento do progresso:

  • completed com o grupo de tarefas a executar
    • POST /tasks até o objeto de resposta GET /tasks/task/[id]?awaitCompletion=true property ter valor, mostrando o status da tarefa individual (3 tarefas concluídas de 5, por exemplo)

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

  • completed com o grupo de tarefas a executar
    • POST /tasks/groups until responde com resultado que indica conclusão (provavelmente tem timeout, e é por isso que deve ser colocado em loop)
por 30.08.2016 / 12:22
fonte
82

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

Copie / cole deste link:

A Multi-Status response conveys information about multiple resources in situations where multiple status codes might be appropriate. The default Multi-Status response body is a text/xml or application/xml HTTP entity with a 'multistatus' root element. Further elements contain 200, 300, 400, and 500 series status codes generated during the method invocation. 100 series status codes SHOULD NOT be recorded in a 'response' XML element.

Although '207' is used as the overall response status code, the recipient needs to consult the contents of the multistatus response body for further information about the success or failure of the method execution. The response MAY be used in success, partial success and also in failure situations.

    
por 29.08.2016 / 14:54
fonte
23

Allthough multi-status é uma opção, eu retornaria 200 (tudo está bem) se todas as solicitações fossem bem-sucedidas e um erro (500 ou talvez 207) de outra forma.

O caso padrão geralmente 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 escolha válida no caso de pelo menos um erro, mas se você vir todo o pacote como uma transação, você também pode enviar 500. - O cliente vai querer interpretar a mensagem de erro de qualquer forma.

Por que não enviar sempre 207? - Porque os casos padrão devem ser fáceis e padronizados. Enquanto casos excepcionais podem ser excepcionais. Um cliente só deve ler o corpo de resposta e tomar decisões complexas, se uma situação excepcional o justificar.

    
por 29.08.2016 / 16:10
fonte
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 despacham o erro no corpo). Para mais detalhes sobre as diferentes abordagens, consulte o link

    
por 30.08.2016 / 02:05
fonte
4

Acho neilsimp1 está correto, mas eu recomendaria uma reformulação dos dados sendo enviados de tal forma que você poderia enviar um 206 - Accepted e processar os dados mais tarde. Talvez com retornos de chamada.

O problema em 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 arquivo CSV (não sei qual é o OP, mas é uma versão simples). POSTE o CSV e recupere um 206. Posteriormente, o CSV pode ser importado e você pode obter o status da importação com um GET (200) em relação a 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 só precisa verificar se o formato dos dados de operações é válido. Então, em algum momento posterior, as operações podem ser executadas. Em um trabalhador de solo de volta, assim você pode escalar mais fácil, por exemplo. Então você pode verificar o status das operações sempre que quiser. Você pode usar polling ou call backs, ou fluxos ou o que for necessário para atender à necessidade de saber quando um conjunto de operações é concluído.

    
por 30.08.2016 / 01:26
fonte
1

Já 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 sucesso / falha e, assim, desempenhar o papel de "exceções do pobre". Em seguida, 200 significa "contrato completamente cumprido" e 4xx ou 5xx indicam falha no cumprimento.

Ingenuamente, esperaria que o contrato de 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 entendo 200 como "tudo ok", e os códigos da família 400 e 500 me forçam a pensar nas consequências de uma falha (parcial). Portanto, use 200 para "todas as tarefas executadas" e 500, além de uma resposta descritiva no caso de uma falha parcial.

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

Então, qual é o contrato que você quer 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 (esperançosamente) maioria dos casos em que o serviço concluiu todas as tarefas, é simples para o cliente detectar esse caso.

Um último aspecto 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 ()". Em HTTP, você pode nomear as URLs do terminal de acordo, esperando que seus desenvolvedores clientes vejam, leiam e entendam a nomenclatura (eu não apostaria nisso). Mais uma razão para escolher o contrato de menor surpresa.

    
por 10.09.2018 / 20:58
fonte
0

Resposta:

Just use one request per action, and accept the extra overhead.

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 dividem o principal no qual o modelo de solicitação-resposta e os códigos de status são baseados. Você está lutando contra a natureza.

HTTP / 1.1 e HTTP / 2 tornaram a sobrecarga de solicitações HTTP muito menor. Eu estimo que há pouquíssimas situações em que é aconselhável o envio de 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 mudanças para não 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.

The 207 (Multi-Status) status code provides status for multiple independent operations (see Section 13 for more information).

...

A Multi-Status response conveys information about multiple resources in situations where multiple status codes might be appropriate. The 'multistatus' root [XML] element holds zero or more 'response' elements in any order, each with information about an individual resource.

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

    
por 29.08.2016 / 20:37
fonte
0

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

Como cliente usando a API, posso lidar com sucesso ou falha total em uma chamada de API. O sucesso parcial é difícil de lidar, pois eu teria que lidar com todos os possíveis estados resultantes.

    
por 29.08.2016 / 21:23
fonte

Tags