Quale codice di stato HTTP restituire se più azioni terminano con stati diversi?


72

Sto creando un'API in cui l'utente può chiedere al server di eseguire più azioni in una richiesta HTTP. Il risultato viene restituito come un array JSON, con una voce per azione.

Ognuna di queste azioni potrebbe fallire o riuscire indipendentemente l'una dall'altra. Ad esempio, la prima azione potrebbe avere esito positivo, l'input per la seconda azione potrebbe essere mal formattato e non riuscire a convalidare e la terza azione potrebbe causare un errore imprevisto.

Se ci fosse una richiesta per azione, restituirei i codici di stato 200, 422 e 500 rispettivamente. Ma ora quando c'è una sola richiesta, quale codice di stato devo restituire?

Alcune opzioni:

  • Restituisci sempre 200 e fornisci informazioni più dettagliate nel corpo.
  • Forse seguire la regola sopra solo quando c'è più di un'azione nella richiesta?
  • Forse restituire 200 se tutte le richieste hanno esito positivo, altrimenti 500 (o qualche altro codice)?
  • Usa solo una richiesta per azione e accetta l'overhead aggiuntivo.
  • Qualcosa di completamente diverso?

3
La tua domanda mi ha fatto riflettere su un altro: programmers.stackexchange.com/questions/309147/…
AilurusFulgens,

7
Leggermente anche correlato: programmers.stackexchange.com/questions/305250/… (vedi la risposta accettata sulla separazione tra codici di stato HTTP e codici applicazione)

4
Qual è il vantaggio che ottieni raggruppando queste richieste? Riguarda la logica aziendale, come una transazione su più risorse, o riguarda le prestazioni? O qualcos'altro?
Luc Franken,

5
Ok, in tal caso suggerirei vivamente di migliorare tale prestazione in altre aree. Prova cose come l'interfaccia utente ottimistica, il batch di richieste, la memorizzazione nella cache ecc. Prima di implementare questa complessità nel tuo livello aziendale. Hai una visione chiara di dove perdi la maggior parte del tempo?
Luc Franken,

4
... non essere troppo fiducioso che le persone guarderanno correttamente quegli stati. La maggior parte dei programmi controlla solo quelli più comuni e si guasta o si comporta male se ottengono un codice di stato imprevisto. (Ricordo che c'era anche una presentazione a DefCon sulla protezione del tuo sito dai crawler inviando stati di uscita casuali che il browser ignora e mostra semplicemente perché a volte i crawler diventano errori e quindi smettono di scansionare quella parte del tuo sito Web).
Bakuriu,

Risposte:


21

La risposta breve e diretta

Poiché la richiesta parla dell'esecuzione dell'elenco di attività (le attività sono la risorsa di cui stiamo parlando qui), quindi se il gruppo di attività è stato spostato in avanti all'esecuzione (ovvero, indipendentemente dal risultato dell'esecuzione), sarebbe sensato che lo stato della risposta sarà 200 OK. Altrimenti, se si verificasse un problema che impedisce l'esecuzione del gruppo di attività, ad esempio la mancata convalida degli oggetti dell'attività o un servizio richiesto non è disponibile, ad esempio, lo stato della risposta dovrebbe indicare quell'errore. Oltre questo, quando inizia l'esecuzione delle attività, visto che le attività da eseguire sono elencate nel corpo della richiesta, mi aspetterei che i risultati dell'esecuzione vengano elencati nel corpo della risposta.


La lunga risposta filosofica

Stai vivendo questo dilemma perché stai deviando da ciò per cui è stato progettato HTTP. Non lo stai interagendo per gestire le risorse, piuttosto, lo stai usando come mezzo di invocazione del metodo remoto (che non è molto strano, tuttavia funziona male senza uno schema preconcetto).

Detto questo, e senza il coraggio di trasformare questa risposta in una lunga guida supponente, il seguente è uno schema URI conforme a un approccio di gestione delle risorse:

  • /tasks
    • GET elenca tutte le attività, impaginate
    • POST aggiunge una singola attività
  • /tasks/task/[id]
    • GET risponde con un oggetto stato di una singola attività
    • DELETE annulla / elimina un'attività
  • /tasks/groups
    • GET elenca tutti i gruppi di attività, impaginati
    • POST aggiunge un gruppo di attività
  • /tasks/groups/group/[id]
    • GET risponde con lo stato di un gruppo di attività
    • DELETE annulla / elimina il gruppo di attività

