Corso di Laboratorio Applicazioni Internet

Transcript

Corso di Laboratorio Applicazioni Internet
Corso di Laboratorio Applicazioni Internet
Corso di Laboratorio Applicazioni Internet
i
Corso di Laboratorio Applicazioni Internet
Copyright © 2005-2016 Dip. di Informatica - Università di Pisa
ii
Corso di Laboratorio Applicazioni Internet
iii
COLLABORATORI
TITOLO :
Corso di Laboratorio Applicazioni Internet
AZIONE
NOME
DATA
FIRMA
A CURA DI
Tito Flagella e
Lorenzo Nardi
24 febbraio 2016
CRONOLOGIA DELLE REVISIONI
POSIZIONE
DATA
DESCRIZIONE
NOME
Corso di Laboratorio Applicazioni Internet
iv
Indice
1
2
Architetture Applicative
1
1.1
Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
1.2
I Web Services e le Architetture Orientate ai Servizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
1.3
L’evoluzione dell’Infrastruttura su Internet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
1.4
L’infrastruttura software per l’erogazione di Servizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
Il Protocollo HTTP e la programmazione di Servlet
9
2.1
HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
2.1.1
Tools http . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
2.1.2
Richiesta HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.1.3
Risposta HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.1.4
Codifica dei parametri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.1.5
Eseguire query HTTP con JAVA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.2
2.3
3
Web/Application Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.2.1
Introduzione alla programmazione di Servlet. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.2.2
Ciclo di vita di una Servlet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.2.3
Implementare HttpServlet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.2.4
Deploy della Servlet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Sessioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.3.1
Problematiche nella gestione delle sessioni - Caso Alitalia . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.3.2
Sessioni in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Introduzione all’XML
3.1
3.2
3.3
23
XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.1.1
Sintassi XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.1.2
Strutturare i dati . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.1.3
Namespace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.1.4
XML in Java, DOM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.1.5
Java API for XML Processing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.1.5.1
DOM Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.1.5.2
SAX Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.1.5.3
StAX Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
XSD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.2.1
Costruzione di uno Schema XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.2.2
Validazione con JAXP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Applicazioni XML su HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Corso di Laboratorio Applicazioni Internet
4
Web Service
Il Protocollo SOAP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.2
WSDL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.2.1
Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.2.2
Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.2.3
Binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.2.4
Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.2.5
WSDL Validation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Web Service in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.3.1
4.3.2
Approccio centrato su classi Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.3.1.1
Uso di WSDL2Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.3.1.2
Uso di Java2WSDL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Approccio centrato su messaggi SOAP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.3.2.1
Message Level Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.3.2.2
SAAJ Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Sicurezza nelle applicazioni WEB
5.1
La sicurezza nelle Applicazioni Internet
5.1.1
5.2
5.3
5.4
6
34
4.1
4.3
5
v
44
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Setup con Keytool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
WS-Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.2.1
Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.2.2
Signature . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
5.2.3
Encryption . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.2.4
TimeStamp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
WS-Security con CXF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.3.1
Autentication con Username Token . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.3.2
Signature . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
5.3.3
Encryption . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
5.3.4
Timestamp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
5.3.5
Timestamp Signature Encrypt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Le specifiche WS-* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Gestione del Backend
54
6.1
SQL Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
6.2
Le Transazioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
6.3
Livelli di isolamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Corso di Laboratorio Applicazioni Internet
vi
Elenco delle tabelle
1
Header HTTP di richiesta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2
Header HTTP piu frequenti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3
Codifica di caratteri speciali . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4
Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
5
Message Exchange Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Corso di Laboratorio Applicazioni Internet
1
1.1
1 / 57
Architetture Applicative
Introduzione
La rapida diffusione di Internet ha provocato una vera e propria rivoluzione nelle architetture delle applicazioni distribuite,
aumentando significativamente la complessità dei sistemi in gioco.
E’ sicuramente interessante, prima di approfondire gli aspetti architetturali tipici di una applicazione Internet, ripercorrere
rapidamente l’evoluzione delle architetture dei sistemi distribuiti negli ultimi anni.
Inizialmente, quelle che oggi chiamiamo Applicazioni Enterprise (come ad esempio i sistemi di prenotazione, o il software di
gestione degli ATM) non erano realizzati come oggi in maniera distribuita ma erano invece applicazioni monolitiche ospitate da
un Mainframe. L’interazione da pate degli utenti (o meglio degli operatori) avveniva da terminali, tipicamente i 3270 a fosfori
verde sempre più difficili da trovare in circolazione, connessi al Mainframe via cavi coassiali. I terminali non avevano alcuna
capacità di elaborazione locale dei dati e si limitavano a trasmettere al mainframe i dati digitati dall’utente e a visualizzare sul
display i caratteri ricevuti dal mainframe.
A quei tempi la scalabilità di un’applicazione, cioè la sua capacità di incrementare nel tempo la propria capacità di risposta
in funzione delle necessità e delle disponibilità di risorse, era quindi unicamente di tipo verticale, basata cioè sulla capacità di
incrementare la potenza di calcolo del mainframe.
Corso di Laboratorio Applicazioni Internet
2 / 57
Un primo cambiamento radicale si ebbe con la diffusione, avvenuta nel corso degli anni ’80, di due tecnologie ancora oggi
estremamente diffuse: i database relazionali e i personal computer.
I personal computer offrivano sia potenza di calcolo a basso costo che i primi tool per lo sviluppo di interfacce grafiche.
I Data Base Management System (DBMS) d’altro canto offrivano uno strumento affidabile ed a basso costo per la gestione delle
transazioni applicative.
Si diffuse così un nuovo paradigma applicativo, solitamente riferito come client-server, basato proprio sull’uso coordinato di
queste due tecnologie:
• il client, detto anche Fat Client a causa delle sue dimensioni notevoli, include l’interfaccia utente (presentation logic), la logica
applicativa del programma (business logic) e la logica di elaborazione dei dati (data manipulation logic);
• il DBMS si limita inizialmente a gestire la manipolazione dei dati in maniera transazionale.
Il nuovo paradigma si diffonde con una straordinaria rapidità, grazie alla sua caratteristica di sfruttare il parallelismo nel più
semplice dei modi, distribuendo cioè l’esecuzione della maggior parte del carico sui PC degli stessi utenti.
La semplicità dell’architettura client-server, che ne ha costituito il principale punto di forza, mostra però nel tempo alcuni limiti
importanti. In particolare:
• l’uso di un Fat Client richiede un upgrade del client ad ogni modifica dell’applicazione (nuove feature e/o bug-fix);
• se è vero, come abbiamo detto, che il client scala ottimamente sui PC utilizzati al crescere del numero di utenti, la stessa cosa
non succede però per il DBMS, che tende a diventare un collo di bottiglia dell’intera architettura.
Per risolvere questo problema ci si comincia a muovere verso il concetto di quello che oggi viene chiamato Application Server.
Si tratta sostanzialmente di spostare parte della logica applicativa dal client al server. I vendor più importanti si muovono quindi
in due direzioni:
• I produttori di DBMS cominciano ad aggiungere funzionalità applicative nei loro prodotti, nella forma di trigger e stored
procedure.
• I produttori dei TP Monitor, i framework applicativi (come li chiameremmo oggi) che dominavano il mercato Mainframe,
colgono l’opportunità per portare i loro software su sistema Unix, avviando il processo di downsizing da mainframe a un
ambiente etorogeneo, che usa estesamente minicomputer, reti LAN e personal computer.
Corso di Laboratorio Applicazioni Internet
3 / 57
Queste due strade si rilevano però pocò più che un palliativo. I DB server, che già costituivano un collo di bottiglia dell’intera architettura, vedono aumentare il loro carico di lavoro. I TP Monitor, anche in ambiente Unix mantengono significative
controindicazioni in termini di costi e complessità d’uso.
In questa fase intervengono altre due significative novità destinate ad influenzare l’evoluzione delle architetture applicative:
• da una parte si sviluppa un’attenzione crescente verso gli aspetti di portabilità, standardizzazione ed interoperabilità, poi
scaturita nella definizione di Open Systems;
• si afferma decisamente il paradigma di programmazione ad oggetti e, di conseguenza, cominciano ad affermarsi tecnologie per
la distribuzione degli oggetti in rete.
Lo sforzo più significativo per la costruzione di un sistema ad oggetti distribuito si sviluppa nell’ambito dell’OMG (Object
Management Group), un’organizzazione che raggruppa tutti i più importanti player del mercato del software e che arriva a standardizzare l’architettura software CORBA (Common Object Request Broker Architecture), un’architettura ad oggetti distribuita,
definita da OMG, come segue: a vendor-independent architecture and infrastructure that computer applications use to work
together over networks. Using the standard protocol IIOP, a CORBA-based program from any vendor, on almost any computer, operating system, programming language, and network, can interoperate with a CORBA-based program from the same or
another vendor, on almost any other computer, operating system, programming language, and network.
CORBA ha poi effettivamente ottenuto un ampio successo, diventando uno standard di fatto nel mondo Enterprise per la
realizzazione di applicazioni multi-linguaggio e multi-piattaforma.
è in questo contesto che esplode la novità della tecnologia Internet, trainata dal travolgente successo del World Wide Web.
La rete Internet in effetti esisteva già da tempo ma viene scossa dall’invenzione del WWW, un nuovo paradigma progettato
per la visualizzazione di testi ipermediali in rete, e basato sull’uso del protocollo HTTP e dei Browser Web, utilizzati per la
visualizzazione di pagine descritte tramite il linguaggio HTML.
l’architettura WWW prevede infatti l’uso del protocollo http per le comunicazioni tra il Browser ed il Web Server. Il Web Server
serve direttamente al Browser le informazioni statiche, prelevandole dal proprio file system, ma può anche utilizzare il protocollo
CGI per richiedere ad applicazioni esterne di generare dinamicamente le informazioni (tipicamente pagine html ed immagini) da
restituire al browser.
A mano a mano che il Web si diffonde su Internet, e che si comincia a costruire siti web dinamici, ci si rende conto che
questo semplice modello architetturale avrebbe potuto essere utilizzato anche per la realizzazione di vere e proprie applicazioni
distribuite.
Corso di Laboratorio Applicazioni Internet
4 / 57
I primi siti web dinamici utilizzavano infatti il browser non solo per visualizzare le pagine html, ma anche per raccogliere le
preferenze dell’utente, ad esempio per il login o per le selezioni della pagina, tramite l’uso del costrutto FORM del linguaggio
HTML. Il browser si dimostra così adatto a gestire il livello di presentazione, prima occupato dal Fat Client nell’architettura
client server.
Dal punto di vista del backend, invece, questi siti cominciano ad accedere sempre più intensamente a varie fonti informative (data
base ed in generale sistemi legacy), tramite l’uso di applicazioni CGI, per costruire le pagine html da restituire come risposta al
Browser.
Ne deriva un’architettura applicativa nella quale:
• il browser tende a diventare un Client Universale, con la sola funzione di gestire l’interazione con l’utenza;
• il web server si candida ad assumere la funzione di "Application Server", mediando le interazioni tra il browser e gli ulteriori
livelli di backend applicativo (DBMS e sistemi legacy);
• il protocollo http si candida a diventare lo standard di fatto per tutte le comunicazioni applicative veicolate tramite Internet;
• nel backend continuano a vivere sia i DBMS acceduti dalle estensioni Web tramite middleware basati sul linguaggio SQL
(odbc, jdbc, ...), sia le applicazioni legacy vere e proprie, più (CORBA, RMI, DCOM) o meno (RPC, API proprietarie, ...)
recenti.
Questa nuova architettura si afferma rapidamente, identificata inizialmente come Architettura a Tre Livelli (Three Tier Architecture). Successivamente, a mano a mano che si intesifica la disponibilità e l’uso di Servizi Internet, si evolve verso un’architettura
a più livelli (Multi Tier Architecture), schematizzata nella figura successiva, che prevede sia interazioni verso il backend che
verso servizi esterni disponibili anch’essi tramite Internet.
1.2
I Web Services e le Architetture Orientate ai Servizi
La diffusione di Internet si porta dietro tutto un fiorire di nuove tecnologie. Sostanzialmente si fa strada l’idea che se la tecnologia
Internet si è dimostrata adatta a supportare lo straordinario successo del WEB, scalando su milioni di server e di utenti diversi,
può essere una tecnologia di successo anche per le applicazioni distribuite.
Corso di Laboratorio Applicazioni Internet
5 / 57
Nascono quindi tutta una serie di nuove proposte, che ruotano principalmente attorno all’uso di XML per la rappresentazione dei
dati di interscambio e l’uso di http per il livello di trasporto fisico dei pacchetti xml.
Nel loro insieme queste tecnologie vanno oggi sotto il nome di Web Services, e si differenziano in due proposte diverse.
La prima, spesso riferita semplicemente come Web Services è basata sull’uso del protocollo SOAP, di cui parleremo estesamente
più avanti, per l’imbustamento dei contenuti applicativi, e sulla definizione di numerosi e spesso eccessivamente complessi formati di header della busta SOAP, utilizzati per contenere le metainformazioni necessarie a vari servizi di infrastruttura (sicurezza,
indirizzamento, transazionalità, etc.).
La seconda, solitamente riferita come Restful Web Services o semplicemente REST parte invece dall’assunzione che il protocollo
http possa essere usato nativamente per realizzare applicazioni, senza bisogno dell’ulteriore livello di imbustamento SOAP.
REST impone però un insieme di vincoli molto rigidi alla progettazione delle applicazioni ed è stato spesso oggetto di vere e
proprie guerre di religione tra i puristi del paradigma e coloro i quali utilizzavano il termine REST semplicemente per indicare
una qualunque applicazione http che non utilizzasse il protocollo SOAP. Nel post seguente del 2008, Roy Fielding, l’autore della
proposta REST, stigmatizza in maniera abbastanza decisa la sua irritazione per questo tipo di aberrazione dell’architettura REST
e ne ribadisce i principali vincoli.
Di recente si è così diffusa il nuovo termine di Web API o addirittura semplicemente API per indicare una qualunque modalità
di interazione applicativa basata sul protocollo http. La figura che segue mostra il trend di diffusione dei 3 termini di cui sopra
negli ultimi anni.
Corso di Laboratorio Applicazioni Internet
6 / 57
In tutte queste diverse interpretazioni, i Web Services si stanno diffondendo moltissimo come tecnologia per la realizzazione delle
Architetture Service Oriented (SOA), un paradigma di progettazione applicativa basato sul concetto di servizio, considerato da
tempo il paradigma emergente nelle architetture applicative, e che ha finalmente trovato nei Web Services un’adeguata tecnologia
attuativa. Oggi praticamente tutti i principali portali applicativi, forniscono le stesse funzionalità anche tramite API. La figura
che segue mostra alcune tra le API più accedute oggi su Internet.
Corso di Laboratorio Applicazioni Internet
1.3
7 / 57
L’evoluzione dell’Infrastruttura su Internet
Abbiamo visto come si siano diffusi una serie di nuove possibilità per la realizzazione delle interfacce applicative dei servizi,
sostanzialmente basate su protocollo http ed interscambio di dati in formati come XML e JSON.
La forte evoluzione delle applicazioni su Internet ha comportato però esigenze del tutto nuove anche nelle infrastrutture utilizzate
per il rilascio in produzione di tali applicazioni, facilmente intuibili considerando la quantità di utenti potenziali delle applicazioni
e soprattutto di dati su cui tali applicazioni debbono poter operare.
Abbiamo già detto di come i DBMS relazionali, pur essendo un elemento imprescindibile di qualunque architettura applicativa
su Internet, costituissero tuttavia un potenziale collo di bottiglia di tali applicazioni. L’architettura a 3 livelli ha permesso di
scaricare il DB da qualunque logica applicativa, ma lascia il DB come elemento unico di sincronizzazione dei vari nodi del cluster
degli Application Server del secondo livello. E sappiamo che questo può avere un forte impatto sulle prestazioni complessive
dell’applicazione, principalmente a causa delle difficoltà di scalare orizzontalmente tipica degli engine transazionali.
D’altra parte le nuove applicazioni Internet hanno requisiti prestazionali incommensurabilmente maggiori delle applicazioni
tradizionali ed hanno richiesto quindi soluzioni innovative anche per quanto attiene alle tecnologie di gestione dei dati.
Queste problematiche sono state tradizionalmente gestite con varie tecniche di partizionamento dei dati. è possibile in questo
modo replicare lo stesso schema dati su più server DBMS, da utilizzare per la memorizzazione di partizioni diverse dei dati
applicativi (ad esempio partizionando i clienti sulla base della regione di appartenenza ed assegnando ogni partizione).
L’esperienza di YouTube fino al 2008, descritta a questa url (http://highscalability.com/youtube-architecture), costituisce un
interessante esempio di come un semplice partizionamento dei dati sulla base dell’identificativo incrementale degli utenti dell’applicazione abbia permesso di migliorare sensibilmente le prestazioni complessive.
Tecniche di partizionamento dei DBMS sempre più evolute, implementate a livello applicativo utilizzando installazioni multiple
su server diversi dello stesso prodotto, o implementate a livello del DB engine utilizzando versioni distribuite dello stesso DBMS,
hanno permesso una effettiva scalabilità orizzontale della gestione dei dati applicativi.
Tuttavia i dati a cui le applicazioni Internet hanno bisogno di accedere aumentano costantemente: immaginiamo ad esempio
la necessità di Google di indicizzare tutti i dati del web, o la nuova esigenza, sempre più sentita di analizzare i commenti sui
Social Network per interpretare la pubblica opinione sui temi più disparati (Sentiment Analysis). Le prestazioni possibili con le
tradizionali tecniche di indicizzazione e di accesso ai dati sono inadatte per queste nuove esigenze e portano quindi a tecnologie
del tutto nuove.
• Nell’ottobre del 2003 Google annuncia il Google File System (http://research.google.com/archive/gfs.htmlhttp://research.google.com/a
• Nel dicembre del 2004 Google annuncia MapReduce (http://research.google.com/archive/mapreduce.html);
Nell’insieme queste due tecnologie permottono a Google di utilizzare una modalità molto più efficiente di indicizzazione dei
dati Web, a volte sintetizzata con il suggestivo slogan "bringing the code to the data". Si tratta di un’estremizzazione delle
tecniche di partizionamento del dato, con le applicazioni strutturate in due fasi: la fase di "map" che vede un unico processo, in
esecuzione parallela su tutti i nodi del cluster, operare sui dati installati localmente a quel nodo. La fase di "reduce" che prevede
l’aggregazione dei dati elaborati dai vari nodi nella fase di map al fine di produrre i risultati finali del calcolo (tipicamente il
risultato di una query). La significativa novità è costituita dal fatto che nella fase di map, la più complessa dal punto di vista
del calcolo, i dati sono dislocati sugli stessi nodi del cluster (tutti dotati di ampi server locali) dove gira il codice applicativa,
annullando così i significati overhead di trasmissione tra applicazioni e DB engine delle architetture DBMS tradizionali.
Google File System e Map reduce aprono la strada ad Hadoop, una distribuzione software completa costruita per supportare i
nuovi scenari di Big Data analysis, come nel frattempo il marketing ha sapientemente ribattezzato questo tipo di applicazioni. Su
Hadoop si sono concentrati alcuni tra i maggiori investimenti della storia recente dell’informatica, sicuramente gli unici di così
grande dimensione relativi a tecnologia pura e non a servizi di successo. Non ci addrentriamo per ora nella descrizione di questa
complessa architettura, tuttora in rapida evoluzione, ne sentiremo parlare ancora a lungo.
Anche fuori dal mondo hadoop si manifestano nel frattempo le più disparate esigenze di utilizzo di dati molto meno strutturati
di quanto non permettessero le rigide schematizzazioni richieste dai DBMS relazionali (si pensi a dati JSon o XML o anche a
semplici documenti binari). Nasce inoltre l’esigenza di memorizzare una enorme quantità di dati e quindi di essere in grado
di scalare tali dati su cluster di grande dimensione. Queste nuove esigenze portano allo sviluppo di una nuova ed articolata
tipologia di DB engine, nel loro insieme etichettati come Database NOSQL. Il termine, inizialmente derivato dal fatto che tali
DB avevano interfacce di interrogazione proprietarie, esplicitamente progettate per lo specifico tipo di dati da gestire, è stato
Corso di Laboratorio Applicazioni Internet
8 / 57
nel tempo interpretato come "Not Only SQL Database", a mano a mano che questi prodotti hanno cominciato a supportare SQL
come interfaccia di interrogazione. Al contrario di quello che accade per i DBMS tradizionali, queste query sono tipicamente
tradotte in vere e proprie applicazioni distribuite sui vari nodi del cluster che costituisce il DB.
Il motivo dell’efficienza dei DB NOSQL e della loro capacità di scalare ottimamente su cluster di grandi dimensioni è stata tipicamente dovuta (almeno inizialmente) al fatto di aver rilasciato alcuni dei vincoli di consistenza supportati dai DBMS relazionali
e di supportare specifiche tipologie di dati (ad esempio con tecniche di memorizzazione basate su grafi).
Di fronte alla diffusione dei DB NOSQL, non si è fatta attendere la reazione dei principali attori del mercato DB, che hanno
coniato la nuova keywork NEWSQL ad indicare nuove versioni dei propri prodotti relazionali, in grado, almeno sulla carta,
di assicurare le stesse prestazioni e scalarità dei nuovi DB NOSQL ma mantenendo tutte le tradizionali caratteristiche dei loro
prodotti consolidati.
1.4
L’infrastruttura software per l’erogazione di Servizi
Abbiamo visto nelle sezioni precedenti come le Applicazioni Internet si presentino come un insieme di nodi applicativi che
interagiscono tra loro utilizzando la tecnologia dei Web Services.
In questa sezione vogliamo cominciare ad analizzare il dettaglio di un singolo nodo applicativo che, da un punto di vista logico,
può essere schematizzato come nella figura successiva.
Nella figura successiva mostriamo invece come i 3 livelli logici del singolo nodo applicativo vengano effettivamente realizzati in
un reale progetto attuativo.
Ogni livello è a sua volta composto da alcuni componenti software, che interagiscono tra loro come schematizzato nella figura
che segue.
è facile immaginare come la progettazione, la realizzazione ed il tuning di un’applicazione Internet non possano non essere
condizionati dalla complessità dell’ambiente in cui queste stesse saranno poi ospitate e dalle interazioni che dovranno avere con
Corso di Laboratorio Applicazioni Internet
9 / 57
componenti esterni. L’obiettivo di questo corso è proprio quello di introdurre alle problematiche specifiche che un progettista, uno
sviluppatore ed un sistemista applicativo dovranno imparare a fronteggiare, lavorando con questa tipologia ormai estremamente
diffusa di applicazioni.
2
2.1
Il Protocollo HTTP e la programmazione di Servlet
HTTP
HTTP è l’acronimo di HyperText Transfer Protocol (protocollo di trasferimento di un ipertesto). Usato come principale sistema
per la trasmissione di informazioni sul web. è il protocollo standard tramite il quale i server Web rispondono alle richieste
dei client. Il protocollo HTTP è basato su un modello richiesta/risposta, quindi ad ogni messaggio di richiesta è associato un
messaggio di risposta, anche vuoto. Per demistificare l’idea che http sia un protocollo di comunicazione utilizzabile solo dai
browser, è utile fare un po di pratica nell’interazione con i server web, usando strumenti alternativi al browser.
2.1.1
Tools http
Telnet è un protocollo client-server basato su TCP. è possibile utilizzare un programma Telnet per stabilire una connessione
interattiva ad un qualunque servizio su un server internet. La sintassi per stabilire una connessione è
telnet host port
Con telnet possiamo collegarci ad un server http, costruire le nostre richieste HTTP, inviarle e leggere la risposta. Cominciamo
effettuando il collegamento, specificando l’host e la porta (la porta di default per i web server è la 80):
telnet www.google.it 80
Trying 209.85.129.99...
Connected to www.google.it.
Escape character is ’^]’.
Una volta stabilita una connessione al server http possiamo inviare una richiesta e ricevere la relativa risposta:
telnet www.google.it 80
Trying 209.85.135.104...
Connected to mu-in-f104.google.com (209.85.135.104).
Escape character is ’^]’.
GET /index.html HTTP/1.1
Host: www.google.it
Connection: Close
HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Date: Mon, 26 Jan 2009 09:33:56 GMT
Expires: -1
Content-Type: text/html; charset=ISO-8859-1
Set-Cookie: PREF=ID=68f0a6e6797de267:TM=1234361415:LM=1234361415:S=6L8dH9vX0HGcTvJw;
expires=Fri, 11-Feb-2011 14:10:15 GMT; path=/; domain=.google.it
Server: gws
Transfer-Encoding: chunked
Connection: Close
←-
1ad4
<html>[.....omissis......]</html>
0
cURL è un tool per il trasferimento di file su una moltitudine di protocolli di trasporto (http, ftp, ...). La stessa richiesta eseguita
in precedenza può esser replicata con cURL in questo modo:
curl www.google.it/index.html
Corso di Laboratorio Applicazioni Internet
10 / 57
con la possibilità di specificare una serie di opzioni per definizione di header, redirezione dell’output, codifica dei parametri etc...
(http://curl.haxx.se/docs/manual.html)
2.1.2
Richiesta HTTP
Analizziamo come è strutturata una richiesta HTTP
GET /index.html HTTP/1.1
Host: www.google.it
User-agent: Mozilla
Accept: text/html, image/jpeg, image/png
La prima è la linea di richiesta: è composta dal metodo, URI della risorsa e versione del protocollo. Il metodo di richiesta, per la
versione 1.1, può essere uno dei seguenti:
• GET: è usato per ottenere il contenuto della risorsa indicata nell’URI (come può essere il contenuto di una pagina HTML)
• POST: è usato di norma per inviare informazioni al server (ad esempio i dati di un form)
• HEAD: funziona come il metodo GET, ma nella risposta vengono specificati solo gli header e non il corpo del messaggio.
• PUT: questo metodo richiede che il contenuto del messaggio venga memorizzato nella posizione specificata dalla URI
• DELETE: richiede la cancellazione della risorssa specificata nella URI.
• TRACE: fa eseguire l’echo del messaggio
• OPTIONS: richiede al server di fornire informazioni sulle opzioni di comunicazione disponibili per la risorsa specificata.
• CONNECT: indica al proxy di assumere il comportamento di tunnel
l’URI sta per Uniform Resource Identifier ed indica l’oggetto della richiesta (ad esempio la pagina web che si intende ottenere).
I metodi HTTP più comuni sono GET, HEAD e POST. Molte degli altri metodi, anche se definiti nella specifica HTTP 1.1, non
sono implementati dalla maggior parte dei web server.
Le linee successive a quella di richiesta sono gli header http. Gli header sono nella forma:
[nome]:[valore]
Di seguito sono riportati alcuni header di uso comune per il messaggio di richiesta. Per una lista completa rimandiamo alle
specifiche del W3C.
Header
Accept
Descrizione
Mime Type accettati nella riosposta
Authorization
Credenziali per l’autenticazione
Connection
Host
Tipo di connessione che il client
preferisce
Domain name dell’host per il virtual
hosting
Tabella 1: Header HTTP di richiesta
Esempio
Accept: text/plain
Authorization: Basic
QWxhZGRpbcGVuIHNlc2FtZQ==
Connection: Close
Host: www.link.it
Corso di Laboratorio Applicazioni Internet
2.1.3
11 / 57
Risposta HTTP
La richiesta che abbiamo inviato in precedenza ritorna una risposta simile alla seguente:
HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Date: Mon, 26 Jan 2009 09:33:56 GMT
Expires: -1
Content-Type: text/html; charset=ISO-8859-1
Set-Cookie: PREF=ID=68f0a6e6797de267:TM=1234361415:LM=1234361415:S=6L8dH9vX0HGcTvJw;
expires=Fri, 11-Feb-2011 14:10:15 GMT; path=/; domain=.google.it
Server: gws
Transfer-Encoding: chunked
Connection: Close
←-
1ad4
<html>[.....omissis......]</html>
0
La prima linea indica la versione del protocollo HTTP, il codice di stato e la Reason Phrase. Il codice di stato è un numero a tre
cifre classificabile come segue:
• 200~299 Successo
• 300~399 Ridirezione
• 400~499 Errore del Client
• 500~599 Errore del Server
Nel nostro caso la richiesta della pagina di root è stata completata con successo, con un 200 ok. Vediamo alcuni esempi di codice
di stato che un server può inviarvi. Per la lista completa come sempre rimandiamo al sito del W3C.
• 200: OK; operazione completata con successo
• 302: ridirezione a una nuova URL; la URL originale è stata spostata. Non si tratta di un errore, i browser compatibili
cercheranno la nuova pagina.
• 304: usa una copia locale; i browser compatibili mandano una informazione su "lastmodified" della copia della pagina in cache.
Il server può rispondere con il codice 304 invece di mandare di nuovo la pagina in modo che il client utilizzi quella che risiede
in cache.
• 401: non autorizzato. L’utente ha richiesto un documento ma non ha fornito uno username o una password validi.
• 403: Vietato, l’accesso alla URL è vietato.
• 404: Non trovato; il documento non è disponibile sul server.
• 500: Server error; si è verificato un errore interno del server.
A seguire la linea di risposta ci sono gli header (opzionali, come per la richiesta) che forniscono utili informazioni sui dati
contenuti nel body (tipo, lunghezza), sul server che l’ha costruita, sul file richiesto (data di ultima modifica). Come per gli header
di richiesta, segue una lista non esaustiva degli header più comunemente utilizzati: Dopo gli headers c’è una linea vuota a separare
i dati (opzionali, come per la Richiesta). Questi possono essere in qualsiasi formato, anche binario, come specificato nell’header
ContentType. Nel nostro caso, avendo richiesto una pagina HTML, il ContentType è text/html e nel corpo del messaggio vediamo
il codice della pagina.
Corso di Laboratorio Applicazioni Internet
Header
Allow
Content-Lentgth
Content-Type
12 / 57
Descrizione
Metodi di richiesta accettati dal server
Dimensione dei dati in bytes
Mime type del contenuto della
risposta
Date
Data e ora dell’invio della risposta
Exprires
Data di scadenza del documento
Location
Data dell’ultima modifica effettuata
sulla risorsa
Per il redirect
Server
Contiene informazioni sul server
WWW-authenticate
Contiene informazioni per l’accesso in
caso di errore 401
Last-Modified
Esempio
Allow: GET, HEAD
Content-Length: 258
Content-Type: text/html;
Date: Tue, 15 Nov 1994 08:12:31
GMT
Expires: Tue, 15 Nov 1994 08:12:31
GMT
Last-Modified: Tue, 15 Nov 1994
08:12:31 GMT
Location:http://isi.link.it/isi.html
Server:Apache/1.3.29 (Unix)
PHP/4.3.4
WWW-Authenticate: Basic
realm="link-it"
Tabella 2: Header HTTP piu frequenti
2.1.4
Codifica dei parametri
Per effettuare le nostre richieste faremo uso principalmente dei metodi GET o POST. La differenza sostanziale sta nel modo in
cui i dati sono codificati nel messaggio. Per la GET sono incapsulati nella URI di richiesta, mentre per la POST sono inclusi nel
corpo del messaggio.
Si possono compattare più parametri nella query string usando una codifica standard:
• separare i parametri con &
• sostituire i blank con +
• sottoporre ad escape (%xx) i caratteri speciali
www.host.com/page?param1=value1&param2=value2
Per convenzione, il metodo GET dovrebbe essere usato solo per reperire risorse, mentre la POST usata per richieste che
modificano lo stato del server.
Proviamo ad esempio ad eseguire una ricerca su Google con Telnet. La pagina di ricerca è www.google.it/search mentre
il parametro da inviare si chiama q.
telnet www.google.it 80
Trying 209.85.129.99...
Connected to www.google.it.
Escape character is ’^]’.
GET /search?q=ciao HTTP/1.1
Host: www.google.it
Connection: Close
Nel caso di una richiesta con metodo POST, dopo gli header c’è una linea vuota seguita dai dati della richiesta. Nel caso di GET
o HEAD il campo body della richiesta è vuoto non avendo dati da inviare.
Un’altro modo per inserire i parametri è quello delle form html, usato normalmente per la navigazione web. Le Form sono
introdotte dal tag <form>. Oltre a codice html, possono contenere i seguenti tag:
• <input> definisce text entry fields, checkboxes, radio buttons o pushbuttons
• <select> definisce dropdown menus e selection box
Corso di Laboratorio Applicazioni Internet
13 / 57
• <textarea> definisce campi text-entry su più linee
la Form può avere i seguenti attributi:
• action, la URL di destinazione a cui saranno inviati i dati
• method, il metodo HTTP usato per la sottomissione dei dati (get o post)
Vediamo ad esempio il codice di una semplice form che invia un parametro via get:
<form action="http://projects.cli.di.unipi.it/isi/cgi-bin/env.pl" method="GET">
Parametro: <input type="text" name="par">
<br/>
<input type="submit" value="Invia">
</form>
Tramite browser, sarà visualizzato un campo di input testuale ed un bottone.
Il testo inserito nel campo di input verrà adeguatamente codificato e inviato via GET alla pagina http://projects.cli.
di.unipi.it/isi/cgi-bin/env.pl, uno script che ritorna tutte le informazioni utili riguardo la richiesta ricevuta e il
server che lo ospita:
SCRIPT_NAME = /isi/cgi-bin/env.pl
SERVER_NAME = projects.cli.di.unipi.it
HTTP_REFERER = http://localhost:8080/sample/
SERVER_ADMIN = [email protected]
HTTP_ACCEPT_ENCODING = gzip,deflate
HTTP_CONNECTION = keep-alive
REQUEST_METHOD = GET
HTTP_ACCEPT = text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q ←=0.8,image/png,*/*;q=0.5
SCRIPT_FILENAME = /home/projects/isi/cgi-bin/env.pl
SERVER_SOFTWARE = Apache/2.2.3 (Debian) mod_ssl/2.2.3 OpenSSL/0.9.8c
HTTP_ACCEPT_CHARSET = ISO-8859-1,utf-8;q=0.7,*;q=0.7
QUERY_STRING = par=Bed+%26+Breakfast
REMOTE_PORT = 47261
HTTP_USER_AGENT = Mozilla/5.0 (X11; U; Linux i686; it; rv:1.8.1.16) Gecko/20080715 Fedora ←/2.0.0.16-1.fc8 Firefox/2.0.0.16 pango-text
SERVER_PORT = 80
SERVER_SIGNATURE =
Apache/2.2.3 (Debian) mod_ssl/2.2.3 OpenSSL/0.9.8c Server at projects.cli.di.unipi.it Port ←80
HTTP_CACHE_CONTROL = max-age=259200
HTTP_ACCEPT_LANGUAGE = it-it,it;q=0.8,en-us;q=0.5,en;q=0.3
HTTP_COOKIE = SESSa20d042eca1ead7ca16668c646e4e3af=07a100cd31837a1b1588bb2bd08e38a0
REMOTE_ADDR = 212.171.49.34
HTTP_KEEP_ALIVE = 300
SERVER_PROTOCOL = HTTP/1.0
PATH = /usr/local/bin:/usr/bin:/bin
REQUEST_URI = /isi/cgi-bin/env.pl?par=Bed+%26+Breakfast
GATEWAY_INTERFACE = CGI/1.1
SERVER_ADDR = 131.114.120.130
DOCUMENT_ROOT = /home/projects/
HTTP_HOST = projects.cli.di.unipi.it
Corso di Laboratorio Applicazioni Internet
2.1.5
14 / 57
Eseguire query HTTP con JAVA
Abbiamo usato fin qui telnet per effettuare le richieste ai web service, ma vediamo come eseguire le stesse interrogazioni
programmando un semplice client HTTP in java.
Ci sono più classi che consentono di effettuare comunicazioni su protocollo HTTP, aiutando a vario titolo il programmatore la
gestione della specifica nelle operazioni più complesse. Il package che più si avvicina alle nostre esigenze è Commons-HttpClient
(http://hc.apache.org/index.html) del progetto Jakarta Commons.
Implementiamo un client che replica la richiesta fatta in precedenza tramite la form inviando come valore Bed & Breakfast,
stampando richiesta e risposta HTTP. I passi da completare sono i seguenti:
• Costruire la query string.
• Costruire e stampare la richiesta GET.
• Eseguire la richiesta e ricevere la risposta.
• Stampare il messaggio di risposta.
Il primo passo richiede di inserire un parametro nella query string, come abbiamo fatto negli esempi precedenti. La difficoltà sta
nel fatto che nel parametro da passare ci sono caratteri che non possiamo inserire nella query string (il carattere & e gli spazi per
l’esattezza) quindi dobbiamo prima codificari. Vediamo come fare:
String parameter = URLEncoder.encode("Bed & Breakfast");
String queryString = "http://localhost:8080/sample/index.html?par=" + parameter;
Adesso possiamo costruire e stampare la richiesta:
HttpClient httpclient = new DefaultHttpClient();
// Prepariamo la richiesta
HttpGet httpget = new HttpGet(queryString);
// Stampiamone il contenuto
System.out.println(httpget.getRequestLine());
Header[] headers = httpget.getAllHeaders();
for(int i=0; i<headers.length; i++){
System.out.println(headers[i].getName() + ": " + headers[i].getValue());
}
Eseguiamo la richiesta e stampiamo la risposta.
// Eseguiamo la richiesta e prendiamo la risposta
HttpResponse response = httpclient.execute(httpget);
// Stampiamo Status Line e Headers
Corso di Laboratorio Applicazioni Internet
15 / 57
System.out.println(response.getStatusLine());
headers = response.getAllHeaders();
for(int i=0; i<headers.length; i++){
System.out.println(headers[i].getName() + ": " + headers[i].getValue());
}
// Prendiamo il contenuto della risposta
HttpEntity entity = response.getEntity();
InputStream instream = entity.getContent();
String s = null;
BufferedReader reader = new BufferedReader(new InputStreamReader(instream));
while((s = reader.readLine()) != null) System.out.println(s);
instream.close();
Non rimane che gestire eventuali errori sincerandoci di chiudere la connessione se attiva.
2.2
Web/Application Server
Un Web Server implementa il protocollo HTTP lato server. Il suo compito è quello di ricevere le richieste dai vari client. La
porta solitamente utilizzata per questo scopo è la 80. Il browser non richiede che venga specificata e usa quella porta come
default per individuare il server. Qualora la porta del Web Server fosse diversa, allora è necessario specificarla nell’url (es.
http://www.link.it:8080/isi.html) Ricevura la richiesta, il server si occupa di reperire le risorse specificate nella URL di richiesta
secondo il metodo specificato (GET, POST..). A questo punto costruisce un prologo di risposta HTTP contenente informazioni
sullo stato, gli header e i dati relativi alla risorsa richiesta. Completata la risposta la invia al richiedente.
2.2.1
Introduzione alla programmazione di Servlet.
I servlet sono oggetti che operano all’interno di un server per applicazioni (Application Server, per esempio Apache Tomcat) e
potenziano le sue funzionalità.
La parola servlet fa coppia con applet, che si riferisce a piccoli programmi scritti in Java che si eseguono all’interno di un
browser. L’uso più frequente dei servlet è generare pagine web in forma dinamica a seconda dei parametri della richiesta spedita
dal browser. Un servlet può avere molteplici funzionalità e può essere associato ad una o più risorse web.
Un esempio potrebbe essere un meccanismo per il riconoscimento dell’utente. Quando digito un URL del tipo miosito/login,
viene invocato un servlet che verificherà che i dati inseriti siano corretti e in base a questa decisione mi potrà indirizzare in una
pagina di conferma o di errore.
Sotto quest’ottica un servlet è un programma che deve rispettare determinate regole e che processa in un determinato modo una
richiesta HTTP. Nulla vieta che all’interno dello stesso server web possano girare più servlet associati a URL diversi; ognuno di
questi servlet farà cose diverse e estenderà le funzionalità del server web.
La Servlet API (http://java.sun.com/products/servlet/2.2/javadoc/index.html) è un’estensione standard di Java sin dalla versione 1.2 e si tratta di moduli caricati dinamicamente dal server su richiesta. Una servlet è in grado di gestire più richieste
contemporaneamente in modalità thread safe, consentendo a più processi di uilizzare le stesse risorse gestendone la concorrenza.
La diffusione di questa tecnologia garantisce una buona portabilità del codice su un elevato numero di ambienti, aspetto cruciale
quando si parla di applicazioni internet.
Spesso le servlet sono indirizzati tramite il prefisso servet nella URL
http://hostname:port/servet/Servlet.class[?args]
Il prefisso è configurabile nell’application server ed è possibile avere più prefissi sul solito application server.
2.2.2
Ciclo di vita di una Servlet
Una servlet, nella sua forma più generale, è un’istanza di una classe che implementa l’interfaccia javax.servlet.Servlet.
Molte servlet, comunque, estendono una delle implementazioni standard di questa interfaccia. Dovendo gestire richieste HTTP,
useremo ed esamineremo l’implementazione javax.servlet.http.HttpServlet.
Corso di Laboratorio Applicazioni Internet
16 / 57
Quando viene istanziata/deployata una HttpServlet, l’application server (es. Tomcat) chiama il metodo init della Servlet. La
Servlet dovrebbe così effettuare una procedura di startup unica nel suo ciclo vitale. Adesso la Servlet è pronta per ricevere le
richieste, per ognuna delle quali viene invocato il metodo corrispondente al tipo di richiesta (doGet, doPost, doHead...).
Nota
La servlet viene chiamata concorrentemente per gestire più richieste, quindi dovrebbe essere implementato in modo threadsafe. Qualora non fosse possibile gestire la concorrenza dei thread e si rivelasse necessario rendere la Servlet singlethreaded
è sufficiente che implementi l’interfaccia SingleThreadModel
. Quando è necessario effettuare l’unload della servlet (ad esempio perchè è stata rilasciata una nuova versione, o il server deve
essere spento) viene chiamato il metodo destroy.
public class MyServlet extends HttpServlet{
... ...
@Override public void init() throws ServletException{
// Viene eseguito una sola volta
// alla prima chiamata della servlet
}
@Override public void destroy(){
// Viene eseguito una sola volta
// all’unload della servlet
}
... ...
}
2.2.3
Implementare HttpServlet
Adesso che abbiamo chiaro il ciclo di vita di una Servlet, possiamo implementarne una Servlet Http. javax.servlet.
HttpServlet è l’implementazione dell’interfaccia Servlet che dobbiamo estendere facendo l’override dei metodi ereditati. I
metodi della classe HttpServlet ricevono in ingresso gli oggetti HttpServletRequest e HttpServletResponse che forniscono metodi
per reperire o impostare i dati della richiesta e risposta HTTP.
public class MyServlet extends HttpServlet{
... ...
@Override public void doGet(HttpServletRequest req, HttpServletResponse res){
...
}
@Override public void doPost(HttpServletRequest req, HttpServletResponse res){
...
}
@Override public void doPut(HttpServletRequest req, HttpServletResponse res){
...
Corso di Laboratorio Applicazioni Internet
17 / 57
}
@Override public void doDelete(HttpServletRequest req, HttpServletResponse res){
...
}
... ...
}
Se non viene fatto l’override di un metodo, viene usata l’implementazione ereditata che di default risponde al client con un errore
di tipo 501:Not Implemented. Qualora il metodo fosse implementato, ma non ammesso per la risorsa richiesta, il server
deve rispondere con un errore 405:Method not allowed.
Ottenuta una HttpServletRequest è possibile in ogni metodo:
• getParameterNames accede alla lista dei nomi dei parametri
• getParameter accede ai parametri per nome
• getQueryString consente il parsing manuale della QUERY_STRING
Vediamo come implementare una servlet http che raccolga i dati inviati dal client.
import
import
import
public
java.io.*;
javax.servlet.*;
javax.servlet.http.*;
class HelloServlet extends HttpServlet {
@Override public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
String nome = req.getParameter("nome");
System.out.println("Hello, " + parameter);
}
}
2.2.4
Deploy della Servlet
Una volta compilata la servlet dobbiamo "confezionarla" per essere passata all’Application Server. Questa è la stuttura standard
per le applicazioni web compatibili J2EE 1.2:
Per quanto riguarda il nostro esempio, dobbiamo mettere la classe appena compilata che implementa la servlet sotto WEB-INF/
classes e creare il file WEB-INF/web.xml. Vediamone un esempio:
<web-app>
<servlet>
<servlet-name>hello</servlet-name>
Corso di Laboratorio Applicazioni Internet
18 / 57
<servlet-class>HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
L’elemento servlet definisce il nome della servlet da usare per riferirla nel resto del documento e la classe che la implementa.
L’elemento servlet-mapping indica al server a quale URL il servizio deve rispondere.
Completati i file necessari e organizzati secondo le specifiche, copiamo tutto nella cartella webapps di Tomcat per fargli eseguire
il deploy dell’applicazione.
2.3
Sessioni
Da html.it
Una delle funzionalità più richieste per un’applicazione web è mantenere le informazioni di un utente durante tutta la sessione.
Una sessione è una sequenza di transazioni http, legate tra di loro secondo una logica applicativa. Sappiamo che il protocollo
http non ha stato e per il server non è possibile capire se due richieste http sono eseguite dal solito client. Per aggirare questo
problema, il server associa e comunica ad un client un identificativo di riconoscimento che questo userà per le richieste successive,
permettendo al server di riconoscerlo. È possibile quindi creare applicazioni su protocollo http che riconoscono l’utente, che
tengono traccia delle sue scelte e dei suoi dati.
Vediamo come nasce una sessione e come il server riesce ad associare la solita sessione ad un client usando i cookies:
Prima Richiesta
1. L’utente contatta il server per la prima volta.
2. Il server controlla se il client ha fornito un SessionID. Se non lo ha fatto crea una nuova sessione e genera un SessionID
per identificare questa sessione.
3. Il server invia un header Set-Cookie al client
4. Il client salva il cookie per il dominio in questione
Seconda Richiesta
1. L’utente visita il server, stavolta inserendo l’header Cookie contenente il SessionID salvato in precedenza.
2. Il server controlla se il client ha fornito un SessionID.
3. Il server verifica se il SessionID ricevuto corrisponde ad una sessione.
4. Il server localizza i dati di sessione e li rende disponibili all’applicazione
Corso di Laboratorio Applicazioni Internet
19 / 57
Nota
I cookie HTTP sono frammenti di testo inviati da un server ad un Web client e poi rimandati indietro dal client al server - senza
subire modifiche - ogni volta che il client accede allo stesso server. Il server invia l’header Set-Cookie con le variabili ed i
loro valori, seguiti da elementi opzionali, come la data di scadenza, il dominio etc.
Set-Cookie:<name>=<value>[;<name>=<value>]...[;expires=<date>][;domain=
<domain_name>][;path=<some_path>][;secure][;httponly]
Il client risponde con l’header Cookie
Cookie:<name>=<value>[;<name>=<value>]...
• i parametri DOMAIN e PATH individuano le URL per cui un cookie è valido
• il parametro expires specifica la validità temporale del cooky (Es: Wdy, DD-Mon-YYYY HH:MM:SS GMT)
• il parametro secure indica che il Cookie va spedito solo se il canale è sicuro (https)
Se il client non supporta i cookies, possiamo comunque trasmettere l’identificativo di sessione in altri due modi:
• Hidden Field: come parametro nel payload del messaggio HTTP
• Url Rewriting: nella URL di richiesta con la sintassi http://host/resorce;JSESSIONID=idsessione?param=
value&..
Le informazioni di sessione sono quindi memorizzate sul server, mentre lato client viene visto solo un identificativo di sessione.
Nota
Per scoprire se un client supporta o meno l’uso dei cookies, non c’e’ altro modo che eseguire un tentativo di scrittura di
cookie e verificarne successivamente l’invio da parte del client. Poichè questo controllo introduce dei passaggi extra non
desiderati nel processo di accesso e fruizione di una applicazione web con sessioni, spesso si preferisce inviare l’id sessione
contemporaneamente via cookie che via hidden field. Le richieste successive paleseranno quale dei due metodi il client ha
accettato.
2.3.1
Problematiche nella gestione delle sessioni - Caso Alitalia
Quando sviluppiamo una applicazione web dobbiamo sempre ricordare che l’utente non necessariamente segue il percorso che
abbiamo immaginato, ma che possa saltare passaggi, inserire dati errati, concorrere con altri utenti nell’uso di risorse condivise.
Alitalia ci fornisce un esempio di cattiva programmazione nell’uso delle sessioni. Vediamo come replicarlo. Andiamo sulla
pagina delle prenotazioni e facciamo una ricerca, ad esempio per un volo A/R Roma - Milano:
Corso di Laboratorio Applicazioni Internet
In un’altra finestra facciamo un’altra ricerca, ad esempio per un volo A/R Roma - Napoli:
20 / 57
Corso di Laboratorio Applicazioni Internet
Torniamo adesso alla lista dei voli Roma - Milano e acquistiamo il primo.
21 / 57
Corso di Laboratorio Applicazioni Internet
22 / 57
Ci aspettavamo il riepilogo del volo Roma - Milano, ma ci arriva quello di Roma - Napoli. Cosa è successo? Se guardiamo il
sorgente della pagina che mostra i voli disponibili vediamo che ad ogni bottone è associato il numero di indice in tabella. Se ne
desume che in sessione è salvata una corrispondenza che associa al numero di indice l’identificativo del volo. Quando abbiamo
fatto la seconda ricerca quei valori sono stati sovrascritti annullando di fatto la prima ricerca. Il progettista del sito non ha tenuto
conto della possibilità che un utente potesse fare ricerche multiple concorrentemente. Una soluzione banale al problema è di non
riferire un volo con l’indice di tabella, ma con un identificativo univoco.
2.3.2
Sessioni in Java
In Java una sessione HTTP viene rappresentata tramite un oggetto HttpSession. Tramite le sessioni si realizza la persistenza
degli oggetti tra una richiesta HTTP e l’altra. Java tenta di gestire le sessioni tramite cookie. Se questo non è possibile si può
alternativamente passare l’ID di sessione tra i parametri della pagina. Per questo scopo, il metodo encodeURL(String URL)
di HttpServletResponse aggiunge il parametro con l’id di sessione all’URL passata nel caso in cui questo sia necessario.
Tramite il metodo getSession() in HttpServletRequest viene restituita la sessione corrente o ne viene creata una
nuova se questa non esiste.
Corso di Laboratorio Applicazioni Internet
23 / 57
Modifichiamo il servlet creato in precedenza e implementiamo uno storico dei parametri passati.
//Recupero la sessione o la creo se non esiste
HttpSession session = request.getSession();
//Recupero la lista di parametri in sessione o la instanzio se ancora non esiste
String parameters = (String) (session.getAttribute("parameters"));
//Aggiungo il parametro arrivato nella lista di parametri e li stampo a video
parameters += myParameter + "\n";
System.out.println(parameters);
//Inserisco la lista dei parametri aggiornata in sessione.
ses.setAttribute("parameters", parameters);
Lato client dovremo recuperare l’id di sessione e inserirlo nelle richieste successive o tramite header o tramite query string.
//Recupero l’id sessione dagli header della risposta
HttpResponse response = httpclient.execute(httpget);
String cookie = response.getHeader("Set-Cookie");
//Inserisco l’idSessione nelle richieste successive
HttpRequest request = ....
request.setHeader("Cookie", cookie);
In questo modo tutte le volte che effettueremo una richiesta inviando l’id di sessione e un valore ad un parametro, riceveremo
nella risposta tutti i valori inviati fino a quel momento.
3
3.1
Introduzione all’XML
XML
Supponiamo che nel nostro scenario di riferimento il cliente voglia comunicare i dati di un’ordine al rivenditore. Supponiamo per
semplicità che il rivenditore abbia bisogno solo dei modelli dei prodotti scelti, delle quantità e del corriere preferito per gestire
un’ordine. I dati da inviare potrebbero essere questi:
DHL
Playstation
Controller
1
2
I dati presentati in questo modo sono però liberi di essere interpretati poichè privi di valore semantico. Abbiamo bisogno di
un linguaggio che ci permetta specificare che la prima riga indica il corriere scelto per la spedizione, mentre le linee successive
indicano il codice di un prodotto e la quantità richiesta. Per gestire questa problematica su Internet, il W3C ha progettato ad-hoc
il linguaggio XML (eXtensible Markup Language), un meta-linguaggio di markup per la strutturazione dei dati.
La scelta di usare XML per la strutturazione dei dati è dettata da alcune sue importanti qualità
• Auto descrittivo: non richiede file esterni che ne definiscano la semantica perchè quest’informazione è già contenuta al suo
interno
• Semplice: ha una sintassi caratterizzata da poche e semplici regole.
• Estensibile: gli elementi già sintatticamente definiti possono essere estesi e adattati ad altri utilizzi.
• Interoperabile: supportato da una grande varietà di framework e linguaggi di programmazione, garantisce un livello di interoperabilità indispensabile per l’implementazione di servizi web
Corso di Laboratorio Applicazioni Internet
3.1.1
24 / 57
Sintassi XML
La sintassi dell’XML è piuttosto semplice e definita da poche, ma rigide, regole. Vediamole brevemente:
È buona norma cominciare il documento XML specificando la versione e la codifica usata:
<?xml version="1.0" encoding="ISO-8859-1"?>
Tutti gli elementi devono avere il tag di chiusura.
<ordine> ... </ordine>
<ordine />
I tag sono case sensitive.
<ordine> sbagliato </Ordine>
I tag devono essere annidati in maniera corretta.
<ordine><articolo> sbagliato </ordine></articolo>
<ordine><articolo> corretto </articolo></ordine>
Un documento XML deve avere un elemento radice
<ordine>
<articolo>
..
</articolo>
</ordine>
I valori degli attributi devono essere tra apici
<ordine corriere = DHL> sbagliato </ordine>
<ordine corriere = "DHL"> corretto </ordine>
Il carattere minore (<) e l’ampersand (&) sono vietati all’interno degli elementi e devono essere codificati. E’ consigliabile farlo
anche con altri caratteri speciali anche se non strettamente necessario:
&lt;
&gt;
&amp;
&apos;
&quot;
<
>
&
’
"
Tabella 3: Codifica di caratteri speciali
Si possono inserire commenti all’interno di un documento XML
<!-- commento -->
Quando parsiamo un documento XML, anche il contenuto degli elementi viene parsato alla ricerca di nodi interni. Se non
vogliamo che una porzione di XML sia processata la definiamo CDATA (Character DATA) in questo modo:
<![CDATA[ .... ]]>
Corso di Laboratorio Applicazioni Internet
3.1.2
25 / 57
Strutturare i dati
Ora che conosciamo le regole sintattiche dell’XML, vediamo come possiamo strutturare i dati del nostro ordine:
<nome>Playstation</nome>
<quantita>1</quantita>
<nome>Controller</nome>
<quantita>2</quantita>
adesso abbiamo dato un significato a quei dati, ma sono ancora ambigui. Possiamo annidare gli elementi e strutturare ancora
meglio le informazioni. Possiamo specificare che un codice ed una quantità sono per un articolo e che tanti articoli fanno
un’ordine:
<ordine>
<articolo>
<nome>Playstation</nome>
<quantita>1</quantita>
</articolo>
<articolo>
<nome>Controller</nome>
<quantita>2</quantita>
</articolo>
</ordine>
Possiamo anche inserire degli attributi ad un elemento
<ordine corriere="DHL">
<articolo>
<nome>Playstation</nome>
<quantita>1</quantita>
</articolo>
<articolo>
<nome>Controller</nome>
<quantita>2</quantita>
</articolo>
</ordine>
3.1.3
Namespace
In XML i nomi degli elementi sono definiti dallo sviluppatore. Questo può causare dei conflitti, ad esempio quando si lavora su
documenti XML di provenienze diverse. Vediamo un esempio pratico: supponiamo che nell’ordine inseriamo anche il nome del
produttore e del prodotto. Potrebbe verificarsi una situazione di questo tipo:
<ordine>
<nome>Nintendo</nome>
...
<nome>Wii</nome>
...
</ordine>
Come fare a distinguerli? Per questo si definiscono due namespace e, grazie al prefisso, si risale al loro significato semantico
<ordine xmlns:ns1="http://www.shop.org/manufacturers/ns"
xmlns:ns2="http://www.shop.org/product/ns">
<ns1:nome>Nintendo</ns1:nome>
...
<ns2:nome>Wii</ns2:nome>
...
</ordine>
Se non viene specificato un prefisso di un namespace, questo viene preso come default e assegnato a tutti i nodi discendenti privi
di namespace. La definizione di un namespace è visibile nel nodo in cui viene inserita e in tutti i suoi discendenti.
Corso di Laboratorio Applicazioni Internet
3.1.4
26 / 57
XML in Java, DOM
Abbiamo scritto lo schema di un’ordine. Vediamo come modificare il client HTTP affichè lo invii alla servlet.
//Creo il documento XML dell’ordine
String xml = "<ordine corriere="DHL">"+
"<articolo>" +
"<nome>Playstation</nome>" +
"<quantita>1</quantita>" +
"</articolo>" +
"<articolo>" +
"<nome>Controller</nome>" +
"<quantita>2</quantita>" +
"</articolo>" +
"</ordine>" +
"</ordine>";
//Creo il client per effettuare la POST dell’XML
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(servletUrl);
//Creo l’entita’ da inviare settandone il giusto ContentType
StringEntity xmlEntity = new StringEntity(xml);
xmlEntity.setContentType("text/xml");
httppost.setEntity(xmlEntity);
System.out.println("executing request " + httppost.getRequestLine());
HttpResponse response = httpclient.execute(httppost);
System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
3.1.5
Java API for XML Processing
La Java API for XML Processing (JAXP) è una libreria standard processare codice XML. L’attuale versione è la 1.4 inclusa in
Java SE 6.0 ed una sua implementazione è scaricabile all’indirizzo jaxp.dev.java.net.
Sono forniti più modi per effettuare il parsing di un documento XML. Un parser è un programma che effettua la lettura di un
documento XML e lo divide in blocchi discreti. I due classici approcci per processare i documenti XML sono:
Corso di Laboratorio Applicazioni Internet
27 / 57
• Simple API for XML Processing (SAX)
• Document Object Model (DOM)
SAX è un’API di basso livello il cui principale punto di forza è l’efficienza. Quando un documento viene parsato usando SAX,
una serie di eventi vengono generati e passati all’applicazione tramite l’utilizzo di callback handlers che implementano l’handler
delle API SAX. Gli eventi generati sono di livello molto basso e devono essere gestiti dallo sviluppatore che, inoltre, deve
mantenere le informazioni necessarie durante il processo di parsing. Oltre ad un utilizzo piuttosto complicato, SAX soffre di due
limitazioni di rilievo: non può modificare il documento che sta elaborando e può procedere alla lettura solo "in avanti": non può
tornare indietro. Quindi, quello che è stato letto è perso e non è possibile recuperarlo.
DOM, invece, ha come punto di forza la semplicità d’utilizzo. Una volta ricevuto il documento, il parser si occupa di costruire un
albero di oggetti che rappresentano il contenuto e l’organizzazione dei dati contenuti. In questo caso l’albero esiste in memoria
e l’applicazione può attraversarlo e modificarlo in ogni suo punto. Ovviamente il prezzo da pagare è il costo di computazione
iniziale per la costruzione dell’albero ed il costo di memoria.
A questi rappresentanti delle due principali tecniche di rappresentazione dei dati XML si aggiungono altri due parser di recente
concezione:
• Streaming API for XML (StAX)
• Transformation API for XML (TrAX)
StAX è un pull parser. A differenza di SAX, che è un push parser, non riceve passivamente i segnali inviati all’handler per
elaborarli, ma è l’utente a controllare il flusso degli eventi. Questo significa che il client richiede (pull) i dati XML quando
ne ha bisogno e nel momento in cui può gestirli, a differenza del modello push, dove è il parser a inviare i dati non appena li
ha disponibili a prescindere che l’utente ne abbia bisogno o sia in grado di elaborarli. Le librerie pull parsing sono molto può
semplici delle push parsing e questo permette di semplificare il lavoro dei programmatori, anche per documenti molto complessi.
Inoltre è bidirezionale, nel senso che oltre a leggere dati XML è anche in grado di produrli. Rimane il limite di poter procedere
solo "in avanti" nell’elaborazione del documento XML.
TrAX, con l’utilizzo di un XSLT stylesheet, permette di trasformare l’XML. Inoltre, poiché i dati vengono solitamente manipolati
con SAX o DOM, questo parser può ricevere in ingresso sia eventi SAX che documenti DOM. Può anche essere usato per
convertire i dati da un formato all’altro, infatti, è possibile prendere un documento DOM, trasformarlo e scriverlo su file, oppure
prendere l’XML da file, trasformarlo e restituirlo in forma di documento DOM.
La tabella seguente riassume brevemente le caratteristiche principali dei parser presentati: Supponiamo di avere un documento
Feature
Tipo di API
Facilità d’uso
Efficenza CPU e
Memoria
Solo "in avanti"
Legge XML
Scrive XML
CRUD (Create Read
Update Delete)
StAX
Pull, streaming
Alta
SAX
Push, streaming
Media
DOM
In memory tree
Alta
TrAX
XSLT Rule
Media
Buona
Buona
Dipende
Dipende
Si
Si
Si
Si
Si
No
No
Si
Si
No
Si
Si
No
No
Si
Si
Tabella 4: Parser
XML contenente le informazioni di una biblioteca. Non sappiamo con esattezza la struttura del documento, ma sappiamo che
il titolo dei libri è contenuto in un elemento di come titolo. Vediamo come parsare il documento con gli strumenti offerti da
JAXP e stampare i titoli dei libri.
Corso di Laboratorio Applicazioni Internet
3.1.5.1
DOM Parser
Vediamo come ottenere un documento DOM partendo dall’XML
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
...
...
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse( new File("/tmp/mysource.xml") );
NodeList nodes = document.getElementsByTagName("title");
while(int i = 0; i < nodes.length(); i ++) {
Element titleElem = (Element)nodes.item(i);
Node childNode = titleElem.getFirstChild();
if (childNode instanceof Text) {
System.out.println("Book title is: " + childNode.getNodeValue());
}
}
3.1.5.2
SAX Parser
Vediamo come effettuare il parsing con SAX
SAXParser saxParser = new SAXParser();
MyContentHandler myHandler = new MyContentHandler();
saxParser.setContentHandler(myHandler);
saxParser.parse(new File("/tmp/mysource.xml"));
Questa è l’implementazione del nostro handler:
28 / 57
Corso di Laboratorio Applicazioni Internet
29 / 57
public class MyContentHandler extends DefaultHandler {
public void startElement(String uri, String localName, String qName, Attributes atts) {
if (localName.equals("title"))
isTitle = true;
}
public void endElement(String uri, String localName, String qName) {
if(localName.equals("title"))
isTitle = false;
}
public void characters(char[ ] chars, int start, int length) {
if(isTitle)
System.out.println(new String(chars, start, length));
}
}
3.1.5.3
StAX Parser
Vediamo come fare il parsing di un documento xml con StAX
XMLInputFactory fac = XMLInputFactory.newInstance();
XMLEventReader eventReader = fac.createXMLEventReader(new FileInputStream("/tmp/mysource. ←xml"));
while(eventReader.hasNext()) {
XMLEvent event = eventReader.next();
if (event instanceof StartElement && ((StartElement)event).getLocalName().equals(" ←title")) {
System.out.println( ((Characters)eventReader.next()).getData());
}
}
3.2
XSD
Abbiamo appena visto come strutturare i dati e dar loro un significato grazie all’uso dell’XML, adesso abbiamo bisogno di uno
strumento che ci permetta di descrivere tale struttura. Questo ci consentirebbe di validarne un messaggio XML, ovvero controllare
che la sua struttura rispetti un determinato schema. Per definire uno schema si utilizza l’XSD (XML Schema Definition), un
linguaggio bassato su XML.
3.2.1
Costruzione di uno Schema XML
Vediamo come si costruisce lo schema di un documento XML.
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.shop.com/ns"
elementFormDefault="qualified">
...
Corso di Laboratorio Applicazioni Internet
30 / 57
</xs:schema>
Il nodo radice deve essere uno schema definito nel namespace http://www.w3.org/2001/XMLSchema. Con il frammento
targetNamespace="http://www.shop.com/ns"
si indica che gli elementi che si andranno a definire avranno quel namespace, mentre con
elementFormDefault="qualified"
indichiamo che tutti gli elementi definiti saranno qualificati (ovvero avranno un namespace), non solo quelli globali.
A questo punto inseriamo gli elementi. Gli elementi si suddividono in semplici e complessi.
Un elemento semplice è un elemento XML che può contenere solo testo. Non può contenere nessun altro elemento o attributo. Il
testo contenuto può comunque avere tipi differenti, quindi essere uno dei tipi inclusi nella definizione di XML Schema (boolean,
string, date, etc.) oppure un tipo definito da noi. Inoltre possiamo imporre restrizioni al tipo di dato inserito per limitarne il
contenuto.
Vediamo ad esempio che codice e quantita sono elementi semplici, dal momento che contengono solamente testo. La
sintassi per definire un elemento semplice è la seguente:
<xs:element name="xxx" type="yyy"/>
dove xxx è il nome dell’elemento e yyy il suo tipo. l’XML Schema ha molti tipi di dato predefiniti. I più comuni sono:
• xs:string
• xs:decimal
• xs:integer
• xs:boolean
• xs:date
• xs:time
Possiamo quindi specificare i due elementi in questo modo:
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.shop.com/ns"
elementFormDefault="qualified">
<xs:element name="quantita" type="xs:integer"/>
<xs:element name="nome" type="xs:string"/>
</xs:schema>
Supponiamo di voler limitare il nome dell’articolo ad una stringa di 7 caratteri alfanumerici maiuscoli, privi di spazi. Per far
questo definiamo un nuovo tipo imponendo una restrizione sul tipo base string:
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.shop.com/ns"
elementFormDefault="qualified">
<xs:element name="quantita" type="xs:positiveInteger"/>
<xs:element name="nome" type="nomeType"/>
<xs:simpleType name="nomeType">
<xs:restriction base="xs:string">
Corso di Laboratorio Applicazioni Internet
31 / 57
<xs:pattern value="[A-Z0-9]{7}"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
Definiamo a questo punto l’elemento articolo, costituito dai due elementi semplici descritti in precedenza. Non essendo solo
testo, non è un elemento semplice, ma complesso. I tipi complessi sono elementi con attributi o costituiti da altri elementi.
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.shop.com/ns"
elementFormDefault="qualified">
<xs:element name="articolo" type="articoloType"/>
<xs:complexType name="articoloType">
<xs:sequence>
<xs:element name="nome" type="nomeType"/>
<xs:element name="quantita" type="xs:integer"/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="nomeType">
<xs:restriction base="xs:string">
<xs:pattern value="[A-Z0-9]{7}"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
Quando definiamo un nuovo tipo complesso, alcuni degli elementi utilizzabili sono:
• sequence: richiede che gli elementi compaiono nell’ordine specificato
• group: un raggruppamento di altri tipi da utilizzare per un complexType
• all: ammette che elementi compaiano (o non compaiano) in qualsiasi ordine
• choice: ammette uno e uno solo degli elementi contenuti
Concludiamo il nostro schema definendo l’elemento ordine come sequenza di molti elementi articolo. Utilizzeremo nuovamente
l’elemento xs:sequence specificando questa volta il numero di occorrenze (di default impostate a 1)
Per specificare un attributo nell’elemento ordine basta inserire il seguente elemento nella sua definizione
<xsd:attribute name="corriere" type="xsd:string" />
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.shop.com/ns"
elementFormDefault="qualified">
<xs:element name="ordine" type="ordineType"/>
<xs:complexType name="ordineType">
<xs:sequence maxOccurs="unbounded">
<xs:element name="articolo" type="articoloType"/>
</xs:sequence>
<xsd:attribute name="corriere" type="xsd:string" />
Corso di Laboratorio Applicazioni Internet
32 / 57
</xs:complexType>
<xs:complexType name="articoloType">
<xs:sequence>
<xs:element name="nome" type="nomeType"/>
<xs:element name="quantita" type="xs:positiveInteger"/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="nomeType">
<xs:restriction base="xs:string">
<xs:pattern value="[A-Z0-9]{7}"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
A questo punto abbiamo definito l’XSD e possiamo validare il contenuto del documento XML. Uno strumento di validazione
online è reperibile all’indirizzo http://www.xmlforasp.net/SchemaValidator.aspx grazie ad esso possiamo controllare se un documento XML rispetta lo schema definito in un documento XSD.
3.2.2
Validazione con JAXP
JAXP, oltre ad effettuare il parsing e la costruzione dell’albero DOM, fornisce gli strumenti per validare i documenti XML
rispetto ad uno o più XML Schema.
Per essere notificati di eventuali errori di validazione devono essere rispettati questi vincoli:
• La Factory deve essere configurata e settato l’handler dell’errore
• Al documento deve essere associato almento uno schema
Vediamo come gestire la validazione con JAXP. Per prima cosa configuriamo il parser
static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/ ←schemaLanguage";
static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance()
factory.setNamespaceAware(true);
factory.setValidating(true);
try {
factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
}
catch (IllegalArgumentException x) {
...
}
Poi settiamo l’XML Schema. Questo può esser fatto o dichiarandolo nel documento XML stesso
<documentRoot
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation=’YourSchemaDefinition.xsd’>
Oppure specificandolo nella factory
Corso di Laboratorio Applicazioni Internet
33 / 57
static final String schemaSource = "YourSchemaDefinition.xsd";
static final String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/ ←schemaSource";
...
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance()
...
factory.setAttribute(JAXP_SCHEMA_SOURCE, new File(schemaSource));
3.3
Applicazioni XML su HTTP
Abbiamo adesso un client che invia ad una servlet il codice XML di un’ordine. Implementiamo la servlet in modo che lo validi
e ne legga il contenuto.
try{
// Imposto la factory per la validazione
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setIgnoringElementContentWhitespace(true);
factory.setNamespaceAware(true);
factory.setValidating(true);
factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
factory.setAttribute(JAXP_SCHEMA_SOURCE, new File(xsdSchema));
// Prendo il parser, valido l’xml e ne ottengo il DOM
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setErrorHandler(new MyErrorHandler());
Document request = builder.parse( req.getInputStream() );
NodeList articoli = request.getElementsByTagName("articolo");
for(int i = 0; i < articoli.getLength(); i++){
Node articolo = articoli.item(i);
NodeList articolo_children = articolo.getChildNodes();
// Cerco e stampo nome e quantita’ degli articoli
for(int h = 0; h < articolo_children.getLength(); h++){
Node articolo_child = articolo_children.item(h);
if(articolo_child.getLocalName().compareToIgnoreCase("nome") == 0)
System.out.print(articolo_child.getTextContent());
if(articolo_child.getLocalName().compareToIgnoreCase("quantita") == 0)
System.out.println(articolo_child.getTextContent());
}
}
}
Infine rispondiamo al Client con un messaggio che confermi la ricezione dell’ordine.
try{
// Imposto il Content-Type
res.setContentType("text/xml");
// Scrivo il corpo del messaggio
String xml = "<esito>OK<esito>";
res.getWriter().write(xml);
}
catch(IOException e){
e.printStackTrace();
Corso di Laboratorio Applicazioni Internet
34 / 57
res.setStatus(500);
}
4
Web Service
Abbiamo visto finora come http (trasporto) e xml (data representation) possano essere intuitivamente usati per realizzare applicazioni web. Queste semplici modalità applicative possono essere utilizzate per sviluppare applicazioni ad hoc, ma non sono
sufficienti ad indirizzare gli aspetti di interoperabilità tra applicazioni sviluppate indipendentemente.
È questo l’obiettivo dei Web Services, un insieme di architetture e specifiche condivise finalizzato a risolvere i problemi di
interoperabilità nella cooperazione applicativa.
4.1
Il Protocollo SOAP
Abbiamo presentato nella parte introduttiva del corso che esistono più linguaggi e protocolli utilizzabili per la realizzazione di
applicazioni di tipo web services. In questo corso faremo riferimento al linguaggio XML ed al protocollo SOAP, ma tenendo ben
presente che la maggior parte dei concetti sono completamente interscambiabili passando ad altre tecnologie come il linguaggio
JSon ed il protocollo REST.
SOAP (Simple Object Access Protocol) è un protocollo basato su XML per lo scambio d’informazioni in un ambiente distribuito
e definisce un formato comune per trasmettere dati tra client e service. SOAP prevede l’imbustamento dei contenuti applicativi
da scambiare all’interno di un formato di busta XML ed e’ indipendente dal protocollo di trasporto utilizato per la consegna dei
messaggi, che teoricamente potrebbe anche avvenire off-line.
Il namespace degli elementi SOAP è http://www.w3.org/2001/12/soap-envelope.
L’elemento base di un messaggio SOAP è il SOAP Envelope.
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2001/12/soap-envelope">
...
</soap:Envelope>
L’Envelope ha due figli: l’Header, opzionale, e il Body, obbligatorio.
L’elemento opzionale SOAP Header estende il messaggio e contiene metadati, informazioni utili al processamento del messaggio, come ad esempio l’identità dell’utente, informazioni riguardo la cifratura del documento, o informazioni per il routing del
messaggio.
<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="http://www.w3.org/2001/12/soap-envelope">
Corso di Laboratorio Applicazioni Internet
35 / 57
<soap:Header>
<myHeader soap:actor="..." soap:mustUnderstand="...">
...
</myHeader>
</soap:Header>
<soap:Body ...>
...
</soap:Body>
</soap:Envelope>
Un elemento dell’Header può avere alcuni attributi speciali:
• MustUnderstand: Talvolta alcuni header devono essere processati affinchè l’applicazione possa procedere oltre. Si consideri
ad esempio l’header contenente le informazioni di cifratura del messaggio. Se non viene processato (e quindi il messaggio
rimane cifrato) il processamento non può andare avanti. Per indicare che un header DEVE essere analizzato o il processamento
interrotto si usa l’attibuto MustUnderstand settandolo a "1" (di default è "0").
• Actor: Un messaggio SOAP può viaggiare dal mittente al destinatario attraversando differenti endpont lungo il percorso. Non
tutti gli header del messaggio sono necessariamente indirizzati al destinatario finale, ma anche a nodi intermedi. L’attributo
opzionale actor serve appunto a specificare l’endpoint al quale è indirizzato l’elemento.
Il SOAP Body, obbligatorio, infine contiene i dati veri e propri del messaggio, chiamato Payload.
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2001/12/soap-envelope">
<soap:Body xmlns="http://www.bank.org/ns">
<pagamento>
<da>cliente</da>
<a>shop</a>
<importo>100</importo>
</pagamento>
</soap:Body>
</soap:Envelope>
Il livello di messaggio è indipendente dal livello di trasporto, quindi la busta SOAP può essere impacchettata per essere inviata
via HTTP, SMTP, etc. , ma la struttura e il contenuto della busta SOAP rimarrà immutato.
4.2
WSDL
Finora abbiamo visto come modellare (XSD) i messaggi scambiati dalle applicazioni, ma gli aspetti quali l’indirizzo fisico del
servizio, le operazioni supportate, tipologia dei messaggi per ogni operazione risultano ancora cablati nelle applicazioni. Il Web
Service Definition Language (WSDL) è un linguaggio basato su XML per specificare tali aspetti dei servizi. Mediante WSDL
può essere quindi descritta l’interfaccia pubblica di un Web Service fornendo le informazioni necessarie per poter interagire con
un determinato servizio: un "documento" WSDL contiene infatti, relativamente al Web Service descritto, informazioni su:
• cosa può essere utilizzato (le "operazioni" messe a disposizione dal servizio);
• come utilizzarlo (il protocollo di comunicazione da utilizzare per accedere al servizio, il formato dei messaggi accettati in input
e restituiti in output dal servizio ed i dati correlati) ovvero i "vincoli" (bindings in inglese) del servizio;
• dove utilizzare il servizio (cosiddetto endpoint del servizio che solitamente corrisponde all’indirizzo - in formato URI - che
rende disponibile il Web Service)
Il WSDL puo’ essere suddiviso tra definizione logica e concreta. La prima descrive le interfacce, le operazioni ed i messaggi,
mentre la seconda definisce il trasporto, il binding e gli endpoint. La struttura principale del WSDL apparirà grossomodo così
Corso di Laboratorio Applicazioni Internet
36 / 57
<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
<!-- abstract definitions -->
<wsdl:types>
....
</wsdl:types>
<wsdl:message>
....
</wsdl:message>
<wsdl:portType>
....
</wsdl:portType>
<!-- concrete definitions -->
<wsdl:binding>
....
</wsdl:binding>
<wsdl:service>
....
</wsdl:service>
</wsdl:definitions>
Vediamo come costruire un WSDL che descriva il comportamento della servlet che abbiamo implementato. Per adesso possiamo
dire che il nostro servizio espone un’operazione che riceve i dati di un’ordine come messaggio XML/SOAP su HTTP, senza
fornire risposta SOAP al client.
4.2.1
Types
Il primo passo per la costruzione di un documento wsdl di un Web Service è quello di definirsi i tipi degli elementi in esso
contenuti. Abbiamo già definto lo schema XSD di messaggio di ordine:
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="ordineType">
<xs:sequence maxOccurs="unbounded">
<xs:element name="articolo" type="articoloType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="articoloType">
<xs:sequence>
<xs:element name="nome" type="nomeType"/>
<xs:element name="quantita" type="integer"/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="nomeType">
<xs:restriction base="xs:string">
<xs:pattern value="[A-Za-z]{10}"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
Corso di Laboratorio Applicazioni Internet
37 / 57
Importiamolo e aggiungiamo i tipi mancanti per l’operazione di notifica
Nota
<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:ord="http://www.rivenditore.org/ordine"
targetNamespace="http://www.rivenditore.org/rivenditore"
<wsdl:import namespace="http://www.rivenditore.org/ordine" location="submitOrdine.xsd ←" />
<wsdl:types>
<xsd:schema targetNamespace="http://www.rivenditore.org/ordine"
<xsd:element name="ordine" type="ordineType"/>
</xsd:schema>
</wsdl:types>
...
</wsdl:definitions>
4.2.2
Messages
Descritti i tipi, possiamo definire i messaggi scambiati. Il nostro servizio ha un solo messaggio SOAP scambiato, quello di
consegna dell’ordine.
<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.rivenditore.org/ordine" ...>
<wsdl:types> ... </wsdl:types>
<wsdl:message name="ordineMessage">
<wsdl:part name="ordine" element="ele:ordine"/>
</wsdl:message>
</wsdl:definitions>
Il passo successivo sarà definire le interfacce. L’elemento portType definisce un gruppo di operazioni. Il nome portType
è sviante ci riferiremo a questo elemento spesso con il nome "interfaccia". Ogni wsdl:operation dell’interfaccia contiene
una combinazione di elementi wsdl:input e wsdl:output e opzionalmente un wsdl:fault. L’ordine di questi elementi
definisce il Message Exchange Pattern (MEP) dell’operazione.
MEP
Request-Response
OneWay
Solicit-Response
Notification
wsdl:operation
<wsdl:input ..> <wsdl:output ..>
<wsdl:input ..>
<wsdl:output ..> <wsdl:input ..>
<wsdl:output ..>
Tabella 5: Message Exchange Pattern
Corso di Laboratorio Applicazioni Internet
38 / 57
avvertimento
La specifica WS-Interoperability fornisce una serie di line guida per garantire una maggiore interoperabilità tra diverse
piattaforme, sistemi operativi e linguaggi di programmazione. Una di queste regole dice di non utilizzare i MEP SolicitResponse e Notification.
Come detto in precedenza, l’operazione esposta dal nostro servizio prevede di ricevere un messaggio contenente un ordine, ma
di non inviare messaggi SOAP di risposta, quindi un MEP di tipo Oneway. Vediamo come descrivere tale operazione.
<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.rivenditore.org/ordine" ...>
<wsdl:types> ... </wsdl:types>
<wsdl:message> ... </message>
<wsdl:portType name="ordineInterface">
<wsdl:operation name="ordine">
<wsdl:input message="ord:ordineMessage"/>
</wsdl:operation>
</wsdl:portType>
</wsdl:definitions>
L’interfaccia è sempre considerata astratta da momento che non sa come saranno rappresentati i messaggi "sul cavo" finchè non
sarà definito il binding che specifica, tra le altre cose, il protocollo di trasporto (nel nostro caso HTTP).
4.2.3
Binding
L’elemento wsdl:binding descrive i dettagli concreti per utilizzare una particolare interfaccia (portType) con uno specifico
protocollo. La struttura di base dell’elemento binding è la seguente:
<wsdl:definitions .... >
<wsdl:binding name=".." type="..">
<wsdl:operation name="..">
<wsdl:input name=".." >
..
</wsdl:input>
<wsdl:output name=".." >
..
</wsdl:output>
<wsdl:fault name="..">
..
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>
</wsdl:definitions>
L’attributo name, come per gli altri elementi, specifica un nome per riferirsi al binding nel resto del documento. Il type deve
specificare un portType precedentemente definito. Nel nostro esempio possiamo inserire un binding chiamato ordineInte
rfaceBinding per l’interfaccia ordineInterface:
Corso di Laboratorio Applicazioni Internet
39 / 57
<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.rivenditore.org/ordine" ...>
...
<wsdl:binding name="ordineInterfaceBinding" type="ordineInterface">
...
<wsdl:operation name="ordine">
...
<wsdl:input>
...
</wsdl:input>
</wsdl:operation>
</wsdl:binding>
...
</wsdl:definitions>
L’elemento wsdl:binding è generico. Definisce solamente il framework per descrivere i dettaggli di binding. Questi dettagli
sono forniti utilizzando degli elementi estensivi. Le specifiche WSDL forniscono alcuni elementi predifiniti per descrivere il binding SOAP, anche se sono in un diverso namespace (http://schemas.xmlsoap.org/wsdl/soap/). Vediamo come fare un SOAP/HTTP
binding per la nostra interfaccia:
<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.rivenditore.org/ordine" ...>
...
<wsdl:binding name="ordineInterfaceBinding" type="ordineInterface">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="ordine">
<soap:operation soapAction=""/>
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
</wsdl:operation>
</wsdl:binding>
...
</wsdl:definitions>
l’elemento soap:binding indica lo stile usato per la rappresentazione del messaggio (i valori possibili sono document o
rpc) rispetto al protocollo di trasporto richiesto (HTTP nel nostro caso). L’elemento soap:operation definisce il valore dell’header HTTP SOAPAction. Infine l’elemento soap:body descrive come i part del messaggio devono essere serializzati
all’interno del messaggio (valori possibili literal o encoded).
Maggiori informazioni su queste caratteristiche sono presentate nella lezione sui web services.
4.2.4
Services
L’elemento wsdl:service definisce una collezione di porte, o endpoint, che espone un particolare binding:
<wsdl:definitions .... >
<wsdl:service name="...">
<wsdl:port name="..." binding="...">
...
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Corso di Laboratorio Applicazioni Internet
40 / 57
Per ogni porta dobbiamo fornire un nome da utilizzare come riferimento ed un binding tra quelli definiti in precedenza. Poi
possiamo aggiungere elementi estensivi che definiscono i dettagli per l’indirizzamento specifici del binding.
<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.rivenditore.org/ordine" ...>
...
<wsdl:service name="ordineService">
<wsdl:port name="ordineInterfaceEndpoint" binding="ord:ordineInterfaceBinding">
<soap:address location="http://www.rivenditore.org/ordine"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
4.2.5
WSDL Validation
Una volta completato il wsdl possiamo verificarne la correttezza sintattica e l’aderenza alle specifiche WS-I. Ci sono vari tools utilizzabili a questo scopo, come quello distribuito con CXF, il Web Services Framework parte del progetto Apache, che utilizzeremo
come base per le nostre attività di esercitazione. Per eseguire la validazione con CXF basta invocare il comando:
wsdlvalidator service.wsdl
4.3
Web Service in Java
Esistono diversi approcci per implementare un Web Service, anche in funzione degli specifici framework e linguaggi utilizzati.
La principale differenza comunque risiede nella scelta di scrivere la logica applicativa dei propri servizi (lato client e server) in
maniera trasparente rispetto al linguaggio di programmazione utilizzato, ad esempio invocando ed implementando direttamente
metodi delle classi applicative, o piuttosto con una visibilità esplicita dei messaggi scambiati, utilizzando quindi approsite API
per la gestione dei messaggi SOAP, e parser xml per la gestione dei contenuti applicativi dei messaggi.
Nel seguito di questa sezione vedremo come usare questi due approcci usando il linguaggio Java.
4.3.1
Approccio centrato su classi Java
Ci sono due possibilità per scrivere applicazioni di questo tipo:
• si scrive il servizio come una classe Java, si usa JAX-WS per l’annotazione della classe e si genera poi il WSDL del servizio,
tramite appositi tool (es: Java2WSDL in CXF);
• nel caso in cui sia già disponibile il WSDL del servizio, si può invece generare le classi java da usare per la sua implementazione: gli "stub" per il lato client e gli "skeleton" sul lato server.
In entrambi questi casi, è quindi possibile usare i Web Services in maniera trasparente dal linguaggio di programmazione, in
modo tale che per ogni operazione supportata dal servizio sia semplicemente necessario invocare un metodo java sul lato client
e implementare un metodo java sul lato server.
4.3.1.1
Uso di WSDL2Java
Il JAX-WS fornisce un mapping completo dalla definizione dei Web Service in WSDL alle classi Java che implementano quel
servizio. L’interfaccia logica, definita dall’elemento wsdl:portType, e’ mappata in un service endpoint interface (SEI).
Ogni tipo complesso definito in un WSDL viene mappato in classi Java che seguono il mapping definito dalla specifica Java
Architecture for XML Binding (JAX-B). L’endpoint definito dall’elemento wsdl:service viene generato in una classe Java
usata dai fruitori per accedere agli endpoint che implementano il servizio.
Corso di Laboratorio Applicazioni Internet
41 / 57
Il tool wsdl2java automatizza la generazione di questo codice. Inoltre fornisce opzioni per la generazione del codice di partenza
per l’implementazione del servizio ed altre funzionalita’.
E’ possibile generare il codice necessario allo sviluppo del servizio con il seguente comando
wsdl2java -ant -impl -server -d outputDir myService.wsdl
Le opzioni hanno le seguenti funzioni:
• L’argomento -ant genera un makefile per Ant, chiamato build.xml per eseguire e compilare l’applicazione
• L’argomento -impl genera una classe che implementa ogni portType specificato dal WSDL
• L’opzione -server genera un semplice main() per l’esecuzione del servizio come applicazione stand alone
• L’opzione -d specifica la directory dove posizionare il codice generato
• myService.wsdl e’ il WSDL del servizio da implementare
Una volta eseguito il comando ci troveremo una serie di classi generate:
• portTypeName.java il SEI. Questo file contiene l’interfaccia che il servizio implementa.
• serviceName.java l’Endpoint. Questa classe verra’ usata dai fruitori per effettuare richieste al servizio.
• portTypeNameImpl.java lo Skeleton. Qeusta classe e’ l’implementazione del servizio.
• portTypeName_portTypeNameImplPort_Server.java un semplice main() che consente il deploy del servizio in un server stand
alone.
4.3.1.2
Uso di Java2WSDL
L’altro approccio per sviluppare Web Service e’ quello che prevede lo sviluppo del codice Java annotato in standard Jax-WS e di
li generarne il WSDL. Vediamo un esempio. Cominciamo dal SEI:
public interface QuoteReporter{
public Quote getQuote(String ticker);
}
ed un esempio di implementazione:
import java.util.*;
public class StockQuoteReporter implements QuoteReporter
{
...
public Quote getQuote(String ticker)
{
Quote retVal = new Quote();
retVal.setID(ticker);
retVal.setVal(Board.check(ticker));[1]
Date retDate = new Date();
retVal.setTime(retDate.toString());
return(retVal);
}
}
Non rimane che annotare il codice con le annotazioni fornite dallo standard JAX-WS e fornire le seguienti informazioni
• Il target namespace del servizio
• La classe che racchiude il messaggio di richiesta
Corso di Laboratorio Applicazioni Internet
42 / 57
• La classe che racchiude il messaggio di risposta
• Se l’operazione e’ di tipo Oneway
• Il tipo di stile di Binding
• Il nome della classe usata per le eccezioni
• Il namespace dei tipi usati dal servizio
Nota
Molte annotazioni hanno valori predefiniti, ma specificare completamente ogni dettaglio del servizio fornisce un maggior
controllo sul WSDL generato e ne migliora l’interoperabilita’.
Ecco alcune delle annotazioni introdotte dalla specifica Jax-WS.
• @XmlAccessorType: Con questa annotazione specifichiamo quali campi o proprieta’ devono essere serializzati. Il valore
FIELD indica che vanno serializzati tutti i campi pubblici e privati.
• @XmlType Mappa una classe al un tipo XML Schema. Se il tipo e’ complesso della specie xs:all o xs:sequence si
puo’ specificare gli elementi inclusi e il loro ordine con la proprieta’ propOrder()
• @XmlElement: Indica che il campo va serializzato e permette di specificarne il nome locale ed il namespace.
• @XmlSchemaType: Mappa un tipo Java ad un tipo semplice predefinito.
• @WebService (Required): Specifica che il metodo seguente e’ l’implementazione di un endpoint.
– name: il nome del wsdl:portType
– targetNamespace: il namespace del WSDL e degli elementi generati (salvo diversamente imposto dalee regole di
mapping di JAXB).
– serviceName: il nome del wsdl:service
– portName: il wsdl:portName
• @SOAPBinding: indica il tipo di binding. Di default utilizza il Wrapped.
• @WebResult: Indica il nome dell’elemento della risposta. Se non e’ specificato utilizza [nomeoperazione]Response
• @Oneway: Indica che il metodo e’ di tipo Oneway.
Per una lista esaustiva con utili esempi di utilizzo, consultare le specifiche.
import javax.jws.*;
@WebService(targetNamespace = "http://www.rivenditore.org/Ordine", name = "OrdineInterface" ←)
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface OrdineInterface {
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
@Oneway
@WebMethod(operationName = "Notifica")
public void notifica(
@WebParam(partName = "parameter", name = "notifica", targetNamespace = "http://www. ←rivenditore.org/ordiniElements")
java.lang.String parameter);
...
}
Una volta terminata l’implementazione e’ possibile verificarne la bonta’ generando il WSDL associato con l’apposito comando
java2wsdl
Corso di Laboratorio Applicazioni Internet
4.3.2
43 / 57
Approccio centrato su messaggi SOAP
Abbiamo visto nella sezione precedente come realizzare servizi web in maniera piu’ o meno trasparente rispetto ai linguaggi di
programmazione utilizzati. In alcuni casi pero’ questo puo’ non essere conveniente, ma si puo’ preferire agire direttamente sui
documenti xml contenuti nei messaggi, anziche’ essere costretti a convertire i messaggi in oggetti java.
Abbiamo gia’ visto nelle precedenti sezioni su xml qualcosa di simile, in particolare come realizzare dei client e servlet in
grado di inviare e ricevere messaggi XML su HTTP. Allo stesso modo potremmo quindi far viaggiare messaggi SOAP su HTTP.
Tuttavia per il trattamento di messaggi SOAP esiste una specifica standard in java, che ne ottimizza la gestione (SAAJ: SOAP
with Attachments API for Java). Vediamo quindi come servirci di questa libreria per imbustare il nostro ordine in un messaggio
SOAP.
4.3.2.1
Message Level Service
In precedenza abbiamo implementato un servizio in base alle operazioni che espone, ovvero un servizio che lavora a livello di
operazioni. Se vogliamo implementare un servizio che lavori direttamente sul messaggio ricevuto possiamo implementarlo in
questo modo:
@WebServiceProvider
@ServiceMode(value=Service.Mode.MESSAGE)&#x200f;
public class OrdiniService implements Provider<SOAPMessage>{
public SOAPMessage invoke(SOAPMessage request) {
SOAPMessage response = null;
...
return response;
}
}
Il servizio puo’ quindi essere implementato agendo sull’oggetto SOAPMessage usando la API SAAJ ed un normale parser xml:
SOAPPart sp = request.getSOAPPart();
SOAPEnvelope se = sp.getEnvelope();
SOAPBody sb = se.getBody();
SOAPHeader sh = se.getHeader()&#x200f;
org.w3c.dom.Document dom =
sb.extractContentAsDocument();
NodeList node = dom.getElementsByTagName("ordine");
...
4.3.2.2
SAAJ Client
Anche sul versante client puo’ essere usato SOAP, come segue:
• Si instanzia una javax.xml.soap.MessageFactory
• Con la MessageFactory si costruisce un javax.xml.soap.SOAPMessage
• Si popola il SOAPMessage coi nodi necessari
• Si invia la busta SOAP usando un SOAPConnector
Vediamo nel seguito il codice da utilizzare.
//Istanziamo una MessageFactory
MessageFactory mf = MessageFactory.newInstance();
//Costruiamo il SOAPMessage vuoto
SOAPMessage msg = mf.createMessage();
Corso di Laboratorio Applicazioni Internet
44 / 57
//Popoliamo il body del SOAPMessage con i nodi necessari
SOAPElement pagamento = msg.getSOAPBody().addChildElement("pagamento", "pay", "http://www. ←bank.org/ns");
pagamento.addChildElement("da","pay").setNodeValue("cliente");
pagamento.addChildElement("a","pay").setNodeValue("shop");
pagamento.addChildElement("importo","pay").setNodeValue("100");
//Scriviamo il SOAPMessage nel messaggio HTTP e riceviamo la risposta
SOAPConnectionFactory scFactory = SOAPConnectionFactory.newInstance();
SOAPConnection con = scFactory.createConnection();
java.net.URL endpoint = new URL("http://rivenditore.com/ordina");
SOAPMessage response = con.call(message, endpoint);
...
5
Sicurezza nelle applicazioni WEB
Le Applicazioni che espongono una Web API sono particolarmente esposte ai consueti problemi di sicurezza. Questa particolare
predisposizione al rischio, proviene dal fatto che, contrariamente alle API tradizionali, si tratta di interfacce di cui a priori non è
facile individuare esattamente gli usi che ne saranno fatti. Le testsuite che vengono tipicamente messe in campo per un collaudo
pre-esercizio sono quindi inevitabilmente limitate rispetto alla creatività dei futuri fruitori dell’applicazione.
Un problema ulteriore è la tipica complessitè delle infrastrutture che ospitano le applicazioni Internet, che abbbiamo già discusso
nella sezione sulle architetture applicative. Di fatto il processo di autenticazione ed autorizzazione delle richieste è tipicamente distribuito tra diversi componenti infrastrutturali ed applicativi e è abbastanza frequente che alcuni aspetti di sicurezza
(tipicamente quelli relativi all’autorizzazione delle richieste in arrivo) finiscano per essere trascurati.
Uno dei problemi più peculiari riguarda la presenza di intermediari nella catena di comunicazioni tra fruitore ed erogatore del
servizio. I tradizionali meccanismi per la sicurezza di rete come il Transport Layer Security (SSL/TLS), Virtual Private Networks
(VPNs), Internet Protocol Security (IPSec) ed il Multipurpose Internet Mail Exchange (S/MIME) sono tecnologie punto a punto
(Point-to-Point). Nonostante queste tecniche siano ampiamente utilizzate per la sicurezza dei Web Service, è spesso necessario
impiegare ulteriori tecnologie che agiscono a livello di messaggio anzichè a livello di trasporto, e si dimostrano quindi più adatte
a gestire le comunicazioni di tipo ’End-to-End’.
Un messaggio xml o json può infatti passare attraverso degli intermediari prima di raggiungere la propria destinazione, ed in
queste situazioni si presentano prolematiche che non è possibile gestire con protocolli legati al trasporto. Ad esempio è possibile
che un requisito di sicurezza richieda che soltanto l’applicazione finale possa leggere il contenuto del messaggio, ma l’uso del
protocollo SSL su http (https) garantisce la riservatezza del dato solo fino al prossimo endpoint a cui il mittente è collegato. Se
questi non è l’applicazione finale ma un intermediario, ci verificheranno due problemi: l’intermediario avrà accesso al dato in
chiaro e non sarà garantito che la comunicazione tra l’intermediario e la destinazione finale sia cifrata. Un’altra controindicazione
delle comunicazioni basate su https è che richiedono la cifratura dell’intero canale, anche se il dato da proteggere è magari soltanto
un campo specifico del messaggio. Questo pone due controindicazioni: il costo eccessivo della cifratura e l’impossibilità di
trattare a livello infrastrutturale il contenuto del messaggio (si pensi ad esempio un proxy http che funga da antivirus).
Per ovviare a queste problematiche è necessario utilizzare tecnologie di sicurezza basate sul messaggio piuttosto che sul trasporto.
In questo corso analizzeremo la specifica WS-Security per la gestione della sicurezza a livello messaggio nel protocollo SOAP.
5.1
La sicurezza nelle Applicazioni Internet
Anche WS-Security si appoggia alle tecniche crittografiche di base per garantire la sicurezza dei messaggi scambiati. Per un
richiamo sui concetti basi rimandiamo alla lezione sulla sicurezza nelle applicazioni internet.
5.1.1
Setup con Keytool
Per l’utilizzo delle tecnologie di base viste a lezione utilizzeremo certificati X509. Vediamo nel seguito come utilizzare il
comando Java keytool per creare e gestire coppie di chiavi private/pubbliche e relativi certificati. Specificando l’opzione -
Corso di Laboratorio Applicazioni Internet
45 / 57
genkey il keytool crea una chiave privata e la sua corrispettiva chiave pubblica in un file criptato denominato keystore che
viene generato nella directory dalla quale è stato lanciato il comando. La chiave pubblica viene inclusa in un certificato self
signed all’interno del file prodotto. Un certificato self-signed è semplicemente quello in cui issuer e subject sono la stessa entità.
[user@home ~]$ keytool -genkey -alias Alice -keypass alicekeys -keystore alice.ks
Immettere la password del keystore: alicestore
Specificare nome e cognome
[Unknown]: Alice
Specificare il nome dell’unità aziendale
[Unknown]: Unipi
Specificare il nome dell’azienda
[Unknown]: Unipi
Specificare la località
[Unknown]: Pisa
Specificare la provincia
[Unknown]: Pisa
Specificare il codice a due lettere del paese in cui si trova l’unità
[Unknown]: IT
Il dato CN=Alice, OU=Unipi, O=Unipi, L=Pisa, ST=Pisa, C=IT è corretto?
[no]: si
[user@home ~]$ ls
alice.keystore
Questo genera la coppia di chiavi, con la chiave privata cifrata con la password "alicekeys", il tutto inserito nel keystore "alice.ks"
a sua volta cifrato con la password "alicestore"
Adesso estraiamo il certificato pubblico che consegneremo a Bob.
[user@home ~]$ keytool -export -keystore alice.ks -alias alice -storepass alicestore -file
alice.cer
Il certificato è memorizzato nel file <alice.cer>
←-
Una volta che alice avrà consegnato il certificato a Bob, questo lo includerà nel suo truststore, ovvero un archivio JKS (lo stesso
del keystore) contenente i certificati dei soggetti fidati.
[user@home ~]$ keytool -import -keystore "bob.ts" -alias "alice" -file "alice.cer" - ←storepass "bobtrust"
Proprietario: CN=Alice, OU=Unipi, O=Unipi, L=Pisa, ST=Pisa, C=IT
Organismo di emissione: CN=Alice, OU=Unipi, O=Unipi, L=Pisa, ST=Pisa, C=IT
Numero di serie: 4982e1c9
Valido da Fri Jan 30 12:17:29 CET 2009 a Thu Apr 30 13:17:29 CEST 2009
Impronte digitali certificato:
MD5: 20:72:F9:D0:30:08:D7:AB:14:64:F1:BE:22:18:44:20
SHA1: 9B:77:8E:A6:EF:51:5B:CA:C2:B9:12:E4:EB:E7:5B:53:AE:AC:FF:07
Considerare attendibile questo certificato? [no]: si
Il certificato è stato aggiunto al keystore
Nota
Per semplicità e’ possibile mettere certificati pubblici e chiavi private nel solito keystore invece che separarli in keystore e
truststore.
Non rimane dunque che fare lo stesso per Bob:
• Generare la coppia di chiavi da includere nel keystore di Bob
• Estrarre il certificato e consegnarlo ad Alice
• Creare il truststore di Alice
A questo punto abbiamo tutto quello che serve per permettere a Alice e Bob di usufruire dei servizi offerti dall’infrastruttura a
chiave publica.
Corso di Laboratorio Applicazioni Internet
5.2
46 / 57
WS-Security
La specifica WS-Security definisce un’estensione di SOAP che implementa autenticazione, integrità e confidenzialità a livello
messaggio. L’obiettivo di questa specifica non è introdurre nuove tecniche, ma quello di utilizzare le soluzioni esistenti in materia
di sicurezza delle comunicazioni con SOAP ed i Web Service.
Il punto di ingresso di WS-Security è un header SOAP, chiamato <Security>. Contiene i dati riguardanti la sicurezza e le
informazioni necessarie per implementare meccanismi come firma e cifratura. Questo elemento può essere presente più volte
all’interno del messaggio se le informazioni di sicurezza da inserire sono destinate a differenti destinatari (indicati con l’attributo
actor). Due header <Security> non possono avere il solito actor, mentre un header senza actor specificato, può essere
consumato da qualsiasi ricevente.
Vediamo un esempio di messaggio SOAP con un header <Security>.
<SOAP:Envelope xmlns:SOAP="...">
<SOAP:Header>
<wsse:Security SOAP:actor="..." SOAP:mustUnderstand="...">
...
</wsse:Security>
</SOAP:Header>
<SOAP:Body Id="MsgBody">
<!-- SOAP Body data -->
</SOAP:Body>
</SOAP:Envelope>
5.2.1
Authentication
Uno dei requisiti centrali di un’infrastruttura di comunicazione è la possibilità di fornire e ricevere referenze attendibili sull’identità dei soggetti coinvolti nella comunicazione. Queste referenze sono informazioni aggiuntive che trovano naturale collocazione
nell’header SOAP del messaggio.
Ci sono molteplici modi per fornire prove della propria identità e WS-Security fornisce un metodo astratto per implementarle. I
metodi che analizzeremo sono:
• Username/Password
• Certificati X.509 su PKI
Vediamo ad esempio di autenticazione tramite Username e Password utilizzata anche in HTTP. Le informazioni sono aggiunte
all’Header SOAP tramite l’elemento UsernameToken:
<!-- No Password -->
<UsernameToken>
<Username>Alice</Username>
</UsernameToken>
<!-- Clear Text Password -->
<UsernameToken>
<Username>Alice</Username>
<Password Type="wsse:PasswordText">Wonderland</Password>
</UsernameToken>
<!-- Digest: SHA1 hash of base64-encoded Password -->
<UsernameToken>
<Username>Alice</Username>
<Password Type="wsse:PasswordDigest">gpBDXjx79eutcXdtlULIlcrSiRs=<Password>
<Nonce>h52sI9pKV0BVRPUolQC7Cg==</Nonce>
<Created>2002-11-04T19:16:50Z</Created>
</UsernameToken>
Corso di Laboratorio Applicazioni Internet
47 / 57
Nel primo caso inviamo il nome dell’utente senza nessuna password di sicurezza. Questo tipo di autenticazione deve essere
utilizzato in combinazione con altre tecniche di sicurezza perchè chiaramente poco robusta.
Stesse considerazioni anche per la seconda tecnica di autenticazione, che prevede di passare la password di sicurezza in chiaro
nel messaggio.
Fornisce invece una buon livello di sicurezza il terzo tipo di autenticazione, che invia la password di sicurezza codificata ed
accompagnata da un digest (o impronta) in combinazione con la data di creazione per ovviare ad attacchi di tipo replay.
Un’altra opzione è quella di inviare un certificato X.509 che, avvalendosi dell’infrastruttura a chiave pubblica, fornisce l’identità
di un soggetto e garantisce l’attendibilità di tali informazioni.
Il certificato X.509 viene incluso in un messaggio in un elemento WS-Security chiamato BinarySecurityToken. L’algoritmo usato per la codifica viene specificato nell’attributo EncodingType mentre il tipo di certificato è specificato in
ValueType.
<wsse:BinarySecurityToken
ValueType="wsse:X509v3"
EncodingType="wsse:Base64Binary"
Id="...">MIIHdjCCB...</wsse:BinarySecurityToken>
5.2.2
Signature
Il processo di autenticazione ci fornisce garanzie sull’identità del soggetto con cui stiamo scambiando informazioni. Quello che
non sappiamo è se le informazioni che giungono a destinazione siano le stesse inserite dal mittente e che non abbiano subito
alterazioni durante il tragitto. Non abbiamo quindi garanzie sull’integrità dei dati, garanzie che che possiamo fornire firmandoli.
WS-Security si appoggia alla specifica XML Signature per firmare un messaggio. Una volta che un messaggio è stato firmato, è
praticamente impossibile poterlo modificare senza che il destinatario non se ne accorga. La firma non impedisce che il messaggio
sia letto, ma assicura al ricevente che
• Le parti firmate del messaggio non sono state modificate dopo la firma
• Il soggetto che ha apposto la firma è lo stesso identificato dal certificato.
A grandi linee, quando viene firmata una parte del messaggio, viene aggiungo un ID alla parte in chiaro nel messaggio, il
certificato del firmatario, anch’esso identificabile univocamente, e un elemento Signature contenente l’elemento firmato, i
riferimenti al certificato del firmatario e all’elemento in chiaro oltre ai dettagli del processo di firma.
<Signature>
<SignedInfo>
<CanonicalizationMethod Algorithm=".../xml-exc-c14n#"/>
<SignatureMethod Algorithm=".../xmldsig#rsa-sha1"/>
<Reference URI="#myBody">
...
<DigestMethod Algorithm=".../xmldsig#sha1"/>
<DigestValue>EULddytSo1...</ds:DigestValue>
<Reference>
<SignedInfo>
<SignatureValue>
BL8jdfToEb1l/vXcMZNNjPOV...
<SignatureValue>
<KeyInfo>
<SecurityTokenReference>
<Reference URI="#MyX509Token"/>
</SecurityTokenReference>
</KeyInfo>
</Signature>
Nella prima parte del messaggio troviamo il <CanonicalizationMethod>. Qualsiasi documento che viene firmato deve
esser prima portato in forma canonica. Per la natura dell’XML, non esiste un modo di definire l’ordine degli attributi, ne di come
trattare gli spazi. Il processo di canonizzazione elimina gli spazi bianchi e ordina gli attributi secondo uno specifico schema.
Corso di Laboratorio Applicazioni Internet
48 / 57
Il valore della firma è contenuto nell’elemento SignatureValue, SecurityTokenReference riferisce il certificato del
firmatario, mentre Reference contiene un riferimento all’elemento firmato.
In questo modo è possibile firmare parti diverse con certificati diversi, risolvendo i problemi addizionali inerenti alle comunicazioni end to end.
5.2.3
Encryption
Abbiamo adesso gli strumenti e le tecniche per garantire integrità e autenticità del contenuto delle informazioni scambiate.
Dobbiamo ancora fornire garanzie di confidenzialità di tali informazioni, ovvero impedire che dati sensibili possano essere letti
da soggetti non autorizzati.
Quello che vogliamo è la possibilità di cifrare il contenuto del messaggio, o parti di esso, di modo che solo il destinatario sia in
grado di decifrarlo e leggerlo. Anche in questo caso il WS-Security si appoggia su uno standard preesistente e collaudato, l’XML
Encryption.
Quando si codificano i dati, si può scegliere la codifica simmetrica o asimmetrica. La prima richiede di condividere un’informazione segreta. Infatti la chiave usata per cifrare è la medesima usata per decifrare. Questa soluzione è efficace se si ha un
buon controllo sulle parti in causa e un buon livello di fiducia su chi detiene le chiavi, ma pone il problema su come distribuire le
chiavi.
Se invece vogliamo un metodo che non pone il problema della distribuzione delle chiavi possiamo utilizzare la codifica asimmetrica che abbiamo usato anche per la firma. Mentre nella firma usavamo la chiave privata per firmare e il certificato pubblico per
verificare l’integrità, adesso usiamo il certificato pubblico per cifrare e la chiave privata per riportare le parti cifrate in chiaro.
5.2.4
TimeStamp
Un concetto comune ai sistemi orientati ai messaggi è quello dello della temporalità dei dati. Se i dati di un messaggio sono
troppo vecchi, dovrebbe essere scartato. Se arrivano due messaggi contradditori, la data di creazione può aiutare a decidere quale
processare e quale scartare. Per gestire queste situazioni, è stato definito l’elemento TimeStamp.
Gli eventi rilevanti di un messaggio sono il momento della creazione, la scadenza scelta dal mittente, e il momento della ricezione.
Sapendo la data di creazione e il tempo di validità il destinatario può decidere se i dati di un messaggio sono usabili o meno. Gli
elementi sono quindi:
• Created: contiene l’istante di creazione
• Expires: Settato dal mittente o da un intermediario, determina la validità temporale dei dati.
• Received: Notifica quando un intermediario ha ricevuto il messaggio.
Per default il tipo di questi elementi è xs:dateTime anche se è possibile usare altri tipi ma incorrendo in problemi di
interoperabilità L’elemento Received può contenere un attributo Actor che indica la URI di chi ha impostato l’ora di
ricezione.
<wsu:Timestamp>
<wsu:Created wsu:Id=
"Id-2af5d5bd-1f0c-4365-b7ff-010629a1256c">
2007-09-19T16:15:31Z
</wsu:Created>
<wsu:Expires wsu:Id=
"Id-4c337c65-8ae7-439f-b4f0-d18d7823e240">
2007-09-19T16:20:31Z
</wsu:Expires>
</wsu:Timestamp>
Corso di Laboratorio Applicazioni Internet
5.3
49 / 57
WS-Security con CXF
Esistono numerose implementazioni della specifica WS-Security in Java. Alcune sono studiate per essere usare stand alone (es.
XWSS) altre specificatamente per essere integrate in infrastrutture come CXF o Axis2.
Vediamo come esempio come configurare due interlocutori SOAP che utilizzano CXF e intendono rendere sicuri i loro messaggi.
CXF, come altri Web Service Framework, grazie alla componibilità del messaggio SOAP, separa l’implementazione della logica
del servizio da quella dei componenti aggiuntivi dello stack WS-*. Il risultato è che l’utilizzo di questi componenti spesso si
riduce ad un esercizio di configurazione.
Dobbiamo modificare il file di configurazione per istruire il framework sui componenti aggiuntivi da inserire:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<jaxws:endpoint id="..."
implementor="..."
address="...">
<jaxws:inInterceptors>
<bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor" />
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="..." value="..." />
<entry key="..." value="..." />
...
</map>
</constructor-arg>
</bean>
</jaxws:inInterceptors>
</jaxws:endpoint>
</beans>
• L’endpoint specifica i dettagli di un servizio. L’attributo implementor specifica la classe che ne implementa la logica,
mentra address specifica a quale indirizzo il servizio deve rispondere.
• Il figlio di endpoint, InInterceptor, definisce una sequenza di intercettori che il messaggio in ingresso deve attraversare
e viene modificato.
• Il SAAJInInterceptor si occupa di trasformare il messaggio xml in un SOAPMessage, operazione necessaria per l’intercettore successivo.
• Il WSS4JInInterceptor implementa la specifica WS-Security e, dal momento che si basa su WSS4J, richiede che il
messaggio passi nella forma di SOAPMessage. Questo intercettore richiede dei parametri di configurazione che vengono
forniti nella forma di coppie chiave/valore.
Con una configurazione simile si abilita il client ad utilizzare l’implementazione di WS-Security:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
Corso di Laboratorio Applicazioni Internet
50 / 57
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www. ←springframework.org/schema/beans/spring-beans-2.0.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<bean id="..." class="...">
<property name="serviceClass" value="..."/>
<property name="address" value="..."/>
<property name="outInterceptors">
<list>
<bean id="client" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean"
factory-bean="proxyFactory" factory-method="create" />
<bean id="saajOut" class="org.apache.cxf.binding.soap.saaj. ←SAAJOutInterceptor" />
<bean id="wss4jOut" class="org.apache.cxf.ws.security.wss4j. ←WSS4JOutInterceptor">
<constructor-arg>
<map>
<entry key="..." value="..." />
<entry key="..." value="..." />
...
</map>
</constructor-arg>
</bean>
</list>
</property>
</bean>
</beans>
←-
I parametri da che invieremo all’interceptor abiliteranno le funzionalità di WS-Security.
5.3.1
Autentication con Username Token
Analizziamo un esempio di configurazione che abilita l’inserimento di un UsernameToken per l’autenticazione. Cominciamo
con il Client. Dobbiamo indicare all’infrastruttura il tipo di azione da applicare al messaggio, lo username e la password.
<entry
<entry
<entry
<entry
key="action" value="UsernameToken" />
key="user" value="Alice" />
key="passwordType" value="PasswordText" />
key="passwordCallbackClass" value="ClientPasswordCallbackClass" />
a classe specificata in passwordCallbackClass indica un’implementazione di CallbackHandler e decide quale password inserire in relazione all’utente passato.
public class ClientPasswordCallback implements CallbackHandler {
public void handle(Callback[] callbacks) throws IOException, ←UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
pc.setPassword("Wonderland);
}
}
Lato server dobbiamo specificare che ci aspettiamo un UsernameToken con password inviata in chiaro.
<entry key="action" value="UsernameToken" />
<entry key="passwordType" value="PasswordText" />
<entry key="passwordCallbackClass" value="ServerPasswordCallbackClass" />
con la classe ServerPasswordCallbackClass implementata come segue:
Corso di Laboratorio Applicazioni Internet
51 / 57
public class ServerPasswordCallbackClass implements CallbackHandler {
public void handle(Callback[] callbacks) throws IOException, ←UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
if (pc.getIdentifer().equals("Alice") {
if (!pc.getPassword().equals("Wonderland")) {
throw new SecurityException("Password errata");
}
}
}
}
5.3.2
Signature
Vediamo come configurare i nostri interlocutori per firmare una parte del messaggio. Per prima cosa dobbiamo creare un file
di properties contenente le seguenti informazioni necessarie a leggere i keystore e truststore contenenti i certificati e le chiavi
private usati nel nostro scenario:
• org.apache.ws.security.crypto.provider indica quale implementazione di org.apache.ws.security.
components.crypto.Crypto passare a WSS4J con le informazioni di cifratura.
• org.apache.ws.security.crypto.merlin.keystore.type indica il tipo di keystore usato
• org.apache.ws.security.crypto.merlin.keystore.password indica la password per accedere al keystore
• org.apache.ws.security.crypto.merlin.file indica il path del keystore.
Vediamo le properties per il keystore di Alice (alice.ks.properties)
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=alicestore
org.apache.ws.security.crypto.merlin.file=alice.ks
Adesso per il truststore di Alice (alice.ts.properties)
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=alicetrust
org.apache.ws.security.crypto.merlin.file=alice.ts
Keystore di Bob (bob.ks.properties)
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=bobstore
org.apache.ws.security.crypto.merlin.file=bob.ks
Truststore di Bob (bob.ts.properties)
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=bobtrust
org.apache.ws.security.crypto.merlin.file=bob.ts
Prodotti i files con le properties dei keystore e truststore, configuriamo gli interceptor affichè Alice firmi parte del messaggio:
Corso di Laboratorio Applicazioni Internet
52 / 57
<entry key="action" value="Signature" />
<entry key="user" value="alice" />
<entry key="passwordCallbackClass" value="ClientPasswordCallbackClass" />
<entry key="signaturePropFile" value="alice.ks.properties" />
<entry key="signatureParts" value="{Element}{http://banca.com/ns}Saldo;{Element}{http:// ←banca.com/ns}CodiceCarta" />
dove user indica l’alias della chiave privata nel keystore, passwordCallbackClass è la classe che deve fornire la password
per leggere quella chiave privata ed il signaturePropFile è il file di properties per il keystore di Alice. signatureParts
indica quali parti si devono firmare. Se non viene specificato viene firmato tutto il Body del messaggio
Configuriamo il servizio con le informazioni necessarie per la verifica della firma:
<entry key="action" value="Signature" />
<entry key="signaturePropFile" value="bob.ts.properties" />
5.3.3
Encryption
Il processo di cifratura è molto simile a quello di firma e le configurazioni si somigliano molto. Il client così configurato cifra
tutto il body del proprio messaggio:
<entry key="action" value="Encrypt" />
<entry key="encryptionUser" value="bob" />
<entry key="encryptionPropFile" value="alice.ts.properties" />
ed il servizio decifra grazie a questa configurazione:
<entry key="action" value="Encrypt" />
<entry key="decryptionPropFile" value="bob.ks.properties" />
<entry key="passwordCallbackClass" value="ServerPasswordCallbackClass" />
5.3.4
Timestamp
Apporre il timestamp al messaggio è l’operazione più semplice da configurare. Lato client indichiamo l’operazione nella act
ion aggiungendo il parametro opzionale timeToLive che esprime, in millisecondi, il gap tra data di creazione e di scadenza
<entry key="action" value="Timestamp" />
<entry key="timeToLive" value="50" />
Ancora più semplice lato server:
<entry key="action" value="Timestamp" />
5.3.5
Timestamp Signature Encrypt
Vediamo un esempio tipico di configurazione di un client che applica un alto livello di sicurezza ai propri messaggi. Le azioni
applicate sono in ordine:
• Applicazione del Timestamp
• Firma del Timestamp e del Body
• Cifratura della firma e del Body
Corso di Laboratorio Applicazioni Internet
53 / 57
<entry key="action" value="Timestamp Signature Encrypt"/>
<entry key="user" value="alice"/>
<entry key="signaturePropFile" value="alice.ks.properties"/>
<entry key="encryptionPropFile" value="alice.ts.properties"/>
<entry key="encryptionUser" value="Bob"/>
<entry key="passwordCallbackClass" value="ClientPasswordCallback"/>
<entry key="signatureParts" value="{Element}{http://docs.oasis-open.org/wss/2004/01/oasis ←-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;{Element}{http://schemas.xmlsoap.org/ ←soap/envelope/}Body"/>
<entry key="encryptionParts" value="{Element}{http://www.w3.org/2000/09/xmldsig#}Signature ←;{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
Il servizio deve essere configurato di conseguenza:
<entry
<entry
<entry
<entry
key="action" value="Timestamp Signature Encrypt"/>
key="signaturePropFile" value="bob.ts.properties"/>
key="decryptionPropFile" value="bob.ks.properties"/>
key="passwordCallbackClass" value="ServerPasswordCallback"/>
Nota
È importante osservare che l’ordine con cui sono inserite le azioni di sicurezza da applicare al messaggio è significativo.
5.4
Le specifiche WS-*
Con WS-* si intende l’insieme di specifiche in campo Web Service basate su SOAP, tra cui quella che abbiamo appena analizzato,
WS-Security. Vista la pletora di specifiche redatte per i Web Service, diamo una veloce descrizione delle piuù affermate ed
utilizzate:
• WS-Addressing: Sappiamo che uno dei vantaggi dei Web Service basati su SOAP è la neutralità rispetto al livello di trasporto. Il
nostro messaggio SOAP può esser quindi trasportato via HTTP, TCP, JMS, SMTP o altri. Alcuni di questi trasporti forniscono
un modo per indirizzare il messaggio verso un servizio. Ad esempio l’HTTP ha una URL che può mappare uno specifico
endpoint. JMS, di contro, ha solo le nozioni di coda e topic, ma potremmo avere più di un servizio associato and una coda o un
topic. Lo stesso problema si propone nell’HTTP quando abbiamo più servizi con la soluta URL HTTP e non abbiamo modo
di distinguere a quale servizio è destinato il messaggio.
La specifica WS-Addressing pone rimedio a questo problema. Invece di specificare l’endpoint destinatario all’esterno nel
messaggio, definisce un set di header standard che consentono di specificarlo all’interno del messaggio stesso.
• WS-Policy: Quando pubblichiamo un servizio, dobbiamo anche comunicare quali servizi aggiuntivi offre o richiede a chi ne
vuole fruire. Ad esempio, il nostro servizio potrebbe richiede la cifratura del messaggio tramite specifici algoritmi di cifratura.
In qualche modo questi requisiti devono esser comunicati ai fruitori del servizio. Solitamente questo viene fatto attraverso
canali esterni, come documentazioni, mail etc.
Lo standard che copre questa problematica è il WS-Policy, fornendo un linguaggio per descrivere le capacità ed i requisiti dei
Web Service tramite particolari elementi chiamati assertion.
• WS-Security Le minacce ad un Web Service coinvolgono minacce al sistema host, all’applicazione ed all’intera infrastruttura
di rete. Per rendere un Web Service sicuro, sono necessari molteplici tecnologie di sicurezza basate sull’XML per risolvere
problemi inerenti l’autenticazione, la gestione degli accessi, le politiche di sicurezza distribuite, la presenza di intermediari.
Il livello di trasporto solitamente fornisce meccanismi di sicurezza per comunicazioni point-to-point, insufficienti per comunicazioni end-to-end tipiche dei Web Service. WS-Security fornisce le specifiche per garantire la sicurezza anche per
comunicazioni end-to-end a livello di messaggio, quindi indipendenti dal trasporto utilizzato.
• WS-ReliableMessaging: Sapere che un messaggio è stato consegnato è una importante funzionalità. Mentre alcuni protocolli
di trasporto, come il JMS, garantiscono l’affidabilità della consegna, altri, come l’HTTP, non sono affidabili. Di conseguenza
abbiamo che l’affidabilità deve esser implementata ad un livello superiore.
WS-ReliableMessaging specifica l’infrastruttura necessaria a garantisce la consegna al destinatario del messaggio per livelli di
trasporto non affidabili.
Corso di Laboratorio Applicazioni Internet
54 / 57
• WS-Transaction: Vedremo a breve i problemi derivanti dall’accesso concorrente ai database e dall’uso di database distribuiti.
Queste problematiche si spostano e complicano quando usiamo database remoti interfacciati con web service. La specifica WSTransaction risolve questi problemi fornendo un’infrastruttura che garantisce la corretta esecuzioned della logica applicativa.
6
Gestione del Backend
E’ molto raro che un web service non abbia bisogno di memorizzare informazioni in una base dati. Nel progetto d’esame è
possibile utilizzare un qualunque database relazionale che supporti accesso tramite l’interfaccia JDBC di Java. Nel seguito,
facendo riferimento all’uso del DB PostgreSQL (http://www.postgresql.org), mostriamo come predisporre la nostra applicazione
per memorizzare le informazioni relative agli ordini ricevuti dal cliente e come tenere aggiornato il magazzino merce in modo
tale da verificare le disponibilità.
La prima operazione da svolgere riguarda la creazione e l’inizializazione delle tabelle necessarie alla nostra applicazione. Esempi
completi per l’esecuzione di queste operazioni in ambiente erby o postgresql sono analizzati nell’esercitazione sull’uso dei
database.
Il passo successivo riguarda la capacità di agire programmaticamente sui dati. Nel progetto vi viene richiesto di programmare
usando le API JDBC, lo standard java per l’esecuzione di comandi SQL. Per l’uso di JDBC nel progetto si rimanda alla lezione
di introduzione all’uso di JDBC ed alle esercitazioni su DB.
6.1
SQL Injection
Molta attenzione va posta in fase di programmazione ai rischi di introdurre nel proprio codice vulnerabilità agli attacchi di SQL
Injection, che permettono all’attaccante di modificare le query inviate dall’applicazione al DB.
Di solito queste vulnerabilità sono introdotte da un uso improprio delle istruzioni di tipo Statement, come discusso nella lezione
di introduzione all’uso di JDBC. Per questo motivo nel progetto d’esame è richiesto di utilizzare PreparedStatement anzichà
Statement in tutte le situazioni soggette a questo tipo di problema.
La differenza sostanziale sta nel fatto che la Statement prende il comando SQL come stringa o come concatenazione di stringhe,
mentre la PreparedStatement viene inizializzata con un template SQL parametrico, cosicchè l’inserimento di parametri nell’istruzione SQL avviene tramite metodi spoecifici (setString, setInteger, ...) che garantiscono la verifica del contenuto, e l’escape
delle stringhe di input utente che potrebbero causare la modifica dell’istruzione SQL originale.
Esempi specifici sono analizzati nell’esercitazione su SQL Injection.
6.2
Le Transazioni
Un’altro aspetto particolarmente critico nella programmazione dell’accesso al backend ` quello transazionale. Una transazione
è una sequenza di azioni di lettura e scrittura della base di dati e di elaborazioni di dati in memoria temporanea che il database
deve eseguire garantendo le seguenti proprietà:
• Atomicità, una transazione e’ un’unita di elaborazione. Solo le transazioni che terminano normalmente (committed) fanno
transitare la base di dati in un nuovo stato. Le transazioni che terminano prematuramente (aborted) sono trattate come se non
fossero mai iniziate.
Nel caso dell’aggiunta di un ordine, se fallisce la query 3, l’aggiornamento della disponibilità, deve essere annullata anche la
2, l’inserzione degli articoli acquistati.
• Consistenza, una transazione è una trasformazione corretta dello stato del database. Al termine di ogni transazione il DB deve
trovarsi in uno stato consistente. Il Database Management System (DBMS) garantisce che nessuno dei vincoli di integrità del
DB venga violato.
• Serializzabilità, l’effetto sulla base di dati dell’esecuzione concorrente di più transazioni è equivalente ad un’esecuzione seriale
delle stesse, cioè ad un esecuzione in cui le transazioni vengono eseguite una dopo l’altra in un qualche ordine. Quindi,
transazioni concorrenti non devono influenzarsi reciprocamente.
Supponiamo di ricevere due ordini contemporaneamente, ognuno per 8 pezzi dello stesso articolo quando in listino sono
disponibili 10 pezzi. La sequenza di operazioni potrebbe essere questa:
Corso di Laboratorio Applicazioni Internet
55 / 57
-- Lettura dalla disponibilita
SELECT disponibilita FROM listino WHERE ...
-- dato in memoria temporanea
var disponibilitaCorrente = $(disponibilita)
-- verifico disponibilita
if(disponibilitaCorrente >= 8){
-- Inserisco l’erdine
INSERT ordine ....
UPDATE listino SET disponibilita=(disponibilitaCorrente-8) WHERE ...
}
Se le ordinazioni vengono eseguite simultaneamente, in assenza di serializzabilità potrebbe succedere che venga verificata la
disponibilità, accettati entrambi gli ordini e aggiornata la disponibilità in maniera errata.
• Persistenza, Le modifiche sulla base di dati di una transazione terminata normalmente sono permanenti, cioè non alterabili da
eventuali malfunzionamenti successivi alla terminazione. Il DBMS deve proteggere il DB a fronte di guasti.
Nota
Consistenza e Persistenza sono gestite dal DBMS e trasparenti alle applicazioni, quindi il programmatore può farci affidamento
senza gestirle in alcun modo.
Una transazione può avere due esiti:
• Commit, indica una terminazione corretta. L’applicazione dopo aver eseguito le varie operazioni di lettura/scrittura sul database
che formavano la transazione esegue una particolare istruzione SQL COMMIT che comunica ufficialmente al Transaction
Manager il termine delle operazioni, e fa quindi evolvere la base di dati verso un nuovo stato consistente modificato dalla
transazione appena conclusa.
• Rollback, indica una terminazione non corretta. E’ possibile sia che la transazione, per qualche motivo applicativo, decida che
non abbia senso continuare la transazione e quindi la abortisce eseguendo l’istruzione SQL ROLLBACK, sia che il sistema non
sia più in grado (es. guasto tecnico o violazione di un vincolo tipo ’foreign key’) di garantire la corretta prosecuzione della
transazione e quindi la abortisce automaticamente.
Il modo più semplice di operare sul DB tramite JDBC è quello di usare l’autoCommit mode sulla connessione. Se l’autocommit
è impostato a true ogni operazione sul DB sarà seguita automaticamente da una commmit. Impostando invece l’autocommit
a false, la transazione viene avviata alla prima istruzione SQL inviata sulla connessione, mentre la sua chiusura non avviene
più in automatico ma esplicitamente tramite l’esecuzione delle istruzioni di commit o rollback da parte del programmatore. Di
seguito è riportato un frammento di codice Java che mostra come gestire una transazione con autommit a false.
java.sql.Connection connectionDB = null;
try{
// inizializzazione driver JDBC
Class.forName(...DRIVER...);
connectionDB = DriverManager.getConnection(CONNECTION_URL , USERNAME , PASSWORD);
// Avvio transazione, impostando l’auto-commit a false
connectionDB.setAutoCommit(false);
... LOGICA APPLICATIVA ...
// transazione terminata con successo
connectionDB.commit();
Corso di Laboratorio Applicazioni Internet
56 / 57
}catch(Exception e){
// transazione terminata con errore
connectionDB.rollback();
... GESTIONE ERRORE ...
} finally {
// Chiusura connessione
connectionDB.close();
}
6.3
Livelli di isolamento
In teoria la serializzabilità dovrebbe essere sufficiente a garantire l’isolamento delle transazioni in tutte le possibili condizioni al
contorno. In realtà non è così, perchè il costo da pagare in termini di riduzione della concorrenza sarebbe troppo elevato rispetto a
quanto effettivamente richiesto dalle applicazioni. Per questo motivo è possibile definire, per ogni transazione, l’effettivo livello
di isolamento necessario per quella transazione. I livelli di isolamento previsti in SQL sono quattro e sono analizzati nella lezione
sulle transazioni.
Il seguente frammento di codice java mostra come impostare il livello di isolamento attraverso la libreria JDBC. La classe java.
sql.Connection fornisce quattro costanti rappresentanti i valori dei quattro livelli di isolamento: Connection.TRANS
ACTION_READ_UNCOMMITTED, Connection.TRANSACTION_READ_COMMITTED, Connection.TRANSACTION
_REPEATABLE_READ, Connection.TRANSACTION_SERIALIZABLE .
java.sql.Connection connectionDB = null;
try{
// inizializzazione driver JDBC
Class.forName(...DRIVER...);
connectionDB = DriverManager.getConnection(CONNECTION_URL , USERNAME , PASSWORD);
// Imposto il livello di isolamento SERIALIZABLE
connectionDB.setTransactionIsolation(Connection.SERIALIZABLE)
// Avvio transazione, impostando l’auto-commit a false
connectionDB.setAutoCommit(false);
... LOGICA APPLICATIVA ...
// transazione terminata con successo
connectionDB.commit();
}catch(Exception e){
// transazione terminata con errore
connectionDB.rollback();
... GESTIONE ERRORE ...
}finally{
// Chiusura connessione
connectionDB.close();
}
Nei più diffusi database (es. PostgreSQL, Oracle) è possibile impostare uno dei quattro livelli di isolamento previsti, ma internamente all’implementazione del DBMS ci sono solo due distinti livelli, che corrispondono a Read Committed e Serializable. Se
viene scelto un livello Read Uncommitted verrà in realtà utilizzato dal DBMS il livello Read Committed, mentre se viene scelto
il livello Repeatable Read verrà utilizzato il livello Serializable. Questo è permesso dal SQL standard poichè i quattro livelli di
Corso di Laboratorio Applicazioni Internet
57 / 57
isolamento definiscono solamente quali anomalie, dovute agli accessi concorrenti dei dati, NON DEVONO succedere, ma non
definiscono quale fenomeno POSSONO succedere.
Per una estesa trattazione dell’uso dei livelli di isolamento si rimanda all’ottima documentazione su Transaction Isolation di
Postgresql.