I servizi Web, in una forma o nell’altra, sono in circolazione da più di due decenni. Ad esempio, i servizi XML-RPC sono apparsi alla fine degli anni 1990, seguiti a breve da quelli scritti nella propaggine SOAP. Servizi in stile architettonico RESTO anche fatto la scena circa due decenni fa, subito dopo i pionieri XML-RPC e SOAP. I servizi REST-style (di seguito, Restful) ora dominano in siti popolari come eBay, Facebook e Twitter. Nonostante le alternative ai servizi web per il calcolo distribuito (ad es., web socket, microservizi e nuovi framework per chiamate a procedure remote), i servizi web Restful rimangono interessanti per diversi motivi:

  • I servizi Restful si basano sull’infrastruttura e sui protocolli esistenti, in particolare sui server Web e sui protocolli HTTP / HTTPS. Un’organizzazione che ha siti web basati su HTML può facilmente aggiungere servizi web per i clienti interessati più nei dati e funzionalità di base che nella presentazione HTML., Amazon, ad esempio, è stato il pioniere di rendere disponibili le stesse informazioni e funzionalità tramite siti Web e servizi Web, basati su SOAP o Restful.

  • I servizi Restful trattano HTTP come un’API, evitando così la complicata stratificazione software che è arrivata a caratterizzare l’approccio basato su SOAP ai servizi Web. Ad esempio, l’API Restful supporta le operazioni CRUD standard (Create-Read-Update-Delete) tramite i verbi HTTP POST-GET-PUT-DELETE, rispettivamente; I codici di stato HTTP informano un richiedente se una richiesta è riuscita o perché non è riuscita.,

  • I servizi Web Restful possono essere semplici o complicati secondo necessità. Restful è uno stile—anzi, molto flessibile-piuttosto che un insieme di prescrizioni su come i servizi dovrebbero essere progettati e strutturati. (Il lato negativo è che può essere difficile determinare ciò che non conta come un servizio riposante.)

  • Per un consumatore o un cliente, i servizi web Restful sono neutrali dal linguaggio e dalla piattaforma. Il client effettua richieste in HTTP (S) e riceve risposte di testo in un formato adatto per lo scambio di dati moderno (ad esempio, JSON).,

  • Quasi tutti i linguaggi di programmazione generici hanno almeno un supporto adeguato (e spesso forte) per HTTP / HTTPS, il che significa che i client di servizi Web possono essere scritti in quelle lingue.

Questo articolo esplora i servizi Restful leggeri in Java attraverso un esempio di codice completo.

Il servizio web Restful novels

Il servizio web Restful novels è costituito da tre classi definite dal programmatore:

  • La classe Novel rappresenta un romanzo con solo tre proprietà: un ID generato dalla macchina, un autore e un titolo., Le proprietà potrebbero essere espanse per un maggiore realismo, ma voglio mantenere questo esempio semplice.
  • La classeNovelsconsiste in utility per varie attività: convertire una codifica in testo normale di unNovel o un loro elenco in XML o JSON; supportare le operazioni CRUD sulla raccolta di romanzi; e inizializzare la raccolta dai dati memorizzati in un file. La classeNovels media tra le istanzeNovel e il servlet.,
  • La classeNovelsServletderiva daHttpServlet, un software robusto e flessibile che esiste sin dai primi Java aziendali della fine degli anni ‘ 90. Il servlet funge da endpoint HTTP per le richieste CRUD client. Il codice servlet si concentra sull’elaborazione delle richieste dei client e sulla generazione delle risposte appropriate, lasciando i dettagli diabolici alle utilità nella classe Novels.

Alcuni framework Java, come Jersey (JAX-RS) e Restlet, sono progettati per i servizi Restful., Tuttavia, HttpServlet da solo fornisce un’API leggera, flessibile, potente e ben testata per la fornitura di tali servizi. Lo dimostrerò con l’esempio dei romanzi.

Distribuire il servizio web romanzi

La distribuzione del servizio web romanzi richiede un server web, ovviamente. La mia scelta è Tomcat, ma il servizio dovrebbe funzionare (ultime parole famose!) se è ospitato su, ad esempio, Jetty o anche su un server di applicazioni Java. Il codice e un README che riassume come installare Tomcat sono disponibili sul mio sito web., C’è anche uno script Apache Ant documentato che costruisce il servizio romanzi (o qualsiasi altro servizio o sito web) e lo distribuisce sotto Tomcat o l’equivalente.