Questa struttura parla di risorse, non di cosa farne. Ciò che viene fatto con le risorse è la preoccupazione di un altro servizio.

Un altro punto importante da sottolineare è che è consigliabile non bloccare a lungo in un gestore di richieste HTTP. Proprio come l'interfaccia utente, un'interfaccia HTTP dovrebbe essere reattiva - in una scala temporale di alcuni ordini di grandezza più lenta (perché questo livello si occupa di IO).

Passare alla progettazione di un'interfaccia HTTP che gestisca rigorosamente le risorse è probabilmente difficile quanto spostare il lavoro lontano da un thread dell'interfaccia utente quando si fa clic su un pulsante. Richiede che il server HTTP comunichi con altri servizi per eseguire attività anziché eseguirle nel gestore richieste. Questa non è un'implementazione superficiale, è un cambio di direzione.


Alcuni esempi di come verrebbe utilizzato tale schema URI

Esecuzione di una singola attività e monitoraggio dell'avanzamento:

  • POST /tasks con il compito da eseguire
    • GET /tasks/task/[id]finché l'oggetto della risposta completedha un valore positivo mentre mostra lo stato / l'avanzamento correnti

Esecuzione di una singola attività e in attesa del suo completamento:

  • POST /tasks con il compito da eseguire
    • GET /tasks/task/[id]?awaitCompletion=truefino a quando completedha un valore positivo (probabilmente ha timeout, motivo per cui questo dovrebbe essere ripetuto)

Esecuzione di un gruppo di attività e monitoraggio dell'avanzamento:

  • POST /tasks/groups con il gruppo di attività da eseguire
    • GET /tasks/groups/group/[groupId]fino a quando la completedproprietà dell'oggetto response ha valore, mostrando lo stato delle singole attività (3 attività completate su 5, ad esempio)

Richiesta di un'esecuzione per un gruppo di attività e in attesa del suo completamento:

  • POST /tasks/groups con il gruppo di attività da eseguire
    • GET /tasks/groups/group/[groupId]?awaitCompletion=true fino a quando non risponde con un risultato che indica il completamento (probabilmente ha timeout, motivo per cui dovrebbe essere ripetuto)

Penso che parlare di ciò che ha senso semanticamente sia il modo giusto di affrontarlo. Grazie!
Anders,

2
Avrei proposto questa risposta se non ci fosse già stata. È impossibile effettuare più richieste in una singola richiesta HTTP. D'altra parte, è perfettamente possibile effettuare una singola richiesta HTTP che dice "esegui le seguenti azioni e fammi sapere quali sono i risultati". E questo è ciò che sta accadendo qui.
Martin Kochanski,

Accetterò questa risposta anche se non ha il maggior numero di voti. Anche se le altre risposte sono buone, penso che questa sia l'unica a ragionare sulla semantica dell'HTTP.
Anders,

87

Il mio voto sarebbe quello di dividere questi compiti in richieste separate. Tuttavia, se troppi viaggi di andata e ritorno sono fonte di preoccupazione, mi sono imbattuto nel codice di risposta HTTP 207 - Multi-Status

Copia / incolla da questo link:

Una risposta multi-stato trasmette informazioni su più risorse in situazioni in cui potrebbero essere appropriati più codici di stato. Il corpo di risposta multi-stato predefinito è un'entità HTTP text / xml o application / xml con un elemento radice "multistatus". Ulteriori elementi contengono i codici di stato delle serie 200, 300, 400 e 500 generati durante l'invocazione del metodo. I codici di stato serie 100 NON DOVREBBERO essere registrati in un elemento XML di "risposta".

Sebbene "207" sia utilizzato come codice di stato della risposta globale, il destinatario deve consultare il contenuto dell'organismo di risposta a più stati per ulteriori informazioni sull'esito positivo o negativo dell'esecuzione del metodo. La risposta PU MAY essere utilizzata in caso di successo, parziale successo e anche in situazioni di fallimento.


