In questo articolo esamineremo le caratteristiche del paradigma REST, cercheremo le librerie Java più interessanti per implementare i nostri servizi e realizzeremo un progetto d'esempio.
Introduzione a REST
REST, o più esplicitamente Representional State Transfer, è una delle "buzzword" degli ultimi anni: l'acronimo indica uno stile architetturale per l'implementazione di servizi leggeri distibuiti, ed è stato utilizzato per la prima volta da da Roy T. Fielding per esporre la propria tesi di dottorato.
Il paradigma REST è basato su un protocollo di comunicazione stateless, client-server, chacheable e scalabile, tipicamente HTTP (ma non necessariamente, in realtà).
Nella sua forma più semplice si basa sull'utilizzo dei metodi propri del protocollo HTTP (Fielding stesso ha lavorato sullo standard) per effettuare tramite semplici richieste al server le consuete operazioni CRUD, garantendo al tempo stesso un facile disaccoppiamento tra client e server.
La richiesta di azioni che alterino lo stato della risorsa (modifiche di valore, rimozione, etc) seguono uno schema abbastanza naif simile al seguente:
Operazione | Metodo HTTP |
---|---|
Creazione nuova risorsa idempotente: Sostituzione risorsa esistente |
PUT |
Creazione nuova risorsa Modifica (valori) risorsa preesistente |
POST |
Cancellazione risorsa | DELETE |
Accesso (visualizzazione, etc) alla risorsa | GET |
REST consente quindi di implementare "servizi leggeri", incentrati sulle risorse e facilmente scalabili, contrapposti ai webservices e ai tradizionali metodi RPC.
Interazione client-server e affordance delle risorse
Il client dovrà preoccuparsi di effettuare richieste basate sulla struttura della Url, sul metodo di richesta e sul content-type obiettivo della rappresentazione.
Dal lato del server invece appare particolarmente essenziale la scelta di path semplici e strutturati, e con una semantica di facile comprensione. E' qui interessante osservare che una oculata scelta dei content-type da esporre consente di rendere disponibile e comprensibile una risorsa tanto agli agenti software che ai normali utenti umani.
Diviene perciò essenziale in questo contesto il concetto di affordance, con il quale si fa riferimento a quella che potremmo definire con una prefirasi come "la possibilità di azione su una risorsa": il termine è qui mutuato da una definizione dello psicologo Gibson.
La coppia <risorsa, azione da eseguire sulla risorsa> viene sostanzialmente rappresentata da una URI, che diventa perciò anche un meccanismo oggettivo di definizione di una semantica condivisa tra client e server. Un esempio semplice che potremmo citare ricorrendo all'HTML su HTTP è quello dei tag link, se si pensa agli utilizzi per rappresentare grafi emergenti nella pratica che va consolidandosi per il web semantico, o alla possibilità di uso per generare vere e propie mappe di navigazione (gestita da alcuni bowser come Opera).
Gli orari dei treni... per chi REST
Immaginando ad esempio una applicazione per la consultazione degli orari dei treni potrebbero esistere due tipi di risorse: la risorsa "treno" (descrittiva di dettagli quali ad esempio orario, costi, tragitto e tipologia), ed una risorsa "lista dei treni", anch'essa in possesso di una rappresentazione specifica
Le URL utilizzate non corrisponderebbero a risorse fisiche: probabilmente i dati verrebbero generati tamite delle query sul database, suscettibili di variazioni del tutto trasparenti all'utente, tali da permettere tra le altre cose di conservare nel tempo l'URL per una certa risorsa.
Potremmo utilizzare dispositivi diversi per accedere alle risorse, ottenenendo le rappresentazioni opportune (ricordiamoci di specificare il Content-type
!): il browser per navigare la pagina web che rappresenta l'orario dei treni, così come una applicazione candario/timeline da dispositivo mobile.
La lista dei treni sul tragitto da roma a milano potrebbe avere una url simile alla seguente:
http://www.orarioferroviario.net/trains/roma/milano
mentre la scheda di un singolo treno potrebbe essere raggiungibile alla URL:
http://www.orarioferroviario.net/trains/roma/milano/numero-corsa
È interessante qui notare come il progetto delle URL reali dipenderebbe dall'analisi dei casi d'uso. Probabilmente la sospensione di un treno non creerebbe problemi al riepilogo complessivo, consentendo l'introduzione di una risposta ad hoc per una eventuale richiesta ad hoc. Inoltre variazioni di orario non costituirebbero problemi, non essendo informazioni in alcun modo correlate alla costuzione logica della url. Da notare infine come la necessità di trasmissione di parametri di controllo logico sia qui estremamente ridotta, consentendo l'uso dei parametri sulle query per effettuare ad esempio richieste basate su filtri:
http://www.orarioferroviario.net/trains/roma/milano?partenza=2000
Pro e contro
Non bisogna credere che le cose semplici siano meno faticose da realizzare, e Fielding stesso ci mette in guardia rispetto a questo malinteso: la semplicità di un appoccio REST ottimale è l'obiettivo da raggiungere, e potrebbe in certi casi costare anche tempi di realizzazione più lunghi di quelli di approcci ormai consolidati.
- contro: costruire interazioni particolarmente complesse sulle risorse potrebbe quindi richiedere maggiore attenzione e tempo rispetto ad approcci differenti.
- pro: il vantaggio in questo contesti rispetto a tecnologie più impegnative come SOAP/RPC è nella riduzione della parte di configurazione e nella riduzione dei parametri di contollo: non abbiamo cioè bisogno di spedire "messaggi" per informare client o server di ciò che sta per avvenire. La semplicità ottenuta ha trovato una ampia adesione presso moltissimi framework e api, e piattaforme "social" molto note.
A tutti gli effetti non è però essenziale l'utilizzo di HTTP: Fielding insiste piuttosto sull'idea di Hypertext e Hypermedia.
Hypermedia e Web service
Ogni risorsa è unica e le azioni destinate a modificarne lo stato devono essere espresse anch'esse tramite URI logiche; è quindi rappresentata da un mediatype che ha il compito di informare il client sui metodi da utilizzare per gestire gli hyperlink e in generale le URI.
I collegamenti ipertestuali e ipermediali consentono di mettere in relazione le risorse, e deve essere possibile richiedere azioni che ne modifichino lo stato interno, senza dovere invece tenere traccia dello stato della richiesta.
Nel caso di risorse statiche non devono essere utilizzati riferimenti dipendenti da percorsi fisici, nè riferimenti a specifiche tecnologie di elaborazione. Non andrebbero infine utilizzate delle URI prefissate: dovrebbe essere possibile (ri)costruire facilmente le URI con regole di facile interpretazione o individuazione, per consentire una eventuale successiva riconfigurabilità.
Principi REST(ful)
In realtà REST va inteso nelle intenzioni dell'autore come un vero e proprio pattern architetturale: diversi aspetti della metodologia di progetto suggerita rimandano alle best practice maturate nel web. Si pensi tanto per fare un esempio al concetto di permalink, che in qualche misura consente di ragionare per risorse, spesso immerse in contesti di media misti e fruibili con varie tipologie di dispositivi e software.
La definizione è d'altro canto tutt'ora oggetto di controversie: essendo impossibile stabilire dei criteri inoppugnabili di verifica della aderenza alle linee guida del pattern, alcuni hanno deciso di adottare il termine RESTful per sottolineare la piena conformità ad una serie di principi e prendere le distanze da implementazioni magari più semplici e più "pragmatiche", probabilmente influenzati dal dibattito che l'autore stesso alimenta quotidianamente sul suo sito.
Questi sono i principi RESTful essenziali:
- client/server
- stateless (ad esempio HTTP, ma non necessariamente)
- a livelli
- cacheable
- scalabile
Nella prima parte dell'articolo abbiamo tracciato le linee teoriche del paradigma e abbiamo evocato, con esempi molto semplici, alcune possibili linee di implementazione.
A questo punto ci concentreremo su come implementare questi "servizi leggeri" in java: in particolare cercheremo di capire quali siano i framework a nostra disposizione per realizzare applicazioni REST in maniera semplice.
Framework e librerie Java per REST
Esistono diverse soluzioni utilizzabili per l'implementazione di applicazioni basate su REST in java, alcune delle quali conformi allo standard proposto JSR311, ed altre basate su approcci del tutto (o quasi) indipendenti. Generalmente è possibile l'esposizione delle risorse e la manipolazione dei mediatype tramite opportune annotazioni, spesso sovrapponibili tra di loro, ed è in tal modo generalmente reso di facile implementazione il principio HATEOAS, cioè la risposta hypermedia generata dal server deve contenere dei links ad uri corrispondenti alle azioni che possono essere generate.
Abbiamo raccolto qui di seguito un piccolo elenco dei framework più diffusi per l'implementazione REST in java:
Framework | Note |
---|---|
Jersey | Implementazione delle specifiche JAX-RS di Sun/Oracle, utilizza principalmente annotazioni, e supporta anche parametri "a matrice", come vedremo dopo |
Resteasy | Implementazione di JBoss delle specifiche JAX-RS. |
Spring | Spring framework offre diversi vantaggi nella costruzione e testabilità di un ambiente robusto e semplifica l'attuazione dei requisiti di progettazione delle URL logiche e della loro scalabilità
Nota: Dalla versione 3 in poi, in Spring è stata semplificata l'interazione di MVC con Ajax e l'esposizione di rappresentazioni JSON e XML |
RESTlet | Di massima un qualsiasi servizio REST garantisce una certa indipendenza del client dalla piattaforma e da uno specifico linguaggio di programmazione, e si pesta particolarmente bene ai test con tool da riga di comando come curl .Ciò nonostante, alcuni framework mettono a diposizione dei client interni o dei wrapper a librerie utili a costruire un client. Tra le quattro implementazioni, quella del framework RESTlet mette a disposizione anche una implementazione di base per la parte client, essendo pensata per agevolare lo sviluppo su dispositivi mobili basati su Android |
Abbiamo scelto di realizzare la nostra applicazione "giocattolo" basata su REST utilizzando Spring, per la notevole modularità e progressiva grande diffusione di questo framework: l'intento è quello di fornire una bozza di progetto da cui partire per realizzare applicazioni reali. Per chi non conoscesse il framework o volesse approfondirne aspetti specifici sulle convenzioni usate e sui componenti MVC rimandiamo alla Guida Spring.
L'obiettivo è quello di implementare un'applicazione per consultare un semplice orario ferroviario. La nostra applicazione dovrà esporre una semplice lista di treni e le schede specifiche per ogni treno. Renderemo accessibili le risorse in vari formati (XML, JSON), oltre a garantirne la navigazione via browser (HTML).
1. Creazione e configurazione del progetto
Per iniziare creiamo il progetto per l'applicazione, utilizzando Eclipse. Una volta lanciato l'IDE scegliamo per comodità l'opzione di generazione di un web-project, in questo modo semplifichiamo un po' la configurazione ed abbiamo una struttura di progetto già pronta e abbastanza convenzionale.
Ci serviranno naturalmente un po' di librerie: in particolare Spring framework, JAXB (per il marshalling di XML), Jackson (per la gestione JSON), e poche altre. Per facilitare la gestione delle dipendenze possiamo avvalerci del tool maven, di facile integrazione tramite un plugin opportuno in Eclipse.
Per chi volesse invece importare ed utilizzare direttamente il progetto di esempio allegato occorrerà personalizzare il path di deploy per il proprio web-container nelle property del file pom.xml:
<properties>...
<deploy.webapps>E:javatomcatwebapps</deploy.webapps>
</properties>
ed effettuare un semplice
mvn install
che genererà il war file dell'applicazione direttamente all'interno del path scelto. (si è scelta questa soluzione per non appesantire troppo il procedimento)
Il progetto presenta la struttura visibile in figura:
da cui è facile individuare la presenza di un unico controller, di due bean rappresentanti le due tipologie di risorse che vogliamo gestire (lista dei treni e singolo treno), e le relative view per la parte presentazionale. Avremo poi necessità di simulare lo strato di persistenza dei dati, tramite la classe TrainDS, più alcuni file xml per la configurazione del contesto dell'applicazione, del rewrite delle url e della dispatcher servlet, che come sappiamo è il fulcro della gestione di controllo di Spring.
Strumenti di supporto per i test
Per testare la nostra applicazione utilizzeremo dei tool molto semplici da usare, così da concentrarci sulla strategia di implementazione: è facile immaginare che possano essere facilmente sostituiti con strumenti più robusti ed integrati all'interno del framework.
- curl: http://curl.haxx.se/ è un semplice tool a riga di comando molto utilizzato in ambito linux ed installabile anche su altri sistemi operativi. Tramite esso è possibile effettuare richieste HTTP ai servizi in sviluppo concentrandosi soltanto sulle risposte ottenute, è quindi molto utile nella fase di prima costruzione dei controller.
- RESTClient: https://addons.mozilla.org/en-US/firefox/addon/restclient/ è un plugin per Mozilla Firefox pche offre funzionalità simili a curl all'interno di una interfaccia grafica integrata nel browser. Noi lo utilizzeremo per evidenziare le interazioni relative a Content-Type e metodi di richiesta diversi da quelli relativi ad una normale navigazione dei contenuti via browser.
2. Creare i bean
Considereremo quindi due risorse: la lista e la singola scheda. La scelta più naturale appare quella di creare due bean, ciascuno dei quali può essere annotato con le annotazioni jaxb.
Ad esempio il bean relativo ad un singolo treno sarà caratterizzato da proprietà quali il codice identificativo del treno, le località di destinazione e partenza, e l'orario di partenza, e ci basterà annotare la classe con il nome del tag xml da generare:
il bean Train
@XmlRootElement(name="train")
public class Train {
private long id;
private String from, to, departureTime;
public Train() {}
[...]
// getters
// setters
}
In questo modo disponiamo di un tipo di dati utilizzabile all'interno della lista treni definita dal prossimo bean, appena più complicato del precedente:
il bean TrainList
@XmlRootElement(name="trains")
public class TrainList {
private int count;
private Set<Train> trains;
[...]
@XmlElement(name="train")
public Set<Train> getTrains() {
return trains;
}
public void setTrains(Set<Train> trains) {
this.trains = trains;
}
}
Le annotazioni inserite hanno il pregio di non alterare la struttura dei bean e di essere gestite in maniera quasi del tutto trasparente dal marshaller, che sarà dunque in grado di generare una rappresentazione xml dell'intera lista, costruendola assemblando l'xml generato dai due bean. Naturalmente il marshalling avverrà a seguito di richieste per il Content-Type xml. Tra poco vedremo infatti anche come definire la content negotiation.
3. Configuriamo Spring: controller e views
Definiamo poi alcuni file di configurazione per Spring: qui per scrivere i file da zero è richiesta una familiarità di base con il framework, quindi ci concentreremo solo sui particolari più interessanti. In particolare per quanto concerne web.xml lasciamo il controllo delle richieste alla DispatcherServlet di Spring. Questa servlet principale utilizza un alias "rest", al quale è associato convenzionalmente un suo proprio file di configurazione rest-servlet.xml, e a cui associamo un file per gestire i dati di prova, che vedremo dopo: rest-context.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/rest-context.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>rest</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>rest</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
[...]
<filter-mapping>
<filter-name>UrlRewriteFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Scegliamo poi come nome del contesto di esecuzione anche qui "rest": le richieste andrebbero pertanto effettuate nella forma:
-applicazione-/rest/trains
ma decidiamo di mascherare il contesto per esporre dei path più comprensibili, tramite url rewrite:
-applicazione-/trains
rest-context.xml - Questo file ci servirà per indicare a Spring come generare i dati di esempio. In pratica avremo bisogno di definire un bean TrainDS (data service) che simuli l'interazione con uno strato di persistenza o una qualche sorgente dati per le risorse, nonchè ovviamente i metodi di manipolazione. Per semplicità dentro TrainDS aggiungiamo staticamente alcuni dati di prova, e perchè questi siano accessibili nel contesto di esecuzione di spring fin dall'avvio basta scrivere in rest-context:
<script type="code" language="xml">
<bean id="trainDS" class="esempi.rest.spring.ds.TrainDS" />
</script>
Il controller TrainController
Data la semplicità di navigazione prevista abbiamo scelto di utilizzare un solo controller (la classe TrainController) che si farà carico di gestire entrambi i tipi di risorsa in base alle richieste ricevute. Tanto per cominciare sarà opportuno preoccupaci di implementae alcuni metodi per gestire la visualizzazione dell'intera lista dei treni:
@Controller
public class TrainController {
// questo oggetto contiene i dati di test
private TrainDS trainDS;
// questo è l'oggeto che si occuperà del marshalling
private Jaxb2Marshaller jaxb2Mashaller;
[...]
@RequestMapping(method=RequestMethod.GET, value="/trains")
public ModelAndView getTrains() {
TrainList list = new TrainList(trainDS.getAll());
return new ModelAndView("trainsView", "trains", list);
}
@RequestMapping(method=RequestMethod.GET, value="/trains/{from}/{to}")
public ModelAndView getTrainsByPath(@PathVariable String from, @PathVariable String to) {
Set<Train> buffer = trainDS.getTrainsByPath(from, to);
final TrainList list = new TrainList(buffer);
return new ModelAndView("trainsView", "trains", list);
}
}
Il primo metodo elabora una richiesta GET (la classica richiesta http inviata dal browser in fase di navigazione) sul path
costruisce il model contenente i dati di esempio e li spedisce ad una view che definiremo tra poco (all'interno della directory /WEB-INF/jsp), e che chiameremo trainsView.jsp. Il codice jsp è veramente molto semplice (viene sostanzialmente costruita una tabella) e lo omettiamo per brevità, il risultato a video (aggiungendo qualche minima direttiva CSS) sarà qualcosa di simile a questo:
Per rendere possibile il rilevamento e la corretta esecuzione del nostro semplice controller da parte del framework Spring, dovremo preoccuparci di un po' di configurazioni all'interno del file rest-servlet.xml. Andrà infatti definita la scansione per rilevare il controller annotato, e dovremo preoccuparci di definire alcuni tag utili al framework per "iniettare" sugli opportuni setter method i dati ed il marshaller all'interno del controller.
// scansione per il rilevamento del controller
<context:component-scan base-package="esempi.rest.spring.controller" />
// marshaller sui bean
<bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>esempi.rest.spring.bean.Train</value>
<value>esempi.rest.spring.bean.TrainList</value>
</list>
</property>
</bean>
[...]
// comunichiamo al framework di iniettare i dati nel controller
<bean id="trainController" class="esempi.rest.spring.controller.TrainController">
<property name="trainDS" ref="trainDS" />
<property name="jaxb2Mashaller" ref="jaxbMarshaller" />
</bean>
Per generare l'xml utilizziamo il sistema standard di Jaxb2Marshaller, facilmente utilizzabile dentro Spring: una volta richiesta una risorsa utilizzando il content-type application/xml, il bean verrà convertito dal marshaller e fornito al client che ne ha fatto richiesta.
aggiungiamo finalmente la configurazione necessaria per gestire le view jsp:
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
Come accennavamo non vogliamo soffermarci più di tanto nell'esame delle view perchè esse presentano una struttura particolarmente semplice e convenzionale, pensata per esporre una visualizzazione piuttosto lineare dei dati presenti nei bean di riferimento. Ad esempio per la view relativa alla lista dei treni per ottenere una presentazione simile a quella della figura 2 ci basterà prevedere una struttura del tipo:
/WEB-INF/jsp/trainsView.jsp
<table>
<thead>
<tr>
<th>codice treno</th>
<th>partenza</th>
<th>arrivo</th>
<th>orario di partenza</th>
</tr>
</thead>
<c:forEach var="train" items="${trains.trains}">
<tr>
<td><a href="${basePath}/train/${train.id}.html">${train.id}</a></td>
<td>${train.from}</td>
<td>${train.to}</td>
<td>${train.departureTime}</td>
</tr>
</c:forEach>
</table>
Nella view precedente abbiamo previsto la possibilità di cliccare su un treno specifico per esaminarne una scheda dettagliata. L'ossatura della scheda di un treno è del tutto simile alla precedente.
Aggiungiamo quindi al controller un metodo get per gestire la nuova view, l'unico elemento di interesse in questo caso è la necessità di utilizzare una uri contenente l'id necessario per accedere univocamente alla risorsa.
@RequestMapping(method=RequestMethod.GET, value="/train/{id}")
public ModelAndView getTrain(@PathVariable String id) {
Train e = trainDS.get(Long.parseLong(id));
return new ModelAndView("trainView", "train", e);
}
È interessante osservare come tramite i parametri della annotazione @RequestMapping possiamo definire tutto ciò che ci serve per un servizio REST, ovvero:
- value - una url su cui effettuare la richiesta (al limite integrando i parametri nella url come @PathVariable, come nel nostro caso, per l'id)
- method - un metodo http per la richiesta
- headers – un array di stringhe rappresentate gli header da inviare per la richiesta, tra cui il Content-Type.
Riassumendo: se tutto è andato nel migliore dei modi possiamo finalmente navigare le due pagine che rappresentano in html le nostre i dati delle nostre risorse di prova.
Il sistema più semplice per provare il funzionamento dei servizi appena creato è accedere alle risorse (essenzialmente: la lista totale dei treni e la scheda semplificata descrittiva di un treno) navigandole tramite il browser come pagine web (modalità GET), ottenendo le pagine già presentate sopra:
4. Content Negotiation
Una parte essenziale dei servizi REST è giocata come dicevamo dalla possibilità di richiedere differenti rappresentazioni per le stesse risorse. A tal proposito gioca un ruolo fondamentale la definizione della content-negotiation: in pratica si tratta di utilizzare all'interno di rest-servlet.xml un ViewResolver leggermente più complicato del solito, che descriva anche le possibili tipologie di media-type (nel nostro caso pagine web html oppure rappresentazioni xml/json delle risorse) esponibili dal servizio.
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml"/>
</map>
</property>
<property name="defaultViews">
<list>
<!-- esposizione json -->
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/>
<!-- esposizione xml -->
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg ref="jaxbMarshaller" />
</bean>
</list>
</property>
</bean>
La presenza di un certo grado di disomogeneità nella gestione dei parametri di richiesta da parte di browser differenti (rispetto all'agent ma anche al Content-Type) rende particolarmente importante l'individuazione di alcuni workaround che assicurino una resa omogenea dei risultati. Ad esempio nella figura 3 si può osservare l'utilizzo dell'estensione .html sulle richieste, che consente di esplicitare ed uniformare la tipologia di richiesta rispetto a come verrà trattata da browser differenti. In pratica omettendo l'estensione .html nell'esempio proposto e navigando le pagine con il browser Chrome non otterremmo il risultato in figura, ma una rappresentazione xml.
Per ovviare a problemi di questo tipo la Content-negotiation definita sopra consente di utilizzare all'interno del path di richiesta delle estensioni che rendano esplicito il Content-Type richiesto, anche qualora esso non venga correttamente trasmesso dal dispositivo di navigazione. In più la presenza di una estensione nella uri consente un ulteriore livello di cache, ove configurabile, e non costringe alla trasmissione di ulteriori parametri di controllo, mantendendoci pertanto in maniera chiara ed efficiente ancora una volta all'interno dello stile REST.
5. Navigazione e Test
Test dei Content-Type
Un metodo molto semplice per convincersi che tutto sta funzionando a dovere anche per i Content-Type differenti dall'html è quello di provare a navigare le uri utilizzando come estensioni finali .xml
e .json
: otterremo delle rappresentazioni nei due formati dei nostri dati, anche se un po' scomode da consultare dentro al browser. Anche da questo punto di vista va sottolineato ennesimamente come ciascun browser adotti una propria politica per visualizzare dati non html ed xml in particolare.
Una maniera decisamente più comoda ma altrettanto semplice di fare qualche prova e leggere più agevolmente i risultati di navigazione è quella di utilizzare delle chiamate curl:
equivalente alla navigazione sulla uri: .../train.xml
equivalente alla navigazione sulla uri: .../train.json
Aggiunta dei metodi HTTP: DELETE
A questo punto dovremmo avere un paio di tipi di risorse, navigabili su dati di esempio (che simulano l'accesso ad un database o altro, non è importante) in "sola lettura", ma in vari formati. Possiamo cioè farci restituire rappresentazioni alternative delle stesse risorse, ma non abbiamo ancora implementato dei metodi per modificare i dati di esempio.
L'implementazione dell'azione di cancellazione di una risorsa in base al suo ID all'interno del nostro controller è particolarmente semplice, e consiste nell'utilizzo della stessa uri di lettura della risorsa, utilizzando però il metodo DELETE al posto del metodo GET:
@RequestMapping(method=RequestMethod.DELETE, value="/train/{id}")
public ModelAndView removeTrain(@PathVariable String id) {
trainDS.remove(Long.parseLong(id));
Set<Train> trains = trainDS.getAll();
TrainList list = new TrainList(trains);
return new ModelAndView("trainsView", "trains", list);
}
A questo punto se volessimo provare a cancellare i dati del terzo treno non dovremmo fare altro che
e poi verificare che tutto sia andato a buon fine ricaricando la lista dei treni sul browser (in realtà giàcurl ci restituirà la pagina html dei treni).
aggiunta dei metodi HTTP: POST e PUT
Per concludere ci proponiamo di implementare delle azioni di aggiunta e modifica di treni. Per comodità i dati da inserire (o modificare) verranno inviati in formato xml, quindi la prima cosa da fare è implementare due metodi nel controller, rispettivamente per l'aggiunta e la modifica dei dati di una risorsa.
// aggiunta nuova risorsa
@RequestMapping(method=RequestMethod.POST, value="/trains", headers={"content-type=application/xml"})
public ModelAndView addTrain(@RequestBody String body) {
Source source = new StreamSource(new StringReader(body));
Train e = (Train) jaxb2Mashaller.unmarshal(source);
trainDS.add(e);
return new ModelAndView("trainView", "train", e);
}
È interessante notare come nel caso POST gestiamo l'aggiunta di una nuova risorsa, quindi la uri di riferimento sarà la uri dell'intera risorsa lista:
È importante ricordarsi di selezionare opportunamente il Content-type.
Nel secondo caso (il PUT) gestiamo l'aggiornamento di una risorsa in base ad id (qualora essa non esista già verrà comunque aggiunta ex-novo), quindi la uri sarà stavolta quella già usata per la scheda di un singolo treno.
// modifica risorsa esistente
@RequestMapping(method=RequestMethod.PUT, value="/train/{id}")
public ModelAndView updateTrain(@RequestBody String body) {
Source source = new StreamSource(new StringReader(body));
Train e = (Train) jaxb2Mashaller.unmarshal(source);
trainDS.update(e);
return new ModelAndView("trainView", "train", e);
}
Immaginando allora di voler aggiungere una nuova risorsa inviando un frammento di dati xml durante una richiesta (PUT), il controller farà unmarshaller dell'xml aggiungendo il nuovo oggetto Train creato dall'xml alla lista interna che simula la persistenza, e così via negli altri casi...
6. Timetable JSON sui nostri dati
È stata infine inserita nel progetto una semplicissima timetable realizzata in javascript, che consente una navigazione basata su richieste json, e che vorrebbe dare una suggestione per l'implementazione di semplici applicazioni mobile basate sui servizi appena realizzati:
Conclusioni
Concludiamo proponendo un confronto di massima tra le annotazioni Spring utilizzate e quelle JAX-RS, per offrire un punto di partenza a chi volesse sperimentare anche altre soluzioni, alternative a Spring.
JAX-RS | Spring |
---|---|
@GET @Path | @RequestMapping(method="GET") |
@QueryParam("q") | @RequestParam("q") |
@FormParam("f") | ---- |
@CookieParam("c") | @CookieValue("c") |
@HeaderParam("h") | @Requestheader("h") |
@MatrixParam("m") | I matrix parameter non sono attualmente supportati, a meno di simularli tramite tecniche di url-rewrite. |
@Consumes("text/html") @Produces("text/html") |
Al momento non esiste un vero supporto in questo senso: l'unica possibilità è utilizzare gli attributi "Content-Type" ed "Accept" di @RequestMapping. Il workaround usato anche nel nostro esempio prevede la generazione di estensioni sulla uri, per rendere chiara la richiesta anche in assenza di header opportuni. |