In che modo un'API REST dovrebbe gestire le richieste PUT a risorse parzialmente modificabili?


46

Supponiamo che un'API REST, in risposta a una GETrichiesta HTTP , restituisca alcuni dati aggiuntivi in ​​un oggetto secondario owner:

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'Programmer'
  }
}

Chiaramente, non vogliamo che nessuno possa PUTtornare indietro

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'CEO'
  }
}

e avere successo. In effetti, probabilmente non riusciremo nemmeno a implementare un modo per far sì che ciò abbia potenzialmente successo, in questo caso.

Ma questa domanda non riguarda solo gli oggetti secondari: cosa, in generale, si dovrebbe fare con i dati che non dovrebbero essere modificabili in una richiesta PUT?

Dovrebbe essere necessario mancare dalla richiesta PUT?

Dovrebbe essere scartato silenziosamente?

Dovrebbe essere controllato e se differisce dal vecchio valore di quell'attributo, restituire un codice di errore HTTP nella risposta?

O dovremmo usare le patch JSON RFC 6902 invece di inviare l'intero JSON?


2
Tutti questi funzionerebbero. Immagino che dipenda dalle tue esigenze.
Robert Harvey,

Direi che il principio della minima sorpresa indicherebbe che dovrebbe mancare dalla richiesta PUT. Se non è possibile, controllare e se è diverso e restituire con il codice di errore. Lo scarto silenzioso è il peggiore (l'invio da parte dell'utente si aspetta che cambi e tu dici "200 OK").
Maciej Piechotka,

2
@MaciejPiechotka il problema è che non puoi usare lo stesso modello sul put come sull'inserto o ottenere ecc. Preferirei usare lo stesso modello e ci sarebbero semplici regole di autorizzazione sul campo, quindi se inseriscono un valore per un campo che non dovrebbero cambiare, restituiscono un 403 Proibito e se in seguito viene impostata l'autorizzazione per permetterlo ottengono un 401 Non autorizzato se non sono autorizzati
Jimmy Hoffa

@JimmyHoffa: Per modello intendi il formato dei dati (poiché potrebbe essere possibile riutilizzare il modello nel framework MVC Rest a seconda della scelta, se ne viene utilizzato uno qualsiasi - OP non ne ha menzionato nessuno)? Andrei con la rilevabilità se non fossi vincolato dal framework e l'errore iniziale sia leggermente più rilevabile / facile da implementare rispetto al controllo delle modifiche (ok - non dovrei toccare il campo XYZ). In ogni caso, scartare è peggio.
Maciej Piechotka,

Risposte:


46

Non esiste alcuna regola, né nelle specifiche W3C né nelle regole non ufficiali di REST, che afferma che a PUTdeve usare lo stesso schema / modello del corrispondente GET.

È bello se sono simili , ma non è insolito PUTfare le cose in modo leggermente diverso. Ad esempio, ho visto molte API che includono un tipo di ID nel contenuto restituito da a GET, per comodità. Ma con un PUT, quell'ID è determinato esclusivamente dall'URI e non ha alcun significato nel contenuto. Qualsiasi ID trovato nel corpo verrà silenziosamente ignorato.

REST e il web in generale sono fortemente legati al principio di robustezza : "Sii conservatore in ciò che fai [invia], sii liberale in ciò che accetti". Se sei d'accordo filosoficamente con questo, allora la soluzione è ovvia: ignora qualsiasi dato non valido nelle PUTrichieste. Ciò vale sia per i dati immutabili, come nel tuo esempio, sia per le assurdità reali, ad esempio i campi sconosciuti.

PATCHè potenzialmente un'altra opzione, ma non dovresti implementarla a PATCHmeno che tu non stia effettivamente per supportare aggiornamenti parziali. PATCHsignifica aggiornare solo gli attributi specifici che includo nel contenuto ; esso non significa sostituire l'intera entità, ma escludono alcuni campi specifici . Quello di cui stai effettivamente parlando non è in realtà un aggiornamento parziale, è un aggiornamento completo, idempotente e tutto, è solo che parte della risorsa è di sola lettura.

Una buona cosa da fare se si sceglie questa opzione sarebbe quella di rispedire un 200 (OK) con l'entità effettivamente aggiornata nella risposta, in modo che i clienti possano vedere chiaramente che i campi di sola lettura non sono stati aggiornati.

