Swappa : Uni / Panoramica REST
Creative Commons License

Torna alla pagina di Ingegneria dei Processi Aziendali


  :: Panoramica sul REST :: 

1. Cos'è il REST?

REST è un termine introdotto da Roy Fielding per descrivere uno stile architetturale per applicazioni web, ed è l'acronimo di Representational State Transfer. Per capire cosa significa facciamo un passo indietro.
Il web è formato da risorse, intese come oggetti di interesse. Ad esempio la RAI potrebbe definire come risorsa il programma Blob, ed i client potrebbero accedere alla risorsa attraverso l'URL:
http://www.rai.it/programma/blob
Ciò che viene restituito al client è una rappresentazione della risorsa (ad esempio una pagina html, se è un browser), che lo porrà in un certo stato. Utilizzando i collegamenti ipertestuali inclusi nella rappresentazione ricevuta, il client potrà accedere ad altre risorse (altre rappresentazioni) passando in un nuovo stato. Riassumendo: l'applicazione del client cambia (trasferisce) lo stato ad ogni rappresentazione della risorsa, da cui Representational State Transfer.
Prima di andare avanti, ecco il disclaimer fondamentale: la pagina web NON È una risorsa, ma una sua rappresentazione.


Il REST è un'alternativa leggera ai meccanismi come le RPC e i servizi Web che usano SOAP, WSDL, ecc. Questo non significa che non sia potente, e infatti non c'è nulla che si possa fare in SOAP che non sia possibile realizzare con un'architettura REST.

Va detto infine che il REST non è uno standard, ma ne usa molti: HTTP, URI, formati standard dei file (xml, html, jpg, ...), tipi MIME (text/xml, text/html, image/jpg, ...), ...

2. Principi architetturali

Il REST definisce un insieme di principi architetturali per realizzare i servizi web, che spesso sono dei veri e propri vincoli di progettazione:

Non sono invece vincoli di progettazione la piattaforma su cui vengono eseguiti i servizi (non importa se il server è Unix e il client è Mac, o viceversa), né il tipo di linguaggio usato per implementarli.

Nei prossimi sottoparagrafi approfondiremo alcuni dei principi architetturali citati.

2.1 Design delle URL

Linee guida

Ecco alcune linee guida da seguire nella fase di progettazione delle URI:

URL logiche e URL fisiche

Una risorsa è un'entità concettuale, e una rappresentazione è la concreta manifestazione della risorsa sull'applicazione del client. Abbiamo detto che ogni risorsa ha un URL associata, ma va chiarito che questa non è una URL fisica, bensì logica.
Per capire meglio consideriamo questa URL di esempio, che restituisce la rappresentazione della risorsa "3230° puntata di Blob":
http://www.rai.it/programma/blob/3230
L'identificativo numerico non deve farci credere che esistono (almeno) 3230 file, uno per ogni rappresentazione della risorsa "puntata di Blob": bastano infatti 2-3 file di script per generare automaticamente il contenuto di tutte le rappresentazioni. Dato però che all'utente va nascosta la tecnologia implementativa dietro ogni URL, gli sarà fornito un indirizzo alla risorsa logica e non a quella fisica che la genera.
Ecco dunque perché le URL di un servizio REST sono chiamate URL logiche, perché non implicano l'esistenza di un file fisico associato.

Breve nota sulle istanze delle risorse: hanno vita limitata rispetto 'identificativo numerico: si tratta di una pratica comune per identificare le istanze delle risorse. Queste

2.2 Interfaccia comune

Avere un'interfaccia comune tra client e server semplifica e disaccoppia l'architettura, facendo sì che le due componenti possano evolvere in modo indipendente. L'interfaccia standard per i servizi web RESTful è il protocollo HTTP, non a caso quello di trasferimento di un ipertesto. Le operazioni supportate sono le cosiddette funzioni CRUD:

Per essere più completi, ecco una tabella che mostra gli effetti delle richieste HTTP su due diversi tipi di URI: quelle che identificano gli insiemi di risorse e quelle che le identificano singolarmente.

