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

68

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

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

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

Alcune opzioni:

  • Restituisci sempre 200 e fornisci informazioni più dettagliate nel corpo.
  • Forse segui la regola precedente solo quando c'è più di un'azione nella richiesta?
  • Forse restituisce 200 se tutte le richieste hanno successo, altrimenti 500 (o qualche altro codice)?
  • Utilizza solo una richiesta per azione e accetta l'overhead aggiuntivo.
  • Qualcosa di completamente diverso?
posta Anders 29.08.2016 - 14:47
fonte

8 risposte

20

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 per l'esecuzione (cioè, indipendentemente del risultato dell'esecuzione), allora sarebbe ragionevole che lo stato della risposta sia 200 OK . Altrimenti, se ci fosse un problema che impedisse l'esecuzione del gruppo di task, come la mancata validazione degli oggetti task , o qualche servizio richiesto non è disponibile per esempio, allora lo stato della risposta dovrebbe denotare che errore. Passato, quando l'esecuzione delle attività inizia, visto che le attività da eseguire sono elencate nel corpo della richiesta, allora mi aspetto che i risultati dell'esecuzione vengano elencati nel corpo della risposta.

La lunga risposta filosofica

Stai riscontrando questo dilemma perché stai deviando da ciò per cui è stato progettato HTTP. Non lo stai interagendo per gestire le risorse, piuttosto lo stai utilizzando come metodo di chiamata a metodi remota (che non è molto strano, tuttavia funziona male senza uno schema preconcetto).

Con quanto detto sopra, e senza il coraggio di trasformare questa risposta in una guida a lungo termine, il seguente è uno schema URI che si conforma a un approccio di gestione delle risorse:

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

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

Un altro punto importante da fare è che è consigliabile non bloccare per molto tempo un gestore di richieste HTTP. Molto simile all'interfaccia utente, un'interfaccia HTTP dovrebbe essere reattiva, in un intervallo di tempo inferiore di alcuni ordini di grandezza (perché questo strato si occupa dell'IO).

Il passaggio alla progettazione di un'interfaccia HTTP che gestisca rigorosamente le risorse è probabilmente difficile quanto spostare il lavoro 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 tale schema URI sarebbe usato

Esecuzione di una singola attività e avanzamento del monitoraggio:

  • POST con l'attività da eseguire
    • /tasks/groups/group/[id] fino a quando l'oggetto di risposta GET ha un valore positivo mentre mostra lo stato corrente / progresso

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

  • DELETE con l'attività da eseguire
    • POST /tasks fino a GET /tasks/task/[id] ha un valore positivo (probabilmente ha un timeout, motivo per cui questo dovrebbe essere ripetuto in loop)

Esecuzione di un gruppo di task e avanzamento del monitoraggio:

  • completed con il gruppo di attività da eseguire
    • POST /tasks fino a quando l'oggetto risposta GET /tasks/task/[id]?awaitCompletion=true ha valore, mostrando lo stato di un singolo compito (3 attività completate su 5, ad esempio)

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

  • completed con il gruppo di attività da eseguire
    • POST /tasks/groups fino a quando risponde con un risultato che denota completamento (probabilmente ha un timeout, motivo per cui dovrebbe essere ripetuto)
risposta data 30.08.2016 - 12:22
fonte
82

Il mio voto sarebbe dividere queste attività in richieste separate. Tuttavia, se troppi round trip sono un problema, ho trovato codice di risposta HTTP 207 - Multi-Status

Copia / incolla da questo 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.

    
risposta data 29.08.2016 - 14:54
fonte
23

Sebbene il multi-status sia un'opzione, restituirei 200 (Tutto va bene) se tutte le richieste sono riuscite e un errore (500 o forse 207) altrimenti.

Il caso standard dovrebbe di solito essere 200 - tutto funziona. E i clienti dovrebbero solo controllarlo. 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 client vorrà interpretare l'errore-messaggio in ogni caso.

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 il corpo della risposta e prendere ulteriori decisioni complesse, se una situazione eccezionale lo giustifica.

    
risposta data 29.08.2016 - 16:10
fonte
13

Un'opzione consisterebbe nel restituire sempre un codice di stato 200 e quindi restituire errori specifici nel corpo del documento JSON. Questo è esattamente il modo in cui sono progettate alcune API (restituiscono sempre un codice di stato 200 e inviano l'errore nel corpo). Per ulteriori dettagli sui diversi approcci, consulta link

    
risposta data 30.08.2016 - 02:05
fonte
4

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

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 cosa sia l'OP ma è una versione semplice). POST il CSV e recupera un 206. Successivamente è possibile importare il CSV ed è possibile ottenere lo stato dell'importazione con un GET (200) rispetto a un URL che mostra gli errori per riga.

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