Ci sono certamente alcune persone che la pensano diversamente: che dovrebbe essere un errore tentare di aggiornare una porzione di sola lettura di una risorsa. C'è una giustificazione per questo, principalmente sulla base del fatto che si restituirebbe sicuramente un errore se l' intera risorsa fosse di sola lettura e l'utente tentasse di aggiornarlo. Sicuramente va contro il principio di robustezza, ma potresti considerarlo più "autocompattante" per gli utenti della tua API.

Ci sono due convenzioni per questo, che corrispondono entrambe alle tue idee originali, ma mi espanderò su di esse. Il primo è vietare la visualizzazione dei campi di sola lettura nel contenuto e, se lo fanno, restituire un HTTP 400 (richiesta errata). Le API di questo tipo dovrebbero anche restituire un HTTP 400 se sono presenti altri campi non riconosciuti / non utilizzabili. Il secondo è richiedere che i campi di sola lettura siano identici al contenuto corrente e restituire un 409 (Conflitto) se i valori non corrispondono.

Non mi piace molto il controllo dell'uguaglianza con 409 perché invariabilmente richiede al cliente di fare un GETper recuperare i dati attuali prima di poter fare un PUT. Non è carino e probabilmente porterà a prestazioni scadenti, per qualcuno, da qualche parte. Inoltre, non mi piace davvero 403 (proibito) per questo in quanto implica che l' intera risorsa è protetta, non solo una parte di essa. Quindi la mia opinione è che se devi assolutamente convalidare invece di seguire il principio di robustezza, convalida tutte le tue richieste e restituisci un 400 per quelli che hanno campi extra o non scrivibili.

Assicurati che il tuo 400/409 / qualunque cosa includa informazioni su quale sia il problema specifico e su come risolverlo.

Entrambi questi approcci sono validi, ma preferisco il primo in linea con il principio di solidità. Se hai mai provato a lavorare con un'API REST di grandi dimensioni, apprezzerai il valore della compatibilità con le versioni precedenti. Se decidi di rimuovere un campo esistente o renderlo di sola lettura, è una modifica compatibile con le versioni precedenti se il server ignora questi campi e i vecchi client funzioneranno comunque. Tuttavia, se si fa la validazione rigoroso sul contenuto, è non è compatibile più, clienti vecchi e cesserà di lavoro. Il primo significa generalmente meno lavoro sia per il manutentore di un'API che per i suoi clienti.


1
Buona risposta e votato. Tuttavia, non sono sicuro di essere d'accordo con questo: "Se decidi di rimuovere un campo esistente o renderlo di sola lettura, è una modifica compatibile con le versioni precedenti se il server ignora quei campi e i vecchi client continueranno a funzionare. " Se il client si affidasse a questo campo rimosso / di sola lettura, ciò non influirebbe comunque sul comportamento generale dell'app? Nel caso della rimozione dei campi, direi che probabilmente è meglio generare esplicitamente un errore invece di ignorare i dati; in caso contrario, il client non ha idea che il suo aggiornamento precedentemente funzionante non riesca ora.
Rinogo,

Questa risposta è sbagliata per 2 motivi da RFC2616: 1. (sezione 9.1.2) PUT deve essere indipendente. Metti molte volte e produrrà lo stesso risultato del mettere una sola volta. 2. L'arrivo a una risorsa dovrebbe restituire l'entità inserita se non sono state fatte altre richieste per cambiare la risorsa
brunoais

1
Che cosa succede se si esegue la verifica dell'uguaglianza solo se il valore immutabile è stato inviato nella richiesta. Penso che questo ti dia il meglio dei due mondi; non costringi i clienti a fare un GET e comunichi loro che qualcosa non va se hanno inviato un valore non valido per un valore immutabile.
Ahmad Abdelghany,

Grazie, il confronto approfondito che hai fatto negli ultimi paragrafi dall'esperienza è esattamente quello che stavo cercando.
discesa

9

Potenza idem

A seguito della RFC, un PUT dovrebbe consegnare l'oggetto completo alla risorsa. Il motivo principale di ciò è che PUT dovrebbe essere idempotente. Ciò significa che una richiesta, che viene ripetuta, dovrebbe valutare lo stesso risultato sul server.

Se consenti aggiornamenti parziali, non può più essere potente. Se hai due clienti. Client A e B, quindi il seguente scenario può evolversi:

Il client A ottiene un'immagine dalle immagini delle risorse. Questo contiene una descrizione dell'immagine, che è ancora valida. Il client B inserisce una nuova immagine e aggiorna la descrizione di conseguenza. L'immagine è cambiata. Il cliente A vede, non deve cambiare la descrizione, perché è come desidera e mette solo l'immagine.

Questo porterà ad un'incoerenza, l'immagine ha i metadati sbagliati allegati!

Ancora più fastidioso è che qualsiasi intermediario può ripetere la richiesta. Nel caso decida in qualche modo che il PUT non è riuscito.

Il significato di PUT non può essere modificato (sebbene sia possibile utilizzarlo in modo improprio).

Altre opzioni

Fortunatamente c'è un'altra opzione, questa è PATCH. PATCH è un metodo che consente di aggiornare parzialmente una struttura. Puoi semplicemente inviare una struttura parziale. Per applicazioni semplici, va bene. Questo metodo non è garantito per essere potente. Il client deve inviare una richiesta nel seguente modulo:

PATCH /file.txt HTTP/1.1
Host: www.example.com
Content-Type: application/example
If-Match: "e0023aa4e"
Content-Length: 20
{fielda: 1, fieldc: 2}

E il server può rispondere con 204 (nessun contenuto) per contrassegnare il successo. In caso di errore non è possibile aggiornare una parte della struttura. Il metodo PATCH è atomico.

Lo svantaggio di questo metodo è che non tutti i browser lo supportano, ma questa è l'opzione più naturale in un servizio REST.

Esempio di richiesta di patch: http://tools.ietf.org/html/rfc5789#section-2.1

Patch Json

L'opzione json sembra essere abbastanza completa e un'opzione interessante. Ma può essere difficile implementarlo per terze parti. Devi decidere se la tua base di utenti può gestirlo.

È anche un po 'contorto, perché è necessario creare un piccolo interprete che converte i comandi in una struttura parziale, che verrà utilizzata per aggiornare il modello. Questo interprete dovrebbe anche verificare se i comandi forniti hanno senso. Alcuni comandi si annullano a vicenda. (scrivi fielda, cancella fielda). Penso che tu voglia segnalarlo al cliente per limitare il tempo di debug dalla sua parte.

Ma se hai tempo, questa è una soluzione davvero elegante. Dovresti comunque convalidare i campi ovviamente. È possibile combinare questo con il metodo PATCH per rimanere nel modello REST. Ma penso che POST sarebbe accettabile qui.

Andare male

Se decidi di scegliere l'opzione PUT, che è alquanto rischiosa. Quindi non dovresti almeno scartare l'errore. L'utente ha una certa aspettativa (i dati verranno aggiornati) e se lo rompi, regalerai ad alcuni sviluppatori non un buon momento.

Puoi scegliere di tornare indietro: 409 Conflict o 403 Forbidden. Dipende da come guardi il processo di aggiornamento. Se lo vedi come un insieme di regole (incentrate sul sistema), allora il conflitto sarà più bello. Qualcosa del genere, questi campi non sono aggiornabili. (In conflitto con le regole). Se lo vedi come un problema di autorizzazione (incentrato sull'utente), allora dovresti tornare vietato. Con: non sei autorizzato a modificare questi campi.

Dovresti comunque forzare gli utenti a inviare tutti i campi modificabili.

Un'opzione ragionevole per imporlo è di impostarlo su una sotto-risorsa, che offre solo i dati modificabili.

Opinione personale

Personalmente andrei (se non devi lavorare con i browser) per il semplice modello PATCH e poi estenderlo con un processore di patch JSON. Questo può essere fatto differenziando sui mimetipi: il tipo mime di patch json:

application / json-patch

E json: application / json-patch

semplifica l'implementazione in due fasi.


3
Il tuo esempio di idempotenza non ha senso. O cambi la descrizione o no. Ad ogni modo, otterrai lo stesso risultato, ogni volta.
Robert Harvey,

1
Hai ragione, penso che sia ora di andare a letto. Non posso modificarlo. È più un esempio della razionale di inviare tutti i dati in una richiesta PUT. Grazie per il puntatore.
Edgar Klerks,

So che è stato 3 anni fa ... ma sai dove nella RFC posso trovare ulteriori informazioni su "PUT dovrebbe consegnare l'oggetto completo alla risorsa". Ho visto questo menzionato altrove, ma vorrei vedere come è definito nelle specifiche.
CSharper,

Penso di averlo trovato? tools.ietf.org/html/rfc5789#page-3
CSharper
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.