Risorsa POST GET PUT DELETE
URI insiemi di risorse
www.rai.it/programmi
Crea un nuovo membro dell'insieme (la sua URI è assegnata automaticamente, e di solito viene ritornata al client) Mostra l'elenco delle URI e qualche dettaglio aggiuntivo sui membri dell'insieme Sostituisce l'intero insieme con un altro Cancella l'intero insieme
URI singole risorse
www.rai.it/programmi/blob
Tratta la risorsa indicata come se fosse un insieme e crea una nuova sotto-risorsa al suo interno Restituisce una rappresentazione della risorsa indicata, espressa nel tipo MIME più appropriato Sostituisce la risorsa indicata, e se non esiste la crea Cancella la risorsa indicata dal suo insieme

Codici di risposta

Un altro aspetto importante di una chiamata REST è quella di ritornare il codice di risposta HTTP appropriato. Vediamo i principali:

2.3 Flusso tra ipertesti

Quando si progetta la modalità di navigazione tra ipertesti per i trasferimenti di stato, la regola d'oro è: MAI lasciare al client il compito di costruire la URL per effettuare azioni sulla risorsa, ma INCLUDERLE nella risposta REST sotto forma di elenco di link.

Ad esempio se il client vuole ottenere l'elenco dei programmi della RAI e successivamente accedere alla rappresentazione di uno di questi:

2.4 Stateless o stateful?

Abbiamo detto che le comunicazioni tra client e server devono essere stateless, ma questo non vale per i servizi web o le loro risorse. Come il significato stesso del nome "REST" ci ricorda, tutto ruota intorno al problema di concordare uno stato in un sistema distribuito!
Dal punto di vista dei client lo stato è catturato dalla posizione corrente nell'ipertesto, cioè l'interfaccia attraverso cui possono interagire con le risorse. Il server può influenzare le transizioni di stato del client inviando diverse rappresentazioni nelle risposte alle GET, o inviando diversi collegamenti ipertestuali da seguire.
Dal punto di vista dei server è lo stato della risorsa che cattura lo stato attuale del servizio. In questa prospettiva il client non è solo colui che ha accesso alla risorsa (nelle sue diverse rappresentazioni), ma anche colui che ne può manipolare lo stato usando l'interfaccia CRUD.

Nello schema sotto illustreremo meglio quanto detto finora:

STATO DEL CLIENT ###                      ### STATO DELLA RISORSA
                 ###                      ###
                  |                        |
       ...        | GET /risorsa           |   ------------------
       ...        |----------------------->|   |/risorsa        |
                  |<-----------------------|   |----------------|
        |         | <a href="1" />         |   |<a href="1" />  |
        |         | <a href="2" />         |   |<a href="1" />  |
                  |                        |   |                |
       000        |                        |   |                |
       000        |                        |   |                |
                  |                        |   |                |
      /   \       | GET /1                 |   |/1              |
     /     \      |----------------------->|   |----------------|
                  |<-----------------------|   |<xml>           |
   111     222    | 200 OK                 |   |                |
   111     222    | <xml>                  |   |----------------|
                  |                        |
                  |                        |

3. Operazioni SAFE e UNSAFE

La funzione POST può essere usata sia in lettura che in scrittura, quindi va trattata con attenzione perché potrebbe cambiare lo stato della risorsa con effetti indesiderati. Ad esempio, se vogliamo incrementare il saldo di un conto corrente di 200€ utilizzando una POST e qualcosa va storto nella comunicazione (ad esempio il server va giù), ripetere l'operazione potrebbe depositare 400€ invece della somma corretta.
Le altre funzioni CRUD sono definite invece idempotenti, perché la loro ripetizione non ha effetti collaterali sulla risorsa:

In particolare, le operazioni idempotenti che non modificano lo stato del server (come la GET) sono definite SAFE; mentre quelle UNSAFE (come la POST) sono l'esatto opposto. Per limitare i potenziali danni di un'esecuzione errata di una UNSAFE è opportuno riformularla sotto forma di una serie di operazioni SAFE. Ad esempio, trasformare l'operazione Deposita(200€); in:
s = GetSaldo() // si usa una GET (operazione SAFE)
s = s + 200€ // operazione eseguita in locale
SetSaldo(s) // si usa una PUT (operazione idempotente)

Si ottiene perciò uno schema di questo tipo:

			 /saldo