Tomcat è disponibile per il download dal suo sito web. Una volta installato localmente, lasciare che TOMCAT_HOME sia la directory di installazione., Ci sono due sottodirectory di interesse immediato:

  • TOMCAT_HOME/bin directory contiene avvio e di arresto script per sistemi Unix-like (startup.sh e shutdown.sh) e Windows (startup.bat e shutdown.bat). Tomcat viene eseguito come applicazione Java. Il contenitore servlet del server web si chiama Catalina. (In Jetty, il server Web e il contenitore hanno lo stesso nome.) Una volta avviato Tomcat, immettere in un browser per visualizzare un’ampia documentazione, inclusi esempi.,

  • La directory TOMCAT_HOME/webapps è l’impostazione predefinita per i siti Web e i servizi Web distribuiti. Il modo più semplice per distribuire un sito web o un servizio Web è copiare un file JAR con un’estensione .war (quindi, un file WAR) in TOMCAT_HOME/webapps o una sottodirectory dello stesso. Tomcat quindi decomprime il file WAR nella propria directory. Ad esempio, Tomcat decomprimerebbe novels.war in una sottodirectory denominata novels, lasciando novels.war così com’è., Un sito web o un servizio può essere rimosso eliminando il file WAR e aggiornato sovrascrivendo il file WAR con una nuova versione. A proposito, il primo passo nel debug di un sito Web o servizio è verificare che Tomcat abbia decompresso il file WAR; in caso contrario, il sito o il servizio non è stato pubblicato a causa di un errore fatale nel codice o nella configurazione.,

  • Poiché Tomcat ascolta per impostazione predefinita sulla porta 8080 le richieste HTTP, inizia un URL di richiesta per Tomcat sulla macchina locale:

    Access a programmer-deployed WAR file by adding the WAR file’s name but without the .war extension:

    If the service was deployed in a subdirectory (e.g., myapps) of TOMCAT_HOME, this would be reflected in the URL:

    I’ll offer more details about this in the testing section near the end of the article.

As noted, the ZIP file on my homepage contains an Ant script that compiles and deploys a website or service. (A copy of novels.war is also included in the ZIP file.) For the novels example, a sample command (with % as the command-line prompt) is:

% ant -Dwar.name=novels deploy

Questo comando compila i file sorgente Java e quindi crea un file distribuibile chiamato novels.war, lascia questo file nella directory corrente e lo copia in TOMCAT_HOME/webapps., Se tutto va bene, un GET domanda (utilizzando un browser o un’utilità della riga di comando, ad esempio curl) serve come primo test:

% curl http://localhost:8080/novels/

Tomcat è configurato per impostazione predefinita, per il caldo distribuisce: il server web non ha bisogno di essere spento per la distribuzione, aggiornamento, o rimuovere un’applicazione web.

Il servizio romanzi a livello di codice

Torniamo all’esempio romanzi, ma a livello di codice. Si consideri la classeNovel qui sotto:

Esempio 1., La nuova classe

Questa classe implementa l’ compareTo metodo Comparable interfaccia, perché Novel istanze sono memorizzati in un thread-safe ConcurrentHashMap, che non impone un ordinamento. Nel rispondere alle richieste di visualizzazione della raccolta, il servizio romanzi ordina una raccolta (un ArrayList) estratta dalla mappa; l’implementazione di compareTo impone un ordine crescente ordinato per Novel ID.,

La classe Novels contiene varie funzioni di utilità:

Esempio 2. La classe di utilità Romanzi

Il metodo più complicato èpopulate, che legge da un file di testo contenuto nel file WAR distribuito. Il file di testo contiene la raccolta iniziale di romanzi. Per aprire il file di testo, il metodopopulate ha bisogno delServletContext, una mappa Java che contiene tutte le informazioni critiche sul servlet incorporato nel contenitore servlet., Il file di testo, a sua volta, contiene record come questo:

Jane Austen!Persuasion

La riga viene analizzata in due parti (autore e titolo) separate dal simbolo bang (!). Il metodo crea quindi un’istanzaNovel, imposta le proprietà autore e titolo e aggiunge il romanzo alla raccolta, che funge da archivio dati in memoria.