Questo stesso pattern può essere applicato a molte opzioni di 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 possono essere eseguite. Ad esempio, in un lavoratore di terra posteriore, in modo da poter scalare più facilmente. Quindi puoi controllare lo stato delle operazioni quando vuoi. È possibile utilizzare il polling o richiamate, o flussi o qualsiasi altra cosa per soddisfare la necessità di sapere quando una serie di operazioni complete.

    
risposta data 30.08.2016 - 01:26
fonte
1

Ci sono 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 successo / fallimento e quindi svolgere il ruolo di "eccezioni di un povero uomo". Quindi 200 significa "contratto completamente soddisfatto" e 4xx o 5xx indicano inadempimento.

Ingenuamente, mi aspetterei che il contratto della tua richiesta di più azioni sia "fare tutti i miei compiti" e, se uno di essi fallisce, la richiesta non è stata (completamente) vincente. Tipicamente, come cliente, comprendo 200 come "tutto ok", ei codici della famiglia 400 e 500 mi costringono a pensare alle conseguenze di un (parziale) fallimento. Quindi, usa 200 per "tutte le attività fatte" e 500 più una risposta descrittiva in caso di un fallimento parziale.

Un diverso contratto ipotetico potrebbe essere "provare a fare tutte le azioni". Quindi è completamente in linea con il contratto se (alcune) le azioni falliscono. Quindi restituiresti sempre 200 e un documento dei risultati dove trovi le informazioni su successo / fallimento per le singole attività.

Quindi, qual è il contratto che vuoi seguire? Entrambi sono validi, ma il primo (200 solo nel caso in cui sia stato fatto tutto) è più intuitivo per me, e meglio in linea con i tipici modelli di software. E per la (auspicabilmente) maggioranza dei casi in cui il servizio ha completato tutte le attività, è semplice per il cliente rilevare tale caso.

Un ultimo aspetto importante: come comunichi la tua decisione contrattuale ai tuoi clienti? Per esempio. in Java, userei 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 capiscano la denominazione (non ci scommetterei su questo). Un motivo in più per scegliere il contratto di sorpresa minima.

    
risposta data 10.09.2018 - 20:58
fonte
0

Risposta:

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

Un codice di stato descrive lo stato di una operazione. Pertanto, ha senso avere una operazione per richiesta.

Più operazioni indipendenti rompono il principio 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 reso il sovraccarico delle richieste HTTP molto più basso. Stimo che ci siano pochissime situazioni in cui è consigliabile eseguire richieste di batch indipendenti.

Detto questo,

(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 indicato il codice 207 multi-stato. Tuttavia, questo è definito solo per WebDAV ( RFC 4918 ), un'estensione di 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.

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

    
risposta data 29.08.2016 - 20:37
fonte
0

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

Come client che utilizza l'API, posso gestire il successo o l'insuccesso completo in una chiamata API. Un successo parziale è difficile da gestire, poiché dovrei gestire tutti i possibili stati risultanti.

    
risposta data 29.08.2016 - 21:23
fonte

Leggi altre domande sui tag