OOO                      000
OOO                      000
 |                        |
 |  GET /saldo            |
 |----------------------->|
 |<-----------------------|
 |  200 OK                |
 |  s: 26                 |
 |                        |
 |                        |
 | PUT /saldo             |
 | s: 26                  |
 |----------------------->|
 |<-----------------------|
 |  200 OK                |
 |  s: 27                 |

Il servizio potrebbe essere ulteriormente raffinato, ad esempio tenendo conto delle modifiche concorrenti allo stato della risorsa da parte di due client diversi. Queste potrebbero generare infatti problemi come questo:

			 /saldo

OOO                      000                      888
OOO                      000                      888
 |                        |                        |
 |  GET /saldo            |                        |
 |----------------------->|                        |
 |<-----------------------|  PUT /saldo            |
 |  200 OK                |  s: 26                 |
 |  s: 26                 |<-----------------------|
 |                        |----------------------->|
 |                        |  200 OK                |
 | PUT /saldo             |  s: 27                 |
 | s: 26                  |                        |
 |----------------------->|                        |
 |<-----------------------|                        |
 |  409 Conflict          |                        |

Il messaggio di errore 409 informa che c'è stato un conflitto dovuto all'inconsistenza della risorsa, che era stata infatti chiamata con un valore diverso da quello corrente.

Nei sottoparagrafi seguenti vedremo altri esempi di soluzione dei problemi di utilizzo di operazioni UNSAFE.

3.1 Esempio "Ordini"

Abbiamo creato un servizio web che tra le altre cose permette all'utente di ordinare un oggetto e farselo recapitare a casa. Ciò che vogliamo è che nessun ordine venga perso, e che nessun ordine venga processato due volte (o spediremmo due oggetti uguali). In breve:

La soluzione è fare in modo che ogni richiesta d'ordine sia unica. Come? Ad esempio, se stiamo facendo compilare l'ordine attraverso una form html potremo inviare un parametro nascosto (insieme a quelli inseriti dall'utente) che faccia creare lato server un "ticket" dell'ordine. In questo modo se il server riceve una richiesta d'ordine con un ticket già noto potrà facilmente scartarla in quanto duplicato. Vediamo lo schema:

CLIENT			        SERVER

 OOO                             000
 OOO                             000
  |                               |
  |  POST                         |
  |------------------------------>| (insieme "ticket")
  |<------------------------------|
  |  200 OK                       |
  |  ticket                       |
  |                               |
  |                               |
  | POST                          |
  | ordine+ticket                 |
  |------------------------------>| (insieme "ordini aperti")
  |<------------------------------|
  |  201 Created                  |
  |  Location: URI ordini aperti  |

Alcune caratteristiche che deve avere il ticket:

3.2 Esempio "Ordini 2"

Proviamo a trovare una soluzione alternativa a quella descritta nel paragrafo precedente.
Se prima abbiamo "incorporato" il ticket nei dati inviati tra client e server, ora lo utilizzeremo per creare un ordine pendente, cioè una URL su cui il client potrà ordinare usando una PUT. Una volta ordinato con successo, l'ordine pendente sarà spostato nell'insieme di risorse ordini aperti, a cui il client sarà rediretto con una risposta dal server con codice di stato 303 ("la risposta alla richiesta può essere trovata sotto un'altra URI usando il metodo GET").
Ecco lo schema:

CLIENT			          SERVER

 OOO                               000
 OOO                               000
  |                                 |
  |  POST                           |
  |-------------------------------->| (insieme "ordini pendenti")
  |<--------------------------------|
  |  201 Created                    |
  |  Location: URI ordine pendente  |
  |                                 |
  |                                 |
  | PUT                             |
  | ordine                          |
  |-------------------------------->| (ordine pendente)
  |<--------------------------------|
  |  303 See Other                  |
  |  Location: URI ordine aperto    |

Si tratta di una soluzione migliore della precedente, perché usa l'operazione PUT che è meravigliosamente idempotente: potremo quindi continuare a inoltrare la richiesta finché questa non avrà successo.
L'insieme "ordini pendenti" non sarà altro che una nuova risorsa, che avrà dunque bisogno di essere considerata nella fase di design delle URI.

4. Metodologia di progettazione

Una possibile metodologia di progettazione di un servizio REST è la seguente:

  1. identificare le risorse da mostrare come servizi (ad esempio "elenco programmi", "report annuali di rischio", "catalogo libri", "tratte aeree", "sondaggi", ...);
  2. modellare le relazioni (ad esempio "è contenuta", "referenzia", "è una transizione di stato", ...) tra risorse tramite collegamenti ipertestuali. In questa fase bisogna evitare di creare delle "isole", ovvero rappresentazioni scollegate o irraggiungibili dalle altre;
  3. definire URI appropriate per indirizzare le risorse;
  4. decidere cosa far fare alle risorse attraverso le funzioni messe a disposizione dall'interfaccia comune. Si tenga conto che le informazioni andrebbero rivelate gradualmente: meglio avere tanti link per ottenere maggiori informazioni che inviare un singolo mega-documento di risposta;
  5. progettare e documentare la rappresentazione..
    • ..del formato delle richieste e delle risposte, utilizzando uno schema come il DTD, il W3C schema, ...;
    • ..del servizio, utilizzando un documento WSDL o un semplice html.
  6. implementare e fare il deploy del web server;
  7. testarlo con un browser.

5. Esempio finale: SONDAGGIO

Progettiamo l'applicazione web "POTA, VOTA!" che permette di creare sondaggi e di votare, e proviamo a seguire i primi quattro passi della metodologia di progettazione descritti sopra.

5.1 Identificare le risorse

Le risorse individuate sono:

5.2 Relazioni tra le risorse

Ogni sondaggio dell'insieme poll è identificato da un {id}, così come le istanze dell'insieme vote. L'{id} potrebbe essere benissimo un identificativo numerico.
Ogni istanza di sondaggio contiene la sotto-risorsa voto, che a sua volta contiene i voti effettivi degli utenti. Graficamente potremo avere una struttura di questo tipo:

poll
   {id1}
      vote
         {id4}
         {id5}
   {id2}
   {id3}
   ...

La strategia con cui vengono assegnati gli {id} dipende dalla funzione CRUD utilizzata per istanziare le risorse. La questione sarà approfondita nei paragrafi successivi.

5.3 Definire le URI

Le URI delle risorse dovranno incorporare gli {id} delle loro istanze, quindi avremo qualcosa come:

5.4 Definire l'interfaccia comune

5.4.1 Creare un sondaggio

Per creare un sondaggio dovremo:

  1. creare un documento xml conforme allo schema che "POTA, VOTA!" ha progettato per i sondaggi (e che ad esempio ha reso noto in un documento WSDL);
  2. inviare il documento al web server usando una POST.
	  	                     /poll
			             /poll/01234

888 -------------------------------> 000
888 <------------------------------- 000

  POST /poll
  Host: www.potavota.it
  Content-Type: application/xml
  <?xml version="1.0">		
  <options>A,B,C</options>	

  201 Created		
  Location: /poll/01234

Il server assegna al sondaggio appena creato una URL (www.potavota.it/poll/01234), che in futuro può essere usata come servizio web anche da altri client.
Ripetiamo per l'ennesima volta che il modo in cui il web service genera il sondaggio è completamente trasparente al client: l'unica cosa che deve sapere è che se invia quell'xml con una POST otterrà la creazione di una nuova istanza della risorsa poll. In virtù di questa trasparenza, i programmatori del servizio sono liberi di modificare l'implementazione delle risorse senza che il client ne risenta.

5.4.2 Leggere voti o sondaggi

Per leggere il contenuto di un messaggio o di un voto useremo una GET.

/poll
/poll/01234
/poll/01234/vote

000 <------------------------------- 888
000 -------------------------------> 888

  GET /poll/01234 HTTP/1.1
  Host: www.potavota.it
  Accept: application/xml

  200 OK
  <options>A,B,C</options>
  <votes href="/vote">

Il formato del documento che il server deve restituire a una GET non deve essere specificato nell'URI, ma nell'header della GET come valore del parametro Accept:, in cui va inserita la lista di tipi MIME accettati dal client.
Forzare il formato del documento da ricevere nell'URI (ad esempio con una GET www.potavota.it/poll/01234.html) non è un'ottima strategia per il client, perché il server potrebbe non essere in grado di rappresentare la risorsa nel formato richiesto e non gli ritornerebbe niente.

5.4.3 Votare

Partecipare al sondaggio significa creare una sotto-risorsa di tipo voto all'interno del poll interessato. Nello schema seguente (come nei successivi) sarà mostrata a sinistra l'operazione e a destra la verifica del suo risultato utilizzando una GET:

			              /poll
			              /poll/01234
		  	              /poll/01234/vote
			              /poll/01234/vote/1

888 --------------------------------> 000 <------------------------------ 888
888 <-------------------------------- 000 ------------------------------> 888

  POST /poll/01234/vote		            GET /poll/01234
  Host: www.potavota.it                     Host: www.potavota.it
  Content-Type: application/xml             Accept: application/xml
  <?xml version="1.0">	
  <name>Pippo</name>		            200 OK
  <choice>B</choice>                        <options>A,B,C</options>
                                            <votes>
  201 Created			              <vote id="1">
  Location:			                <name>Pippo</name>
  /poll/01234/vote/1		                <choice>B</choice>
				     	      </vote>
				            </votes>

5.4.4 Cambiare il voto

Cambiare il voto in un sondaggio significa aggiornare il valore di quello già dato, per cui dovremo utilizzare la chiamata PUT:

			              /poll
			              /poll/01234
		  	              /poll/01234/vote
			              /poll/01234/vote/1

888 --------------------------------> 000 <------------------------------ 888
888 <-------------------------------- 000 ------------------------------> 888

  PUT /poll/01234/vote/1 HTTP/1.1           GET /poll/01234
  Host: www.potavota.it                     Host: www.potavota.it
  Content-Type: application/xml             Accept: application/xml
  <?xml version="1.0">
  <name>Pippo</name>		            200 OK
  <choice>C</choice>                        <options>A,B,C</options>
                                            <votes>
  200 OK			     	      <vote id="1">
				                <name>Pippo</name>
				                <choice>C</choice>
					      </vote>
				            </votes>

Ora che abbiamo visto sia la POST che la PUT possiamo chiederci: "qual è il metodo corretto per creare una risorsa inizializzando il suo stato??"
La risposta è: dipende. Da cosa? Dal modo in cui vogliamo che gli {id} siano generati:

In realtà c'è una terza via: predisporre una URL del tipo www.potavota.it/poll/poll-new da richiamare con una GET, che restituirà al client tutte le informazioni necessarie per creare una nuova risorsa con una POST. Queste informazioni potrebbero essere sotto forma di form html, o di prototipo xml, o di altro tipo.

5.4.5 Cancellare voti o sondaggi

Infine, per cancellare un voto già dato o un intero sondaggio si userà la richiesta DELETE. Nell'esempio che segue mostreremo come avviene la cancellazione del sondaggio con ID 01234 (si noti che la GET successiva restituisce l'avviso che la risorsa non è stata trovata):

			      /poll
			      /poll/01234
		 	      /poll/01234/vote
			      /poll/01234/vote/1