La classeNovels ha anche utilità per codificare la raccolta di romanzi in XML o JSON, a seconda del formato preferito dal richiedente., XML è l’impostazione predefinita, ma JSON è disponibile su richiesta. Un pacchetto XML-to-JSON leggero fornisce il JSON. Ulteriori dettagli sulla codifica sono di seguito.

Esempio 3. Il NovelsServlet classe

Ricordiamo che NovelsServlet classe di cui sopra si estende il HttpServlet classe, che a sua volta si estende il GenericServlet classe che implementa l’ Servlet interfaccia:

NovelsServlet extends HttpServlet extends GenericServlet implements Servlet

Come il nome, il HttpServlet è stato progettato per i servlets consegnato su HTTP(S)., La classe fornisce metodi vuoti di nome dopo la norma richiesta HTTP verbi (ufficialmente, metodi):

  • doPost Post (= Creare)
  • doGet (Get = Leggere)
  • doPut (Put = Aggiornamento)
  • doDelete (Delete = cancella)

Alcuni altri verbi HTTP sono coperti bene. Un’estensione di HttpServlet, come NovelsServlet, sovrascrive qualsiasi metodo di interesse do, lasciando gli altri come no-ops., IlNovelsServlet sostituisce sette deido metodi.

Ciascuno dei metodi CRUD HttpServlet accetta gli stessi due argomenti. Ecco doPost come esempio:

public void doPost(HttpServletRequest request, HttpServletResponse response) {

L’argomento request è una mappa delle informazioni della richiesta HTTP e response fornisce un flusso di output al richiedente., Un metodo come doPost è strutturato come segue:

  • Leggi le informazionirequest, intraprendendo qualsiasi azione sia appropriata per generare una risposta. Se le informazioni sono mancanti o altrimenti carenti, generare un errore.
  • Utilizzare le informazioni di richiesta estratte per eseguire l’operazione CRUD appropriata (in questo caso, creare unNovel) e quindi codificare una risposta appropriata al richiedente utilizzando ilresponse flusso di output per farlo., Nel caso di doPost, la risposta è una conferma che un nuovo romanzo è stato creato e aggiunto alla raccolta. Una volta inviata la risposta, il flusso di output viene chiuso, che chiude anche la connessione.

Maggiori informazioni sul metodo do sovrascrive

Una richiesta HTTP ha una struttura relativamente semplice. Ecco uno schizzo nel familiare HTTP 1.,1 formato, con commenti introdotti da doppi segni taglienti:

La riga iniziale inizia con il verbo HTTP (in questo caso,GET) e l’URI (Uniform Resource Identifier), che è il nome (in questo caso,novels) che nomina la risorsa mirata. Le intestazioni sono costituite da coppie chiave-valore, con due punti che separano la chiave a sinistra dai valori a destra., L’intestazione con il tastoHost(case insensitive) è obbligatorio; il nome hostlocalhostè l’indirizzo simbolico della macchina locale sulla macchina locale, e il numero di porta8080è il valore predefinito per il server web Tomcat in attesa di richieste HTTP. (Per impostazione predefinita, Tomcat ascolta sulla porta 8443 per le richieste HTTPS.) Gli elementi di intestazione possono verificarsi in ordine arbitrario. In questo esempio ,il valore dell’intestazione

Accept-typeè il tipo MIMEtext/plain.,

Alcune richieste (in particolare, POSTe PUT) hanno corpi, mentre altre (in particolare, GETe DELETE) non lo fanno. Se c’è un corpo (forse vuoto), due newline separano le intestazioni dal corpo; il corpo HTTP è costituito da coppie chiave-valore. Per le richieste senza corpo, gli elementi di intestazione, come la stringa di query, possono essere utilizzati per inviare informazioni., Ecco una richiesta aGET la risorsa/novels con l’ID di 2:

GET /novels?id=2

La stringa di query inizia con il punto interrogativo e, in generale, consiste in coppie chiave-valore, sebbene sia possibile una chiave senza valore.

IlHttpServlet, con metodi comegetParameter egetParameterMap, nasconde piacevolmente la distinzione tra richieste HTTP con e senza corpo., Nell’esempio dei romanzi, il metodogetParameter viene utilizzato per estrarre le informazioni richieste dalle richiesteGET,POST eDELETE. (La gestione di una richiestaPUT richiede un codice di livello inferiore perché Tomcat non fornisce una mappa dei parametri praticabile per le richiestePUT., Qui, per esempio, è una fetta di doPost metodo NovelsServlet override:

Per un senza corpo DELETE richiesta, l’approccio è essenzialmente la stessa:

doGet metodo deve distinguere tra due sapori di un GET richiesta: un sapore significa “avere tutto”, mentre gli altri significa ottenere un specificati., Se il GET URL di richiesta contiene una stringa di query, la cui chiave è un ID, quindi la richiesta viene interpretato come “ottenere un determinato uno”:

http://localhost:8080/novels?id=2 ## GET specified

Se non c’è la stringa di query, il GET richiesta viene interpretato come “avere tutto”:

http://localhost:8080/novels ## GET all

Alcuni diabolico dettagli

I romanzi di service design riflette come un web basato su Java server come Tomcat funziona. All’avvio, Tomcat crea un pool di thread da cui vengono disegnati i gestori delle richieste, un approccio noto come un thread per modello di richiesta., Le versioni moderne di Tomcat utilizzano anche I / O non bloccanti per aumentare le prestazioni.

Il servizio romanzi viene eseguito come una singola istanza della classe NovelsServlet, che a sua volta mantiene una singola raccolta di romanzi. Di conseguenza, si verificherebbe una condizione di gara, ad esempio, se queste due richieste fossero elaborate contemporaneamente:

  • Una richiesta modifica la raccolta aggiungendo un nuovo romanzo.
  • L’altra richiesta ottiene tutti i romanzi della collezione.

Il risultato è indeterminato, a seconda esattamente di come le operazioni di lettura e scrittura si sovrappongono., Per evitare questo problema, il servizio romanzi utilizza un thread-safe ConcurrentMap. Le chiavi per questa mappa vengono generate con un AtomicInteger thread-safe. Ecco il segmento di codice pertinente:

public class Novels {
private ConcurrentMap<Integer, Novel> novels;
private AtomicInteger mapKey;
...

Per impostazione predefinita, una risposta a una richiesta client è codificata come XML. Il programma novels utilizza la vecchia classe XMLEncoder per semplicità;un’opzione molto più ricca è la libreria JAX-B., Il codice è semplice:

Object parametro è una ordinati ArrayList di romanzi (in risposta a un “tutti” richiesta); o di un singolo Novel istanza (in risposta a una sola richiesta); o un String (un messaggio di conferma).

Se un’intestazione di richiesta HTTP fa riferimento a JSON come tipo desiderato, l’XML viene convertito in JSON., Qui è il check-in il doGet metodo NovelsServlet:

String accept = request.getHeader("accept"); // "accept" is case insensitive
if (accept != null && accept.contains("json")) json = true;

Novels le case di classe toJson metodo, che consente di convertire XML JSON:

NovelsServlet verifica la presenza di errori di vario tipo. Ad esempio, una richiestaPOST dovrebbe includere un autore e un titolo per il nuovo romanzo., Se uno è mancante, il doPost metodo genera un’eccezione:

if (author == null || title == null)
throw new RuntimeException(Integer.toString(HttpServletResponse.SC_BAD_REQUEST));

SC nel SC_BAD_REQUEST sta per il codice di stato, e il BAD_REQUEST standard HTTP numerico valore di 400. Se il verbo HTTP in una richiesta èTRACE, viene restituito un codice di stato diverso:

Testare il servizio novels

Testare un servizio web con un browser è complicato., Tra i verbi CRUD, i browser moderni generano solo richiestePOST (Create) eGET (Read). Anche una richiestaPOST è impegnativa da un browser, poiché i valori chiave per il corpo devono essere inclusi; in genere viene fatto attraverso un modulo HTML. Un’utilità da riga di comando come curl è un modo migliore per andare, come questa sezione illustra con alcuni comandi curl, che sono inclusi nello ZIP sul mio sito web.,

Ecco alcuni test di esempio senza l’output corrispondente:

% curl localhost:8080/novels/
% curl localhost:8080/novels?id=1
% curl --header "Accept: application/json" localhost:8080/novels/

Il primo comando richiede tutti i romanzi, che sono codificati di default in XML. Il secondo comando richiede il romanzo con un ID di 1, che è codificato in XML. L’ultimo comando aggiunge un elemento di intestazioneAccept conapplication/json come tipo MIME desiderato. Il comandoget one potrebbe anche utilizzare questo elemento di intestazione. Tali richieste hanno JSON piuttosto che le risposte XML.,

I prossimi due comandi di creare un nuovo romanzo della raccolta e confermare l’aggiunta:

% curl --request POST --data "author=Tolstoy&title=War and Peace" localhost:8080/novels/
% curl localhost:8080/novels?id=4

PUT comando curl assomiglia a un POST comando, ad eccezione che il PUT corpo non utilizzare la sintassi standard. La documentazione per il metododoPut nelNovelsServlet entra nel dettaglio, ma la versione breve è che Tomcat non genera una mappa corretta sulle richiestePUT., Ecco il comando di esempio PUT e un comando di conferma:

% curl --request PUT --data "id=3#title=This is an UPDATE" localhost:8080/novels/
% curl localhost:8080/novels?id=3

Il secondo comando conferma l’aggiornamento.

Infine, il comandoDELETE funziona come previsto:

% curl --request DELETE localhost:8080/novels?id=2
% curl localhost:8080/novels/

La richiesta è per il romanzo con l’ID di 2 da eliminare. Il secondo comando mostra i romanzi rimanenti.

Il web.file di configurazione xml

Sebbene sia ufficialmente facoltativo, un file di configurazioneweb.xml è un pilastro in un sito Web o servizio di livello di produzione., Il file di configurazione consente di specificare il routing, la sicurezza e altre funzionalità di un sito o di un servizio indipendentemente dal codice di implementazione. La configurazione per i romanzi servizio gestisce il routing, fornendo un modello di URL per le domande spedite a questo servizio:

servlet-name elemento fornisce un’abbreviazione (novels) per il servlet nome completo della classe (novels.NovelsServlet), e questo nome è utilizzato nel servlet-mapping elemento qui sotto.,

Ricordiamo che un URL di un servizio distribuito è il nome del file WAR subito dopo il numero della porta:

La barra subito dopo il numero della porta inizia l’URI conosciuto come il percorso della risorsa richiesta, in questo caso, i romanzi di servizio; pertanto, il termine novels si verifica dopo il primo singolo slash.

Nel file web.xml, url-pattern è specificato come /*, che significa qualsiasi percorso che inizia con / novels., Supponiamo che Tomcat incontri un URL di richiesta artificiale, come questo:

La configurazione web.xml specifica che anche questa richiesta deve essere inviata al servlet romans perché il modello /* copre /foobar. L’URL inventato ha quindi lo stesso risultato di quello legittimo mostrato sopra di esso.

Un file di configurazione di livello di produzione potrebbe includere informazioni sulla sicurezza, sia a livello di filo che a livello di utenti., Anche in questo caso, il file di configurazione sarebbe solo due o tre volte la dimensione di quello campione.

Avvolgendo

IlHttpServlet è al centro delle tecnologie web di Java. Un sito web o un servizio web, come il servizio romanzi, estende questa classe, sovrascrivendo i verbi di interesse do. Un framework Restful come Jersey(JAX-RS) o Restlet fa essenzialmente lo stesso fornendo un servlet personalizzato, che funge quindi da endpoint HTTP (S) per le richieste contro un’applicazione Web scritta nel framework.,

Un’applicazione basata su servlet ha accesso, ovviamente, a qualsiasi libreria Java richiesta nell’applicazione Web. Se l’applicazione segue il principio di separazione delle preoccupazioni, il codice servlet rimane attraente semplice: il codice controlla una richiesta, emettendo l’errore appropriato se ci sono carenze; altrimenti, il codice richiama qualsiasi funzionalità possa essere richiesta (ad esempio, interrogando un database, codificando una risposta in un formato specificato), e quindi invia la risposta al richiedente., I tipiHttpServletRequest eHttpServletResponse semplificano l’esecuzione del lavoro specifico del servlet di lettura della richiesta e scrittura della risposta.

Java ha API che vanno dal molto semplice al molto complicato. Se hai bisogno di fornire alcuni servizi Restful usando Java, il mio consiglio è di provare prima di qualsiasi altra cosa HttpServlet.