Chiamare un metodo lato server su una risorsa in modo RESTful


142

Tieni presente che ho una comprensione rudimentale di REST. Diciamo che ho questo URL:

http://api.animals.com/v1/dogs/1/

E ora, voglio fare in modo che il server faccia abbaiare il cane. Solo il server sa come farlo. Diciamo che voglio farlo funzionare su un lavoro CRON che fa abbaiare il cane ogni 10 minuti per il resto dell'eternità. Che aspetto ha quella chiamata? Voglio fare questo:

Richiesta URL:

ACTION http://api.animals.com/v1/dogs/1/

Nel corpo della richiesta:

{"action":"bark"}

Prima che ti arrabbi con me per aver inventato il mio metodo HTTP, aiutami e dammi un'idea migliore su come dovrei invocare un metodo sul lato server in modo RESTful. :)

MODIFICA PER CHIARIMENTI

Qualche chiarimento in più su ciò che fa il metodo "corteccia". Ecco alcune opzioni che possono comportare chiamate API strutturate in modo diverso:

  1. bark invia semplicemente un'e-mail a dog.email e non registra nulla.
  2. bark invia un'email a dog.email e gli incrementi dog.barkCount di 1.
  3. bark crea un nuovo record "bark" con bark.timestamp quando si verifica la corteccia. Incrementa anche dog.barkCount di 1.
  4. bark esegue un comando di sistema per estrarre l'ultima versione del codice del cane da Github. Invia quindi un messaggio di testo a dog.owner dicendo loro che il nuovo codice cane è in produzione.