22
207sembra essere quello che vuole l'OP, ma voglio davvero sottolineare che è probabilmente una cattiva idea avere questo approccio multi-richiesta-in-uno. Se la preoccupazione riguarda le prestazioni, allora dovresti progettare sistemi scalabili orizzontalmente in stile cloud (che è qualcosa su cui i sistemi basati su HTTP sono eccezionali)
Ripristina Monica il

44
@DavidGrinberg Non potrei essere più in disaccordo. Se le singole azioni sono economiche, il sovraccarico di gestione di una richiesta può essere molto più costoso dell'azione stessa. Il tuo suggerimento potrebbe portare a scenari in cui l'aggiornamento di più righe in un database viene eseguito utilizzando una transazione separata per riga perché ogni riga viene inviata come richiesta separata. Questo non è solo orribilmente inefficiente, ma significa anche che non sarà possibile aggiornare atomicamente più righe se necessario. Il ridimensionamento orizzontale è importante ma non sostituisce progetti efficienti.
Kasperd,

4
Ben detto e sottolineando un tipico problema delle implementazioni dell'API REST eseguite da persone ignoranti verso le realtà delle esigenze aziendali come prestazioni e / o atomicità. Ecco perché, ad esempio, la specifica REST di OData ha un formato batch per più operazioni in una chiamata: ce n'è davvero bisogno.
TomTom,

8
@ TomTom, l'OP non vuole l'atomicità. Sarebbe una cosa molto più semplice da progettare, poiché esiste solo uno stato di un'operazione atomica. Inoltre, le specifiche HTTP consentono operazioni di batch per le prestazioni, tramite il multiplexing HTTP / 2 (naturalmente, il supporto HTTP / 2 è un'altra questione, ma le specifiche lo consentono).
Paul Draper,

2
@David Avendo lavorato su alcuni problemi HPC in passato, nella mia esperienza il costo di invio di un singolo byte è praticamente lo stesso di quello di invio di un migliaio (diversi mezzi di trasferimento hanno sicuramente un sovraccarico diverso, ma raramente è meglio di così). Quindi, se le prestazioni sono un problema, non vedo come l'invio di più richieste non comporterebbe grandi spese generali. Ora, se potessi multiplexare più richieste sulla stessa connessione, questo problema scomparirebbe, ma a quanto ho capito, questa è solo un'opzione con HTTP / 2 e il supporto è piuttosto limitato. O mi sta sfuggendo qualcosa?
Voo,

24

Anche se il multi-stato è un'opzione, restituirei 200 (Tutto va bene) se tutte le richieste hanno avuto successo e un errore (500 o forse 207) in caso contrario.

Il caso standard dovrebbe di solito essere 200: tutto funziona. E i clienti dovrebbero solo verificarlo. E solo se si è verificato il caso di errore è possibile restituire un 500 (o un 207). Penso che la 207 sia una scelta valida nel caso di almeno un errore, ma se vedi l'intero pacchetto come una transazione potresti anche inviare 500. - Il cliente vorrà interpretare il messaggio di errore in entrambi i modi.

Perché non inviare sempre 207? - Perché i casi standard dovrebbero essere semplici e standard. Mentre casi eccezionali possono essere eccezionali. Un cliente dovrebbe solo leggere l'organismo di risposta e prendere ulteriori decisioni complesse, se giustificato da una situazione eccezionale.


6
Non sono del tutto d'accordo. Se le richieste secondarie 1 e 3 hanno esito positivo, si ottiene una risorsa combinata e si deve comunque verificare la risposta combinata. Hai solo un altro caso da considerare. Se response = 200 o subresponse 1 = 200, allora la richiesta 1 ha avuto esito positivo. Se response = 200 o subresponse 2 = 200, allora la richiesta 2 ha esito positivo e così via invece di provare semplicemente la risposta secondaria.
gnasher729,

1
@ gnasher729 dipende davvero dall'applicazione. Immagino un'azione guidata dall'utente, che passerà semplicemente al passaggio successivo con (tutto ok) quando tutte le richieste avranno esito positivo. - Se qualcosa è andato storto (stato globale <= 200), è necessario visualizzare errori dettagliati e modificare il flusso di lavoro e è necessario un solo controllo per ogni sottorequisito, poiché si è nella funzione "handleMixedState" e non nella funzione "handleAllOk" .
Falco,

