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 classe
Novels
consiste 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 classe
NovelsServlet
deriva 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 classeNovels
.
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
eshutdown.sh
) e Windows (startup.bat
eshutdown.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, immetterein 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) inTOMCAT_HOME/webapps
o una sottodirectory dello stesso. Tomcat quindi decomprime il file WAR nella propria directory. Ad esempio, Tomcat decomprimerebbenovels.war
in una sottodirectory denominatanovels
, lasciandonovels.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
) ofTOMCAT_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 informazioni
request
, 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 un
Novel
) e quindi codificare una risposta appropriata al richiedente utilizzando ilresponse
flusso di output per farlo., Nel caso didoPost
, 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, POST
e PUT
) hanno corpi, mentre altre (in particolare, GET
e 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
.
Lascia un commento