888 ------------------------> 000 <------------------------ 888
888 <------------------------ 000 ------------------------> 888

DELETE /poll/01234		     GET /poll/01234
                                     Host: www.potavota.it
200 OK				     Accept: application/xml

                                     404 Not Found

6. Breve considerazione sui cookie

In un'architettura REST ben progettata i cookie non dovrebbero essere né presenti, né tanto meno necessari. In primo luogo per una coerenza di design: le chiamate devono essere stateless, quindi dovrebbero contenere al loro interno tutte le informazioni necessarie perché il server le processi. Poiché i cookie per definizione sono informazioni esterne alle chiamate, rappresenterebbero un'eccezione allo stile architetturale.
Il secondo motivo riguarda invece la scalabilità del sistema. Supponiamo ad esempio che i cookie contengano anche solo una chiave di sessione che catturi lo stato dell'applicazione. In questo caso il client dovrà trasferire meno informazioni per chiamata, ma il server sarà obbligato a salvarsi da qualche parte i dati contenuti nel cookie: più sono i client e più saranno i dati da salvare. Inoltre il server si dovrà dotare di un qualche meccanismo di garbage collection per ripulire tutte le sessioni diventate ormai inattive, e all'aumentare dei client serviti il carico di lavoro diventerebbe sempre più oneroso.


Torna alla pagina di Ingegneria dei Processi Aziendali

(Printable View of http://www.swappa.it/wiki/Uni/PanoramicaSulREST)