Dipende davvero da cosa significa. Ad esempio, ho un endpoint che controlla le strategie di trading. È possibile "avviare" un elenco di identificatori in una corsa. Restituisce 200 significa che l'operazione (elaborale) è riuscita - ma non tutte potrebbero avviarsi correttamente. Il che, tra l'altro, non può essere visto nemmeno nel risultato immediato (che inizierà) poiché l'avvio può richiedere alcuni secondi. La semantica nelle chiamate multi-operazione dipende dallo scenario.
TomTom,

Molto probabilmente invierei anche un 500 se ci fosse un problema generale (ad es. Database inattivo) in modo che il server non provi nemmeno le singole richieste, ma possa semplicemente restituire un errore generale. - Perché l'utente ha 3 risultati diversi 1. tutto OK, 2. problema generale, nulla funziona, 3. Alcune richieste non sono riuscite. -> Che di solito porta a un flusso di programma completamente diverso.
Falco,

1
Ok, quindi un approccio sarebbe: 207 = stato individuale per ogni richiesta. Nient'altro: lo stato restituito si applica a ciascuna richiesta. Ha senso per 200, 401, ≥ 500.
gnasher729

13

Un'opzione sarebbe quella di restituire sempre un codice di stato 200 e quindi restituire errori specifici nel corpo del documento JSON. Questo è esattamente il modo in cui alcune API sono progettate (restituiscono sempre un codice di stato 200 e inviano l'errore nel corpo). Per maggiori dettagli sui diversi approcci, consultare http://archive.oreilly.com/pub/post/restful_error_handling.html


2
In questo caso, mi piace l'idea di utilizzare il 200per indicare che tutto va bene, la richiesta è stata ricevuta ed è stata valida , quindi utilizzare JSON per fornire dettagli su ciò che è accaduto in tale richiesta (ovvero il risultato delle transazioni).
rickcnagy,

4

Penso che neilsimp1 sia corretto, ma raccomanderei una riprogettazione dei dati inviati in modo tale da poterli inviare 206 - Accepteded elaborare i dati in un secondo momento. Forse con richiamate.

Il problema con il tentativo di inviare più azioni in una singola richiesta è esattamente il fatto che ogni azione dovrebbe avere il proprio "stato"

Guardando all'importazione di un CSV (non so davvero di cosa tratta l'OP ma è una versione semplice). POST il CSV e indietro un 206. Quindi in seguito il CSV può essere importato e puoi ottenere lo stato dell'importazione con un GET (200) su un URL che mostra errori per riga.

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

Lo stesso modello può essere applicato a molte opzioni batch

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

Il codice che gestisce il POST deve solo verificare che il formato dei dati delle operazioni sia valido. Quindi in un secondo momento le operazioni potrebbero essere eseguite. In un lavoratore a terra, quindi puoi ridimensionare più facilmente, ad esempio. Quindi puoi controllare lo stato delle operazioni quando vuoi. È possibile utilizzare polling o callback, stream o altro per rispondere alla necessità di sapere quando una serie di operazioni viene completata.


2

Già molte buone risposte qui, ma manca un aspetto:

Qual è il contratto che i tuoi clienti si aspettano?

I codici di ritorno HTTP dovrebbero indicare almeno una distinzione tra successo / fallimento e quindi svolgere il ruolo di "eccezioni dei poveri". Quindi 200 significa "contratto completamente adempiuto" e 4xx o 5xx indicano inadempimento.

Ingenuamente, mi aspetto che il contratto della tua richiesta di azioni multiple sia "svolgi tutti i miei compiti", e se uno di loro fallisce, la richiesta non ha (completamente) avuto successo. In genere, come cliente, capisco 200 come "tutto ok", e i codici della famiglia 400 e 500 mi costringono a pensare alle conseguenze di un (parziale) fallimento. Quindi, utilizzare 200 per "tutte le attività eseguite" e 500 più una risposta descrittiva in caso di errore parziale.

Un diverso contratto ipotetico potrebbe essere "provare a fare tutte le azioni". Quindi è completamente in linea con il contratto se (alcune) delle azioni falliscono. Quindi restituiresti sempre 200 oltre a un documento di risultati in cui trovi le informazioni sull'esito positivo / negativo delle singole attività.