14
È interessante notare che l'aggiunta di una generosità sembra aver attirato risposte peggiori rispetto a quelle originarie ;-) Quando si valutano le risposte, ricordare che: 1) Le specifiche per i verbi HTTP precludono qualsiasi scelta diversa da POST. 2) REST non ha nulla a che fare con la struttura dell'URL: è un elenco generico di contrapposizioni (apolidi, memorizzabile nella cache, interfaccia a più livelli, uniforme, ecc.) Che conferisce vantaggi (scalabilità, affidabilità, visibilità, ecc.). 3) La pratica corrente (come l'uso del POST nelle specifiche RPC) supera i definizionisti che stanno inventando le proprie regole API. 4) REST richiede un'interfaccia uniforme (seguendo le specifiche HTTP).
Raymond Hettinger,

@Kirk cosa ne pensi delle nuove risposte? C'è qualcosa che vuoi ancora sapere ma non è stato affrontato in nessuno di essi? Sarei più che felice di modificare nuovamente la mia risposta se può essere più utile.
Giordania,

@RaymondHettinger PATCHpuò essere appropriato. Spiego perché verso la fine della mia risposta .
Giordania,

PATCH sarebbe appropriato solo per incrementare dog.barkCount di uno. POST è il metodo per inviare e-mail, creare un nuovo record di corteccia, eseguire comandi da scaricare da Github o attivare un messaggio di testo. @Jordan, la tua lettura di PATCH RFC è fantasiosa ma in qualche modo in contrasto con il suo intento come una variante di PUT per la modifica parziale delle risorse. Non credo che tu stia aiutando il PO presentando letture non convenzionali delle specifiche HTTP piuttosto che riconoscere la pratica standard dell'uso del POST per le chiamate di procedura remote.
Raymond Hettinger,

@RaymondHettinger la cui pratica standardizza di fatto il POST? Tutte le interfacce RPC standard che ho visto identificano una risorsa per entità (non RESTful), rispetto all'URI, quindi una risposta valida che dia la priorità alla convenzione RPC dovrebbe essere comunque non convenzionale, che penso smentisca il valore dell'RPC convenzionale: uno è fantasioso o incoerente . Non puoi mai sbagliare con POST in quanto è il punto di riferimento per l'elaborazione dei dati, ma ci sono metodi più specifici. REST significa denominare le risorse e descrivere i cambiamenti nel loro stato, non nominare le procedure che cambiano lo stato. PATCH e POST descrivono entrambi i cambiamenti di stato.
Giordania,

Risposte:


280

Perché puntare a un design RESTful?

I principi RESTful portano le funzionalità che semplificano i siti Web (per un utente umano casuale di "navigare") nella progettazione dell'API dei servizi Web , quindi sono facili da usare per un programmatore. REST non è buono perché è REST, è buono perché è buono. Ed è buono soprattutto perché è semplice .

La semplicità del semplice HTTP (senza buste SOAP e POSTservizi sovraccarichi URI singoli ), ciò che alcuni potrebbero chiamare "mancanza di funzionalità" , è in realtà il suo punto di forza . HTTP, subito, ti chiede di avere indirizzabilità e apolidia : le due decisioni di progettazione di base che mantengono scalabile HTTP fino ai mega-siti (e mega-servizi) di oggi.

Ma REST non è il bulltet d'argento: a volte uno stile RPC ("Remote Procedure Call" - come SOAP) può essere appropriato , e talvolta altre esigenze hanno la precedenza sulle virtù del Web. Questo va bene. Ciò che non ci piace è la complessità inutile . Troppo spesso un programmatore o un'azienda porta servizi in stile RPC per un lavoro che un semplice vecchio HTTP potrebbe gestire bene. L'effetto è che HTTP è ridotto a un protocollo di trasporto per un enorme payload XML che spiega cosa sta "realmente" succedendo (non l'URI o il metodo HTTP ne danno un'idea). Il servizio risultante è troppo complesso, impossibile da eseguire il debug e non funzionerà a meno che i client non abbiano la configurazione esatta come previsto dallo sviluppatore.

Allo stesso modo un codice Java / C # può essere non orientata agli oggetti, semplicemente utilizzando il protocollo HTTP non fare un disegno RESTful. Si può essere presi dalla fretta di pensare ai propri servizi in termini di azioni e metodi remoti che dovrebbero essere chiamati. Non sorprende che ciò finisca per lo più in un servizio in stile RPC (o un ibrido REST-RPC). Il primo passo è pensare diversamente. Un progetto RESTful può essere realizzato in molti modi, un modo è quello di pensare alla tua applicazione in termini di risorse, non di azioni:

💡 Invece di pensare in termini di azioni che può eseguire ("fai una ricerca di luoghi sulla mappa") ...

... prova a pensare in termini di risultati di tali azioni ("l'elenco dei luoghi sulla mappa che corrisponde a un criterio di ricerca").

Vado per esempi di seguito. (Un altro aspetto chiave di REST è l'uso di HATEOAS: non lo spazzolo qui, ma ne parlo rapidamente in un altro post .)


Problemi del primo disegno

Diamo un'occhiata al design proposto:

ACTION http://api.animals.com/v1/dogs/1/

Prima di tutto, non dovremmo considerare la creazione di un nuovo verbo HTTP ( ACTION). In generale, questo è indesiderabile per diversi motivi:

  • (1) Dato solo l'URI del servizio, come farà un programmatore "casuale" a sapere che ACTIONesiste il verbo?
  • (2) se il programmatore sa che esiste, come farà a sapere la sua semantica? Cosa significa quel verbo?
  • (3) quali proprietà (sicurezza, idempotenza) ci si dovrebbe aspettare da quel verbo?
  • (4) cosa succede se il programmatore ha un client molto semplice che gestisce solo verbi HTTP standard?
  • (5) ...

Ora prendiamo in considerazione l'usoPOST (discuterò il perché di seguito, prendi la mia parola ora):

POST /v1/dogs/1/ HTTP/1.1
Host: api.animals.com

{"action":"bark"}

Questo potrebbe essere OK ... ma solo se :

  • {"action":"bark"}era un documento; e
  • /v1/dogs/1/era un URI "elaboratore di documenti" (simile alla fabbrica). Un "elaboratore di documenti" è un URI che devi semplicemente "gettare le cose" e "dimenticare" su di esse - il processore può reindirizzare a una risorsa appena creata dopo il "lancio". Ad esempio l'URI per la pubblicazione di messaggi in un servizio di broker di messaggi, che, dopo la pubblicazione, ti reindirizzerebbe a un URI che mostra lo stato dell'elaborazione del messaggio.

Non so molto del tuo sistema, ma scommetto che entrambi non sono veri:

  • {"action":"bark"} non è un documento , in realtà è il metodo con cui stai cercando di intrufolarti nel servizio; e
  • l' /v1/dogs/1/URI rappresenta una risorsa "cane" (probabilmente il cane con id==1) e non un elaboratore di documenti.

Quindi tutto ciò che sappiamo ora è che il design sopra non è così RESTful, ma che cos'è esattamente? Cosa c'è di male? Fondamentalmente, è un male perché è un URI complesso con significati complessi. Non puoi dedurne nulla. Come farebbe un programmatore a sapere che un cane può avere barkun'azione che può essere segretamente infusa da un POSTdentro?


Progettare le chiamate API della tua domanda

Quindi andiamo al sodo e proviamo a progettare QUESTE abbaia pensando in termini di risorse . Consentitemi di citare il libro Restful Web Services :

Una POSTrichiesta è un tentativo di creare una nuova risorsa da una esistente. La risorsa esistente può essere il genitore di quello nuovo in un senso della struttura dei dati, il modo in cui la radice di un albero è il genitore di tutti i suoi nodi foglia. Oppure la risorsa esistente può essere una risorsa "factory" speciale il cui unico scopo è generare altre risorse. La rappresentazione inviata insieme a una POSTrichiesta descrive lo stato iniziale della nuova risorsa. Come per PUT, una POSTrichiesta non deve necessariamente includere una rappresentazione.

Seguendo la descrizione sopra possiamo vedere che barkpuò essere modellato come una sottosorgente di undog (poiché un barkè contenuto in un cane, cioè una corteccia è "abbaiata" da un cane).

Da quel ragionamento abbiamo già ottenuto:

  • Il metodo è POST
  • La risorsa è /barks, subresource di dog:, che /v1/dogs/1/barksrappresenta una bark"fabbrica". Quell'URI è unico per ogni cane (dal momento che è sotto /v1/dogs/{id}).

Ora ogni caso del tuo elenco ha un comportamento specifico.

1. bark invia semplicemente un'e-mail a dog.emaile non registra nulla.

In primo luogo, abbaiare (inviare un'e-mail) è un'attività sincrona o asincrona? In secondo luogo, la barkrichiesta richiede qualche documento (l'e-mail, forse) o è vuota?


1.1 bark invia un'e-mail a dog.emaile non registra nulla (come attività sincrona)

Questo caso è semplice. Una chiamata alla barksrisorsa di fabbrica produce immediatamente una corteccia (una e-mail inviata) e la risposta (se OK o no) viene data immediatamente:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(entity-body is empty - or, if you require a **document**, place it here)

200 OK

Poiché registra (cambia) nulla, 200 OKè sufficiente. Mostra che tutto è andato come previsto.


1.2 bark invia un'e-mail a dog.emaile non registra nulla (come attività asincrona)

In questo caso, il client deve avere un modo per tenere traccia barkdell'attività. L' barkattività quindi dovrebbe essere una risorsa con il proprio URI .:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

{document body, if needed;
NOTE: when possible, the response SHOULD contain a short hypertext note with a hyperlink
to the newly created resource (bark) URI, the same returned in the Location header
(also notice that, for the 202 status code, the Location header meaning is not
standardized, thus the importance of a hipertext/hyperlink response)}

202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

In questo modo, ognuno barkè rintracciabile. Il client può quindi emettere una GETalla barkURI di sapere che è stato corrente. Forse anche usare a DELETEper annullarlo.


2. bark invia un'e-mail a dog.emaile quindi incrementa dog.barkCountdi 1

Questo può essere più complicato, se vuoi far sapere al cliente che la dogrisorsa viene cambiata:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

{document body, if needed; when possible, containing a hipertext/hyperlink with the address
in the Location header -- says the standard}

303 See Other
Location: http://api.animals.com/v1/dogs/1

In questo caso, l' locationintenzione dell'intestazione è far sapere al cliente che dovrebbe dare un'occhiata dog. Da HTTP RFC su303 :

Questo metodo esiste principalmente per consentire all'output di uno POSTscript attivato di reindirizzare l'agente utente su una risorsa selezionata.

Se l'attività è asincrona, barkè necessaria una risorsa secondaria proprio come la 1.2situazione e 303deve essere restituita al GET .../barks/Ytermine dell'attività.


3. La corteccia crea un nuovo " bark" record con la bark.timestampregistrazione quando si è verificata la corteccia. Aumenta anche dog.barkCountdi 1.

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(document body, if needed)

201 Created
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

Qui, barkviene creato a causa della richiesta, quindi lo stato 201 Createdviene applicato.

Se la creazione è asincrona, 202 Acceptedè invece necessario un ( come dice HTTP RFC ).

Il timestamp salvato è una parte della barkrisorsa e può essere recuperato con GETa. Il cane aggiornato può essere "documentato" anche in quello GET dogs/X/barks/Y.


4. bark esegue un comando di sistema per estrarre da Github l'ultima versione del codice del cane. Invia quindi un messaggio di testo per dog.ownerdire loro che il nuovo codice cane è in produzione.

La formulazione di questo è complicata, ma praticamente è un semplice compito asincrono:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(document body, if needed)

202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

Il client quindi invierebbe GETs per /v1/dogs/1/barks/a65h44conoscere lo stato corrente (se il codice è stato estratto, l'e-mail è stata inviata al proprietario e simili). Ogni volta che il cane cambia, a 303è applicabile.


Avvolgendo

Citando Roy Fielding :

L'unica cosa che REST richiede ai metodi è che siano definiti in modo uniforme per tutte le risorse (cioè, in modo che gli intermediari non debbano conoscere il tipo di risorsa per comprendere il significato della richiesta).

Negli esempi sopra, POSTè progettato in modo uniforme. Farà " bark" il cane . Ciò non è sicuro (il che significa che la corteccia ha effetti sulle risorse), né idempotente (ogni richiesta produce una nuova bark), che si adatta alPOST verbo.

Un programmatore dovrebbe sapere: a POSTper barkscedere a bark. I codici di stato della risposta (anche con corpo-entità e intestazioni quando necessario) svolgono il compito di spiegare cosa è cambiato e come il cliente può e deve procedere.

Nota: le fonti primarie utilizzate erano: il libro " Servizi Web riposanti ", il HTTP RFC e il blog di Roy Fielding .




Modificare:

La domanda e quindi la risposta sono cambiate un po 'da quando sono state create per la prima volta. La domanda originale è stata posta sulla progettazione di un URI come:

ACTION http://api.animals.com/v1/dogs/1/?action=bark

Di seguito è la spiegazione del perché non è una buona scelta:

Come i client dicono al server COSA FARE con i dati sono le informazioni sul metodo .

  • I servizi Web RESTful trasmettono informazioni sul metodo nel metodo HTTP.
  • I tipici servizi in stile RPC e SOAP mantengono il loro nel corpo dell'entità e nell'intestazione HTTP.

QUALE PARTE dei dati [il client vuole che il server] operi è l' informazione di scoping .

  • I servizi RESTful utilizzano l'URI. I servizi in stile SOAP / RPC utilizzano ancora una volta le intestazioni body-body e HTTP.

Ad esempio, prendi l'URI di Google http://www.google.com/search?q=DOG. Lì, le informazioni sul metodo sono GETe le informazioni sull'ambito /search?q=DOG.

Per farla breve:

  • Nelle architetture RESTful , le informazioni sul metodo vanno nel metodo HTTP.
  • Nelle architetture orientate alle risorse , le informazioni di ambito vanno nell'URI.

E la regola empirica:

Se il metodo HTTP non corrisponde alle informazioni sul metodo, il servizio non è RESTful. Se le informazioni sull'ambito non si trovano nell'URI, il servizio non è orientato alle risorse.

Puoi inserire "corteccia" "azione" nell'URL (o nel corpo-entità) e utilizzarlo POST. Nessun problema, funziona e potrebbe essere il modo più semplice per farlo, ma questo non è RESTful .

Per mantenere il tuo servizio davvero RESTful, potresti dover fare un passo indietro e pensare a cosa vuoi veramente fare qui (quali effetti avrà sulle risorse).

Non posso parlare delle tue specifiche esigenze aziendali, ma lascia che ti dia un esempio: considera un servizio di ordinazione RESTful in cui gli ordini sono presso URI come example.com/order/123 .

Ora dì che vogliamo annullare un ordine, come lo faremo? Si può essere tentati di pensare che si tratti di un "annullamento" "azione" e progettarlo come POST example.com/order/123?do=cancel.

Non è RESTful, come abbiamo detto sopra. Invece, potremmo PUTuna nuova rappresentazione del ordercon un canceledelemento inviato a true:

PUT /order/123 HTTP/1.1
Content-Type: application/xml

<order id="123">
    <customer id="89987">...</customer>
    <canceled>true</canceled>
    ...
</order>

E questo è tutto. Se l'ordine non può essere annullato, è possibile restituire un codice di stato specifico. (Un design di risorse secondarie, come POST /order/123/canceledcon il corpo-entità, truepuò anche essere disponibile per semplicità.)

Nel tuo scenario specifico, potresti provare qualcosa di simile. In questo modo, mentre un cane abbaia, ad esempio, un GETat /v1/dogs/1/potrebbe includere tali informazioni (ad es<barking>true</barking> . ) . Oppure ... se è troppo complicato, allenta il tuo requisito RESTful e mantieni POST.

Aggiornare:

Non voglio rendere la risposta troppo grande, ma ci vuole un po 'di tempo prima di esporre un algoritmo ( un'azione ) come un insieme di risorse. Invece di pensare in termini di azioni ( "fai una ricerca di luoghi sulla mappa" ), devi pensare in termini di risultati di quell'azione ( "l'elenco dei luoghi sulla mappa che corrisponde a un criterio di ricerca" ).

Potresti ritrovarti a tornare a questo passaggio se scopri che il tuo design non si adatta all'interfaccia uniforme di HTTP.

Le variabili di query sono informazioni di ambito , ma non indicano nuove risorse ( /post?lang=enè chiaramente la stessa risorsa di /post?lang=jp, solo una rappresentazione diversa). Piuttosto, sono usati per comunicare lo stato del client (come ?page=10, in modo che lo stato non sia mantenuto nel server; qui ?lang=enè anche un esempio) o inserire parametri nelle risorse algoritmiche ( /search?q=dogs, /dogs?code=1). Ancora una volta, non risorse distinte.

Proprietà (metodi) dei verbi HTTP:

Un altro punto chiaro che mostra ?action=somethingnell'URI non è RESTful, sono le proprietà dei verbi HTTP:

  • GETe HEADsono sicuri (e idempotenti);
  • PUTe DELETEsono solo idempotenti;
  • POST non è nessuno dei due.

Sicurezza : una GETo una HEADrichiesta è una richiesta di lettura di alcuni dati, non una richiesta di modifica di qualsiasi stato del server. Il cliente può fare GETo HEADrichiedere 10 volte ed è lo stesso che farlo una volta, o mai farlo .

Idempotenza : un'operazione idempotente in una che ha lo stesso effetto se la applichi una o più volte (in matematica, moltiplicare per zero è idempotente). Se DELETEuna risorsa è una volta, l'eliminazione di nuovo avrà lo stesso effetto (la risorsa è GONEgià).

POSTnon è né sicuro né idempotente. Effettuare due POSTrichieste identiche a una risorsa "factory" comporterà probabilmente due risorse subordinate contenenti le stesse informazioni. Con sovraccarico (metodo in URI o corpo-entità) POST, tutte le scommesse sono disattivate.

Entrambe queste proprietà sono state importanti per il successo del protocollo HTTP (su reti inaffidabili!): Quante volte hai aggiornato (GET ) la pagina senza aspettare fino a quando non è completamente caricata?

La creazione di un'azione e il suo posizionamento nell'URL infrangono chiaramente il contratto dei metodi HTTP. Ancora una volta, la tecnologia ti consente, puoi farlo, ma non è un design RESTful.


Sono contento dell'idea che chiamare un'azione su un server, designata come azione nell'URL, non sia RESTful. POSTè stato progettato per "fornire un blocco di dati ... a un processo di gestione dei dati" . Sembra che molte persone distinguano le risorse dalle azioni, ma in realtà le azioni sono solo un tipo di risorsa.
Jacob Stevens,

1
@JacobStevens L'OP ha cambiato un po 'la domanda, quindi devo aggiornare la mia risposta per renderla più diretta (controlla la domanda originale , forse vedrai cosa intendo). Sono d'accordo con POST"fornire un blocco di dati ... a un processo di gestione dei dati", ma la differenza è che un blocco di dati , non un blocco di dati e la procedura (azione, metodo, comando) devono essere eseguito quindi. Questo è POSTsovraccarico, e il POSTsovraccarico è il design in stile RPC, non RESTful.
acdcjunior,

Presumo che la logica di azione / metodo sarebbe ospitata sul server, altrimenti quale sarebbe lo scopo della chiamata? Nel caso in cui descrivi, sono d'accordo, non sarebbe un buon design. Ma il metodo o la subroutine che esegue l'azione sarebbe specificato dall'URI (che è un altro motivo per cui una risorsa di azione designata come verbo alla fine di un URL è utile e RESTful, anche se molti sconsigliano).
Jacob Stevens,

6
La risposta ci ha aggiornato. È un po 'lungo perché sembrava necessaria una spiegazione approfondita ("Tieni presente che ho una comprensione rudimentale di REST."). È stata una specie di lotta per renderlo il più chiaro possibile. Spero che sia utile in qualche modo.
acdcjunior

2
Grande spiegazione, ho votato ma l'intestazione Location non deve essere utilizzata nella risposta 202 accettata. Sembra essere un'interpretazione sbagliata che molte persone fanno da RFC. Controlla questo stackoverflow.com/questions/26199228/…
Delmo

6

Ho risposto prima , ma questa risposta contraddice la mia vecchia risposta e segue una strategia molto diversa per arrivare a una soluzione. Mostra come la richiesta HTTP è costruita dai concetti che definiscono REST e HTTP. Utilizza anche PATCHinvece di POSTo PUT.

Passa attraverso i vincoli REST, quindi i componenti di HTTP, quindi una possibile soluzione.

RIPOSO

REST è un insieme di vincoli destinati ad essere applicati a un sistema ipermediale distribuito al fine di renderlo scalabile. Anche per dargli un senso nel contesto del controllo remoto di un'azione, devi pensare al controllo remoto di un'azione come parte di un sistema ipermediale distribuito - una parte di un sistema per scoprire, visualizzare e modificare informazioni interconnesse. Se questo è più un problema di quanto non valga la pena, allora probabilmente non va bene provare a renderlo RESTful. Se si desidera semplicemente una GUI di tipo "pannello di controllo" sul client in grado di attivare azioni sul server tramite la porta 80, probabilmente si desidera una semplice interfaccia RPC come JSON-RPC tramite richieste / risposte HTTP o WebSocket.

Ma REST è un modo affascinante di pensare e l'esempio nella domanda sembra essere facile da modellare con un'interfaccia RESTful, quindi accettiamo la sfida per divertimento e per l'educazione.

REST è definito da quattro vincoli di interfaccia:

identificazione delle risorse; manipolazione delle risorse attraverso rappresentazioni; messaggi auto-descrittivi; e, hypermedia come motore dello stato dell'applicazione.

Ti chiedi come puoi definire un'interfaccia, soddisfacendo questi vincoli, attraverso la quale un computer dice a un altro computer di abbaiare un cane. In particolare, vuoi che la tua interfaccia sia HTTP e non vuoi scartare le funzionalità che rendono HTTP RESTful quando usato come previsto.

Cominciamo con il primo vincolo: l'identificazione delle risorse .

Qualsiasi informazione che può essere nominata può essere una risorsa: un documento o un'immagine, un servizio temporale (ad esempio "il tempo di oggi a Los Angeles"), una raccolta di altre risorse, un oggetto non virtuale (ad esempio una persona) e così via .

Quindi un cane è una risorsa. Deve essere identificato.

Più precisamente, una risorsa R è una funzione di appartenenza temporalmente variabile M R ( t ), che per il tempo t è mappata a un insieme di entità o valori equivalenti. I valori nel set possono essere rappresentazioni di risorse e / o identificatori di risorse .

È modellare un cane prendendo un insieme di identificatori e rappresentazioni e dicendo che sono tutti associati con l'altro in un dato momento. Per ora, usiamo l'identificatore "dog # 1". Questo ci porta al secondo e al terzo vincolo: rappresentazione delle risorse e autodescrizione .

I componenti REST eseguono azioni su una risorsa utilizzando una rappresentazione per acquisire lo stato corrente o previsto di quella risorsa e trasferendo tale rappresentazione tra i componenti. Una rappresentazione è una sequenza di byte, oltre a metadati di rappresentazione per descrivere tali byte.

Segue una sequenza di byte che catturano lo stato previsto del cane, ovvero la rappresentazione che desideriamo associare all'identificatore "cane n. 1" (si noti che rappresenta solo una parte dello stato in quanto non riguarda il nome del cane, la salute , o anche abbaiare passato):

Abbaia ogni 10 minuti dal momento in cui è stato effettuato questo cambio di stato e continuerà indefinitamente.

Dovrebbe essere collegato ai metadati che lo descrivono. Questi metadati potrebbero essere utili:

È una dichiarazione inglese. Descrive parte dello stato previsto. Se viene ricevuto più volte, consenti solo al primo di avere un effetto.

Infine, diamo un'occhiata al quarto vincolo: HATEOAS .

REST ... vede un'applicazione come una struttura coerente di informazioni e controlla le alternative attraverso le quali un utente può eseguire l'attività desiderata. Ad esempio, cercare una parola in un dizionario online è un'applicazione, così come visitare un museo virtuale o rivedere una serie di appunti di lezione per studiare per un esame. ... Il successivo stato di controllo di un'applicazione risiede nella rappresentazione della prima risorsa richiesta, quindi ottenere quella prima rappresentazione è una priorità. ... L'applicazione del modello è quindi un motore che si sposta da uno stato all'altro esaminando e scegliendo tra le transizioni di stato alternative nell'insieme corrente di rappresentazioni.

In un'interfaccia RESTful, il client riceve una rappresentazione delle risorse per capire come dovrebbe ricevere o inviare una rappresentazione. Ci deve essere una rappresentazione da qualche parte nell'applicazione da cui il cliente può capire come ricevere o inviare tutte le rappresentazioni che dovrebbe essere in grado di ricevere o inviare, anche se segue una catena di rappresentazioni per arrivare a tali informazioni. Questo sembra abbastanza semplice:

Il cliente richiede una rappresentazione di una risorsa identificata come homepage; in risposta, ottiene una rappresentazione che contiene un identificatore di ogni cane che il cliente potrebbe desiderare. Il cliente estrae un identificatore da esso e chiede al servizio come può interagire con il cane identificato, e il servizio dice che il cliente può inviare una dichiarazione inglese che descrive parte dello stato previsto del cane. Quindi il client invia tale dichiarazione e riceve un messaggio di successo o un messaggio di errore.

HTTP

HTTP implementa i vincoli REST come segue:

identificazione delle risorse : URI

rappresentazione delle risorse : entità-corpo

auto-descrizione : metodo o codice di stato, intestazioni e possibilmente parti del corpo-entità (ad es. l'URI di uno schema XML)

hateoas : collegamenti ipertestuali

Hai deciso tu http://api.animals.com/v1/dogs/1 come URI. Supponiamo che il client abbia ottenuto questo da qualche pagina del sito.

Usiamo questo corpo-entità (il valore di nextè un timestamp; un valore di 0mezzi 'quando viene ricevuta questa richiesta'):

{"barks": {"next": 0, "frequency": 10}}

Ora abbiamo bisogno di un metodo. PATCH si adatta alla descrizione "parte dello stato previsto" che abbiamo deciso di:

Il metodo PATCH richiede l'applicazione di una serie di modifiche descritte nell'entità richiesta alla risorsa identificata dall'URI di richiesta.

E alcune intestazioni:

Per indicare la lingua del corpo-entità: Content-Type: application/json

Per essere sicuro che accada una sola volta: If-Unmodified-Since: <date/time this was first sent>

E abbiamo una richiesta:

PATCH /v1/dogs/1/ HTTP/1.1
Host: api.animals.com
Content-Type: application/json
If-Unmodified-Since: <date/time this was first sent>
[other headers]

{"barks": {"next": 0, "frequency": 10}}

In caso di successo, il client dovrebbe ricevere un 204codice di stato in risposta o un 205se la rappresentazione di /v1/dogs/1/è cambiata per riflettere il nuovo programma di abbaiare.

In caso di fallimento, dovrebbe ricevere a 403 e un utile messaggio perché.

Non è essenziale per REST che il servizio rifletta il programma della corteccia in una rappresentazione in risposta a GET /v1/dogs/1/, ma avrebbe più senso se una rappresentazione JSON includesse questo:

"barks": {
    "previous": [x_1, x_2, ..., x_n],
    "next": x_n,
    "frequency": 10
}

Tratta il cron job come un dettaglio di implementazione che il server nasconde dall'interfaccia. Questa è la bellezza di un'interfaccia generica. Il client non deve sapere cosa fa il server dietro le quinte; tutto ciò che interessa è che il servizio comprenda e risponda ai cambiamenti di stato richiesti.


3

Molte persone usano POST per questo scopo. È appropriato per eseguire "qualsiasi operazione non sicura o non sicura quando nessun altro metodo HTTP sembra appropriato".

API come XMLRPC utilizzano POST per attivare azioni che possono eseguire codice arbitrario. L '"azione" è inclusa nei dati POST:

POST /RPC2 HTTP/1.0
User-Agent: Frontier/5.1.2 (WinNT)
Host: betty.userland.com
Content-Type: text/xml
Content-length: 181

<?xml version="1.0"?>
<methodCall>
   <methodName>examples.getStateName</methodName>
   <params>
      <param>
         <value><i4>41</i4></value>
         </param>
      </params>
   </methodCall>

Viene fornito l'esempio RPC per mostrare che POST è la scelta convenzionale dei verbi HTTP per i metodi lato server. Ecco i pensieri di Roy Fielding su POST : praticamente dice che è RESTful usare i metodi HTTP come specificato.

Si noti che RPC stesso non è molto RESTful perché non è orientato alle risorse. Ma se hai bisogno di apolidia, memorizzazione nella cache o stratificazione, non è difficile apportare le trasformazioni appropriate. Vedi http://blog.perfectapi.com/2012/opinionated-rpc-apis-vs-restful-apis/ per un esempio.


Penso che potresti codificare URL i parametri non inserirli nella stringa di query
tacos_tacos_tacos

@Kirk Sì, ma con una piccola modifica, elimina l'ultima barra in avanti: POST api.animals.com/v1/dogs1?action=bark
Raymond Hettinger,

se segui i consigli in questa risposta, tieni presente che l'API risultante non sarà RESTful.
Nicholas Shanks,

2
Questo non è RESTful perché HTTP stabilisce l'URL come identificatore di una risorsa e un URL di /RPC2non fa nulla per identificare una risorsa: identifica una tecnologia server. Invece, questo methodNamecerca di "identificare" la "risorsa", ma anche in questo caso non beneficia della distinzione nome / verbo; l'unica cosa simile a un verbo qui è methodCall. Questo è come 'fare il recupero del nome dello stato' invece di 'recuperare il nome dello stato' - quest'ultimo ha molto più senso.
Giordania,

+1 per i collegamenti; molto istruttivo e l'esperimento "presunto RPC" è inventivo.
Giordania,

2

POSTè il metodo HTTP progettato per

Fornire un blocco di dati ... a un processo di gestione dei dati

I metodi lato server che gestiscono azioni non associate a CRUD sono ciò che Roy Fielding intendeva con REST, quindi sei bravo lì, ed è per questo che è POSTstato reso non idempotente. POSTgestirà la maggior parte della pubblicazione di dati su metodi lato server per elaborare le informazioni.

Detto questo, nel tuo scenario di abbaio di cani, se vuoi che una corteccia sul lato server venga eseguita ogni 10 minuti, ma per qualche motivo è necessario che il trigger abbia origine da un client, PUTservirebbe meglio lo scopo, a causa della sua idempotenza. Bene, rigorosamente in questo scenario non c'è il rischio apparente di più richieste POST che causano invece il miagolio del tuo cane, ma comunque è lo scopo dei due metodi simili. La mia risposta a una domanda SO simile può essere utile per te.


1
PUT vs. POST riguarda l'URL. Il terzo paragrafo dopo 9.6 PUT afferma che lo scopo dei due metodi è quindi che l' PUTURL si riferisce a ciò che dovrebbe essere sostituito dal contenuto del client e l' POSTURL si riferisce a ciò che dovrebbe elaborare il contenuto del client come desidera.
Giordania,

1

Se supponiamo che l'abbaiare sia una risorsa interna / dipendente / secondaria su cui il consumatore può agire, allora potremmo dire:

POST http://api.animals.com/v1/dogs/1/bark

il cane numero 1 abbaia

GET http://api.animals.com/v1/dogs/1/bark

restituisce l'ultimo timestamp di corteccia

DELETE http://api.animals.com/v1/dogs/1/bark

non si applica! quindi ignoralo.


Questo è RESTful solo se consideri /v1/dogs/1/barkdi per sé una risorsa e POSTuna descrizione di come lo stato interno di quella risorsa dovrebbe cambiare. Trovo che abbia più senso considerare solo /v1/dogs/1/come una risorsa e indicare nel corpo-entità che dovrebbe abbaiare.
Giordania,

mmm .. beh, è ​​una risorsa che puoi cambiare il suo stato. Perché il risultato di cambiare il suo stato sta facendo rumore, non lo rende meno risorse! Stai guardando Bark come un verbo (che è) per questo non puoi considerarlo una risorsa. Lo sto guardando come una risorsa dipendente, il cui stato può essere modificato e poiché il suo stato è booleano, non vedo alcun motivo per menzionarlo nel corpo-entità. Questa è solo la mia opinione.
bolbol

1

Revisioni precedenti di alcune risposte suggerivano di usare RPC. Non è necessario guardare a RPC in quanto è perfettamente possibile fare ciò che si desidera aderendo ai vincoli REST.

Innanzitutto, non inserire i parametri di azione nell'URL. L'URL definisce a cosa stai applicando l'azione e i parametri della query fanno parte dell'URL. Dovrebbe essere pensato interamente come un sostantivo. http://api.animals.com/v1/dogs/1/?action=barkè una risorsa diversa - un nome diverso - a http://api.animals.com/v1/dogs/1/. [nb Asker ha rimosso l' ?action=barkURI dalla domanda.] Ad esempio, confrontahttp://api.animals.com/v1/dogs/?id=1 conhttp://api.animals.com/v1/dogs/?id=2 . Risorse diverse, distinte solo per stringa di query. Pertanto, l'azione della richiesta, a meno che non corrisponda direttamente a un tipo di metodo esistente senza corpo (TRACE, OPTIONS, HEAD, GET, DELETE, ecc.) Deve essere definita nel corpo della richiesta.

Quindi, decidere se l'azione è " idempotente ", il che significa che può essere ripetuta senza effetti negativi (vedere il paragrafo successivo per ulteriori spiegazioni). Ad esempio, l'impostazione di un valore su true può essere ripetuta se il client non è sicuro che si sia verificato l'effetto desiderato. Inviano nuovamente la richiesta e il valore rimane vero. L'aggiunta di 1 a un numero non è idempotente. Se il client invia il comando Add1, non è sicuro che abbia funzionato e lo rispedisca, il server ne ha aggiunto uno o due? Una volta determinato questo, sei in una posizione migliore tra cui sceglierePUT e POSTper il tuo metodo.

Idempotente significa che una richiesta può essere ripetuta senza modificare il risultato. Questi effetti non includono la registrazione e altre attività amministrative del server. Utilizzando il tuo primo e secondo esempio, l'invio di due e-mail alla stessa persona si traduce in uno stato diverso rispetto all'invio di una e-mail (il destinatario ne ha due nella loro casella di posta, che potrebbero considerare spam), quindi utilizzerei sicuramente POST per quello . Se barkCount nell'esempio 2 è destinato a essere visto da un utente della tua API o influisce su qualcosa che è visibile al client, allora è anche qualcosa che renderebbe la richiesta non idempotente. Se deve essere visualizzato solo da te, viene considerato come registrazione del server e deve essere ignorato quando si determina l'idempotenza.

Infine, determinare se si prevede che l'azione che si desidera eseguire abbia successo immediatamente o meno. BarkDog è un'azione che si completa rapidamente. RunMarathon non lo è. Se la tua azione è lenta, considera di restituire un 202 Accepted, con un URL nel corpo della risposta affinché un utente esegua il polling per vedere se l'azione è completa. In alternativa, chiedi agli utenti di postare su un URL elenco come /marathons-in-progress/e quindi, quando l'azione è stata eseguita, reindirizzali dall'URL ID in corso a/marathons-complete/ all'URL.
Per i casi specifici n. 1 e n. 2, il server dovrebbe ospitare una coda e il client invierà batch di indirizzi. L'azione non sarebbe SendEmails, ma qualcosa come AddToDispatchQueue. Il server può quindi eseguire il polling della coda per vedere se ci sono indirizzi e-mail in attesa e inviare e-mail se ne trova. Quindi aggiorna la coda per indicare che l'azione in sospeso è stata ora eseguita. Avresti un altro URI che mostra al client lo stato corrente della coda. Per evitare il doppio invio di e-mail, il server potrebbe anche tenere un registro di chi ha inviato questa e-mail e controllare ogni indirizzo per assicurarsi che non ne invii mai due allo stesso indirizzo, anche se POST lo stesso elenco due volte a la fila.

Quando scegli un URI per qualsiasi cosa, prova a pensarlo come un risultato, non un'azione. Ad esempio google.com/search?q=dogsmostra i risultati di una ricerca per la parola "cani". Non esegue necessariamente la ricerca.

Anche i casi n. 3 e n. 4 dell'elenco non sono azioni idempotenti. Suggerisci che i diversi effetti suggeriti potrebbero influire sulla progettazione dell'API. In tutti e quattro i casi utilizzerei la stessa API, poiché tutti e quattro cambiano lo "stato mondiale".


Diciamo che l'azione è sfornare una gigantesca coda di email e inviare un messaggio a un gruppo di persone. È idempotente? Sono azioni idempotenti per PUT o POST?
Kirk Ouimet,

@kirk Ho ampliato la mia risposta.
Nicholas Shanks,

0

Vedi la mia nuova risposta - contraddice questa e spiega REST e HTTP in modo più chiaro e preciso.

Ecco una raccomandazione che sembra essere RESTful ma non è certamente l'unica opzione. Per iniziare ad abbaiare quando il servizio riceve la richiesta:

POST /v1/dogs/1/bark-schedule HTTP/1.1
...
{"token": 12345, "next": 0, "frequency": 10}

token è un numero arbitrario che impedisce abbaia ridondante, indipendentemente da quante volte questa richiesta viene inviata.

nextindica l'ora della corteccia successiva; un valore di 0significa "APPENA POSSIBILE".

Ogni volta che GET /v1/dogs/1/bark-schedule, si dovrebbe ottenere qualcosa come questo, dove t è il tempo dell'ultimo corteccia e u è t + 10 minuti:

{"last": t, "next": u}

Consiglio vivamente di utilizzare lo stesso URL per richiedere una corteccia che usi per scoprire lo stato di abbaio attuale del cane. Non è essenziale per REST, ma sottolinea l'atto di modificare il programma.

Il codice di stato appropriato è probabilmente 205 . Sto immaginando un cliente che guarda il programma attuale,POST s allo stesso URL per cambiarla, e viene incaricato dal servizio di dare una seconda occhiata alla pianificazione per dimostrare che è stata cambiata.

Spiegazione

RIPOSO

Dimentica HTTP per un momento. È essenziale capire che una risorsa è una funzione che richiede tempo come input e restituisce un set contenente identificatori e rappresentazioni . Semplifichiamo a: una risorsa è un insieme R di identificatori e rappresentazioni; R può cambiare: i membri possono essere aggiunti, rimossi o modificati. (Sebbene sia un progetto errato e instabile rimuovere o modificare gli identificatori.) Diciamo che un identificatore che è un elemento di R identifica R e che una rappresentazione che è un elemento di R rappresenta R .

Diciamo che R è un cane. Ti capita di identificare R come /v1/dogs/1. (Significato /v1/dogs/1è membro della R .) Questo è solo uno dei molti modi si potrebbe identificare R . È inoltre possibile identificare R come /v1/dogs/1/x-rayse come/v1/rufus .

Come si rappresenta R ? Magari con una fotografia. Forse con una serie di raggi X. O forse con un'indicazione della data e dell'ora in cui R ha abbaiato l'ultima volta. Ma ricorda che queste sono tutte rappresentazioni della stessa risorsa . /v1/dogs/1/x-raysè un identificatore della stessa risorsa che è rappresentato da una risposta alla domanda "quando ha fatto R abbaiato ultima volta?"

HTTP

Le rappresentazioni multiple di una risorsa non sono molto utili se non puoi fare riferimento a quella che desideri. Ecco perché HTTP è utile: ti consente di connettere identificatori a rappresentazioni . Cioè, è un modo per il servizio di ricevere un URL e decidere quale rappresentazione servire al client.

Almeno, ecco cosa GETfa. PUTè sostanzialmente l'inverso di GET: sei PUTuna rappresentazione r nell'URL se desideri che future GETrichieste a quell'URL restituiscano r , con alcune possibili traduzioni come JSON in HTML.

POSTè un modo più libero di modificare una rappresentazione. Pensa che ci sono logiche di visualizzazione e logiche di modifica che sono controparti tra loro, entrambe corrispondenti allo stesso URL. Una richiesta POST è una richiesta per la logica di modifica di elaborare le informazioni e modificare qualsiasi rappresentazione (non solo la rappresentazione situata dallo stesso URL) come ritiene opportuno il servizio. Presta attenzione al terzo paragrafo dopo 9.6 PUT : non stai sostituendo la cosa all'URL con nuovi contenuti; stai chiedendo all'URL di elaborare alcune informazioni e rispondere in modo intelligente sotto forma di rappresentazioni informative.

Nel nostro caso, chiediamo alla logica di modifica in /v1/dogs/1/bark-schedule(che è la controparte della logica di visualizzazione che ci dice quando ha abbaiato l'ultima volta e quando abbaia successivamente) per elaborare le nostre informazioni e modificare alcune rappresentazioni di conseguenza. In risposta ai futuri GETs, la logica di visualizzazione corrispondente allo stesso URL ci dirà che il cane ora abbaia come vorremmo.

Pensa al cron job come a un dettaglio dell'implementazione. HTTP si occupa di visualizzare e modificare rappresentazioni. D'ora in poi, il servizio dirà al cliente quando il cane ha abbaiato l'ultima volta e quando abbaia dopo. Dal punto di vista del servizio, questo è onesto perché quei tempi corrispondono a cron job passati e pianificati.


-1

REST è uno standard orientato alle risorse, non è guidato dall'azione come sarebbe un RPC.

Se vuoi che il tuo server abbaia , dovresti esaminare diverse idee come JSON-RPC o la comunicazione dei websocket.

Ogni cercare di tenerlo RESTful fallirà, a mio parere: è possibile emettere una POSTcon il actionparametro, non si sta creando nuove risorse, ma come si può avere effetti collaterali, si sono più sicuri.


POSTè stato progettato per "fornire un blocco di dati ... a un processo di gestione dei dati" . Sembra che molte persone distinguano le risorse dalle azioni, ma in realtà le azioni sono solo un tipo di risorsa. La chiamata di una risorsa di azione su un server è ancora un'interfaccia uniforme, memorizzabile nella cache, modulare e scalabile. È anche apolide, ma può essere violato se il client è progettato per aspettarsi una risposta. Ma chiamare un "metodo vuoto" sul server è ciò che Roy Fielding intendeva con REST .
Jacob Stevens,

Come descrivo in mia risposta , in REST puoi implicitamente fare in modo che il server esegua un'azione chiedendogli di dire, d'ora in poi, "la tua azione è stata completata", mentre RPC si basa sull'idea di chiedere semplicemente al server di eseguire l'azione. Entrambi hanno perfettamente senso, così come la programmazione imperativa e dichiarativa hanno entrambi un senso.
Giordania,
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.