Allora, qual è il contratto che vuoi seguire? Entrambi sono validi, ma il primo (200 solo se tutto è stato fatto) è più intuitivo per me e meglio in linea con i tipici schemi software. E per la (si spera) maggior parte dei casi in cui il servizio ha completato tutte le attività, è semplice per il cliente rilevare quel caso.

Un ultimo aspetto importante: come comunichi la tua decisione di contratto ai tuoi clienti? Ad esempio in Java, utilizzerei nomi di metodi come "doAll ()" o "tryToDoAll ()". In HTTP, puoi nominare gli URL degli endpoint di conseguenza, sperando che i tuoi sviluppatori client vedano, leggano e comprendano i nomi (non scommetterei su questo). Un motivo in più per scegliere il contratto di minor sorpresa.


0

Risposta:

Usa solo una richiesta per azione e accetta l'overhead aggiuntivo.

Un codice di stato descrive lo stato di un'operazione. Pertanto, ha senso eseguire un'operazione per richiesta.

Più operazioni indipendenti interrompono l'entità su cui si basano il modello di richiesta-risposta e i codici di stato. Stai combattendo la natura.

HTTP / 1.1 e HTTP / 2 hanno ridotto notevolmente l'overhead delle richieste HTTP. Stimo che ci sono pochissime situazioni in cui è consigliabile raggruppare le richieste indipendenti.


Detto ciò,

(1) È possibile apportare più modifiche con una richiesta PATCH ( RFC 5789 ). Tuttavia, ciò richiede che le modifiche non siano indipendenti; sono applicati atomicamente (tutto o niente).

(2) Altri hanno sottolineato il codice 207 multi-stato. Tuttavia, questo è definito solo per WebDAV ( RFC 4918 ), un'estensione di HTTP.

Il codice di stato 207 (Multi-Status) fornisce lo stato per più operazioni indipendenti (vedere la Sezione 13 per ulteriori informazioni).

...

Una risposta multi-stato trasmette informazioni su più risorse in situazioni in cui potrebbero essere appropriati più codici di stato. L'elemento root [XML] "multistatus" contiene zero o più elementi "response" in qualsiasi ordine, ciascuno con informazioni su una singola risorsa.

Una risposta XML WebDAV 207 sarebbe strana come un'anatra in un'API non WebDAV. Non farlo


1
Stai essenzialmente affermando che @Anders ha un problema XY . Potresti avere ragione, ma sfortunatamente, ciò significa che non hai effettivamente risposto alla domanda che ha posto (quale codice di stato utilizzare per la richiesta multi-azione).
Azuaron,

2
@Azuaron, che tipo di cintura funziona meglio per picchiare i bambini? Penso che "N / A" sia una risposta consentita. Inoltre, Andres ha incluso più richieste nel suo elenco di idee. Ho sostenuto con tutto il cuore questa opzione.
Paul Draper,

In qualche modo mi mancava che lo elencasse. In tal caso, asserisco che è una domanda sciocca, Vostro Onore!
Azuaron,

1
@Azuaron Penso assolutamente che questa sia una risposta valida. Se sto sbagliando tutto, voglio che qualcuno lo dica e non mi dia istruzioni su come guidare al meglio da una scogliera.
Anders,

1
Nulla vieta di inviare JSON nella risposta 207, purché l'intestazione Content-Type sia impostata correttamente e corrisponda a quanto richiesto dal client (Accetta intestazione).
dolmen,

0

Se hai davvero bisogno di avere più azioni in una richiesta, perché non racchiudere tutte le azioni in una transazione nel back-end? In questo modo o tutti hanno successo o tutti falliscono.

Come client che utilizza l'API, posso gestire l'esito positivo o negativo di una chiamata API. Il successo parziale è difficile da affrontare, dal momento che dovrei gestire tutti i possibili stati risultanti.


2
Suppongo che se la richiesta fosse atomica, non avrebbe pubblicato questa domanda.
Andy

@Andy Forse, ma non puoi presumere che abbia considerato tutte le implicazioni di un tale design.
Decano del

La richiesta non dovrebbe essere atomica - ad es. Se il n. 2 fallisce, le modifiche apportate dal n. 1 dovrebbero comunque persistere. Quindi racchiudere tutto in una transazione non è un'opzione.
Anders,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.