Sei sulla pagina 1di 81

Corso di Laboratorio Applicazioni Internet

Corso di Laboratorio Applicazioni Internet

Corso di Laboratorio Applicazioni Internet

ii

Copyright 2005-2011 Dip. Informatica - Universit di Pisa

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

10 marzo 2011

CRONOLOGIA DELLE REVISIONI POSIZIONE DATA DESCRIZIONE NOME

Corso di Laboratorio Applicazioni Internet

iv

Indice
1 Architetture Applicative 1.1 1.2 1.3 2 Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . I Web Services e le Architetture Orientate ai Servizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Linfrastruttura software per lerogazione di Servizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 4 5 6 6 6 7 8 9

Il Protocollo HTTP e la programmazione di Servlet 2.1 HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.2 Tools http . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Richiesta HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Risposta HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Codica dei parametri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Eseguire query HTTP con JAVA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

Web/Application Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.2.1 2.2.2 2.2.3 2.2.4 Introduzione alla programmazione di Servlet. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Ciclo di vita di una Servlet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Implementare HttpServlet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Deploy della Servlet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

2.3

Sessioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.3.1 2.3.2 Problematiche nella gestione delle sessioni - Caso Alitalia . . . . . . . . . . . . . . . . . . . . . . . . . 16 Sessioni in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 20

Introduzione allXML 3.1

XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 Sintassi XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Strutturare i dati . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Namespace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 XML in Java, DOM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Java API for XML Processing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3.1.5.1 3.1.5.2 3.1.5.3 DOM Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 SAX Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 StAX Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

3.2

XSD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.2.1 3.2.2 Costruzione di uno Schema XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 Validazione con JAXP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

3.3

Applicazioni XML su HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

Corso di Laboratorio Applicazioni Internet

Web Service 4.1 4.2

31

SOAP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 WSDL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 Binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 WSDL Validation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

4.3

Web Service in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 4.3.1 Approccio centrato su classi Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 4.3.1.1 4.3.1.2 4.3.2 Uso di WSDL2Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 Uso di Java2WSDL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

Approccio centrato su messaggi SOAP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 4.3.2.1 4.3.2.2 Message Level Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 SAAJ Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 41 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

Sicurezza nelle applicazioni WEB 5.1 5.2 5.3 Minacce per la sicurezza nelle comunicazioni

Web Services Security Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Public Key Infrastructure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 5.3.1 Setup con Keytool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

5.4

WS-Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 5.4.1 5.4.2 5.4.3 5.4.4 Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Signature . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 Encryption . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 TimeStamp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

5.5

WS-Security con CXF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 5.5.1 5.5.2 5.5.3 5.5.4 5.5.5 Autentication con Username Token . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 Signature . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 Encryption . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 Timestamp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 Timestamp Signature Encrypt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

5.6 6

Le speciche WS-* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 54

Gestione delle Transazioni in un Database 6.1 6.2

Gestione del Backend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 JDBC e Gestione Backend con Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 6.2.1 SQL Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

6.3

Le Transazioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

Corso di Laboratorio Applicazioni Internet

vi

6.4

Transazioni concorrenti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 6.4.1 6.4.2 6.4.3 6.4.4 Lost Update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 Dirty Read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 Unrepeatable Read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Phantom Row . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

6.5

Lock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 6.5.1 6.5.2 6.5.3 6.5.4 Lost Update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 Unrepeatable Read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 Phantom Row . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 SELECT ... FOR UPDATE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

6.6

Livelli di isolamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 READ COMMITTED e SERIALIZABLE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 Livello di isolamento Serializable vs Serializzazione Matematica . . . . . . . . . . . . . . . . . . . . . . 71 Lost Update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 Unrepeatable Read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 Phantom Row . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73

6.7

Transazioni Distribuite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 6.7.1 Two Phase Commit (2PC) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74

Corso di Laboratorio Applicazioni Internet

vii

Elenco delle tabelle


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Header HTTP di richiesta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Header HTTP piu frequenti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 9

Codica di caratteri speciali . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Message Exchange Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 Esempio Lost Update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 Esempio Dirty Read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Esempio Unrepeatable Read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 Esempio Phantom Row . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 Implementazioni dei lock per i database pi diffusi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 Esempio Lost Update con lock share . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 Esempio Lost Update con lock exclusive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 Esempio Unrepeatable Read con lock share . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 Esempio Phantom Row . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 Livelli di isolamento e le anomale dovute ad accesso concorrente ai dati . . . . . . . . . . . . . . . . . . . . . . 68 Livello di isolamento Serializable vs Serializzazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 Esempio Lost Update con Read Committed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 Esempio Lost Update con Serializable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73

Corso di Laboratorio Applicazioni Internet

1 / 74

1
1.1

Architetture Applicative
Introduzione

La rapida diffusione di Internet ha provocato una vera e propria rivoluzione nelle architetture delle applicazioni distribuite, aumentando signicativamente la complessit dei sistemi in gioco. E sicuramente interessante, prima di approfondire gli aspetti architetturali tipici di una applicazione Internet, ripercorrere rapidamente levoluzione 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. Linterazione da pate degli utenti (o meglio degli operatori) avveniva da terminali, tipicamente i 3270 a fosfori verde sempre piu difcili 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 dallutente e a visualizzare sul display i caratteri ricevuti dal mainframe. A quei tempi la scalabilit di unapplicazione, 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 / 74

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 grache. I Data Base Management System (DBMS) daltro canto offrivano uno strumento afdabile ed a basso costo per la gestione delle transazioni applicative. Si diffonde cos un nuovo paradigma applicativo, solitamente riferito come client-server, basato proprio sulluso coordinato di queste due tecnologie: il client, detto anche Fat Client a causa delle sue dimensioni spesso eccessive, include linterfaccia 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 lesecuzione della maggior parte del carico sui PC degli stessi utenti. La semplicit dellarchitettura client-server, che ne ha costituito il principale punto di forza, mostra per nel tempo alcuni limiti importanti. In particolare: luso di un Fat Client richiede un upgrade del client ad ogni modica dellapplicazione (nuove feature e/o bug-x); 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 dellintera 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 piu 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 lopportunita per portare i loro software su sistema Unix, avviando il processo di downsizing da mainframe a un ambiente etorogeneo, che usa pesantemente minicomputer, reti LAN e personal computer.

Corso di Laboratorio Applicazioni Internet

3 / 74

Queste due strade si rilevano per poc pi che un palliativo. I DB server, che gi costituivano un collo di bottiglia dellintera architettura, vedono aumentare il loro carico di lavoro. I TP Monitor, anche in ambiente Unix mantengono signicative controindicazioni in termini di costi e complessit duso. In questa fase intervengono altre due signicative novit destinate ad inuenzare levoluzione delle architetture applicative: da una parte si sviluppa unattenzione crescente verso gli aspetti di portabilit, standardizzazione ed interoperabilit, poi scaturita nella denizione 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 piu signicativo per la costruzione di un sistema ad oggetti distribuito si sviluppa nellambito dellOMG (Object Management Group), unorganizzazione che raggruppa tutti i piu importanti player del mercato del software e che arriva a standardizzare larchitettura software CORBA (Common Object Request Broker Architecture), unarchitettura ad oggetti distribuita, denita 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.

E in questo contesto che esplode la novita della tecnologia Internet, trainata dal travolgente successo del World Wide Web. La rete Internet in effetti esisteva gia da tempo ma viene scossa dallinvenzione del WWW, un nuovo paradigma progettato per la visualizzazione di testi ipermediali in rete, e basato sulluso del protocollo HTTP e dei Browser Web, utilizzati per la visualizzazione di pagine descritte tramite il linguaggio HTML. larchitettura WWW prevede infati luso 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 le system, ma puo 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 / 74

I primi siti web dinamici utilizzavano infatti il brwoser non solo per visualizzare le pagine html, ma anche per raccogliere le preferenze dellutente, ad esempio per il login o per le selezioni della pagina, tramite luso del costrutto FORM del linguaggio HTML. Il browser si dimostra cosi adatto a gestire il livello di presentazione, prima occupato dal Fat Client nellarchitettura client server. Dal punto di vista del backend, invece, questi siti cominciano ad accedere sempre piu intensamente a varie fonti informative (data base ed in generale sistemi legacy), tramite luso di applicazioni CGI, per costruire le pagine html da restituire come risposta al Browser. Ne deriva unarchitettura applicativa nella quale: il browser si candida a diventare un Client Universale, con la sola funzione di gestire linterazione con lutenza; 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, piu (CORBA, RMI, DCOM) o meno (RPC, API proprietarie, ...) recenti. Questa nuova architettura si afferma rapidamente, identicata inizialmente come Architettura a Tre Livelli (Three Tier Architecture). Successivamente, a mano a mano che si intesica la disponibilita e luso di Servizi Internet, si evolve verso unarchitettura a piu livelli (Multi Tier Architecture), schematizzata nella gura successiva, che prevede sia interazioni verso il backend che verso servizi esterni disponibili anchessi tramite Internet.

1.2

I Web Services e le Architetture Orientate ai Servizi

La diffusione di Internet si porta dietro tutto un orire di nuove tecnologie. Sostanzialmente si fa strada lidea che se la tecnologia Internet si e dimostrata adatta a supportare lo straordinario successo del WEB, scalando su milioni di server e di utenti diversi, puo essere una tecnologia di successo anche per le applicazioni distribuite.

Corso di Laboratorio Applicazioni Internet

5 / 74

Nascono quindi tutta una serie di nuove proposte, che ruotano principalmente attorno alluso di XML per la rappresentazione dei dati di interscambio e luso di http per il livello di trasporto sico 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, solitamente riferita semplicemente come Web Services e basata sulluso del protocollo SOAP, di cui parleremo estesamente piu avanti, per limbustamento dei contenuti applicativi, e sulla denizione di numerosi e spesso eccessivamente complessi formati di header della busta SOAP, utilizzati per contenere le metainformazioni necessarie a vari servizi di infrastrura (sicurezza, indirizzamento, transazionalita, etc.). La seconda, solitamente riferita come Restful Web Services parte invece dalla convinzione che il protocollo http possa essere usato nativamente anche per realizzare applicazioni, senza bisogno dellulteriore livello di imbustamento SOAP. In entrambe queste 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 nalmente trovato nei Web Services una adeguata tecnologia attuativa.

1.3

Linfrastruttura software per lerogazione di Servizi

Abbiamo visto nelle sezioni precedenti come le Applicazioni Internet si presentino come un insieme di nodi applicativi (servizi), 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, puo essere schematizzato come nella gura successiva.

Nella gura successiva mostriamo invece come i 3 livelli logici del singolo nodo applicativo vengano effettivamente realizzati in un reale progetto attuativo.

Ogni livello e a sua volta composto da alcuni componenti software, che interagiscono tra loro come schematizzato nella gura che segue.

Corso di Laboratorio Applicazioni Internet

6 / 74

E facile immaginare come la progettazione, la realizzazione ed il tuning di unapplicazione Internet non possano non essere condizionati dalla complessita dellambiente in cui queste stesse saranno poi ospitate e dalle interazioni che dovranno avere con componenti esterni. Lobiettivo di questo corso e proprio quello di introdurre alle problematiche speciche che un progettista, uno sviluppatore ed un sistemista applicativo dovranno imparare a fronteggiare, lavorando con questa tipologia sempre piu diffusa di applicazioni.

2
2.1

Il Protocollo HTTP e la programmazione di Servlet


HTTP

HTTP lacronimo 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 demisticare lidea che http sia un protocollo di comunicazione utilizzabile solo dai browser, utile fare un po di pratica nellinterazione 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, specicando lhost 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

Corso di Laboratorio Applicazioni Internet

7 / 74

Server: gws Transfer-Encoding: chunked Connection: Close 1ad4 <html>[.....omissis......]</html> 0

cURL un tool per il trasferimento di le 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

con la possibilit di specicare una serie di opzioni per denizione di header, redirezione delloutput, codica 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 nellURI (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 specicati solo gli header e non il corpo del messaggio. PUT: questo metodo richiede che il contenuto del messaggio venga memorizzato nella posizione specicata dalla URI DELETE: richiede la cancellazione della risorssa specicata nella URI. TRACE: fa eseguire lecho del messaggio OPTIONS: richiede al server di fornire informazioni sulle opzioni di comunicazione disponibili per la risorsa specicata. CONNECT: indica al proxy di assumere il comportamento di tunnel lURI sta per Uniform Resource Identier ed indica loggetto 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 deniti nella specica 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 speciche del W3C.

Corso di Laboratorio Applicazioni Internet

8 / 74

Header Accept Authorization Connection Host

Descrizione Mime Type accettati nella riosposta Credenziali per lautenticazione Tipo di connessione che il client preferisce Domain name dellhost per il virtual hosting Tabella 1: Header HTTP di richiesta

Esempio Accept: text/plain Authorization: Basic QWxhZGRpbcGVuIHNlc2FtZQ== Connection: Close Host: www.link.it

2.1.3

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 classicabile 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 lastmodied 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. Lutente ha richiesto un documento ma non ha fornito uno username o una password validi. 403: Vietato, laccesso alla URL vietato. 404: Non trovato; il documento non disponibile sul server. 500: Server error; si vericato un errore interno del server.

Corso di Laboratorio Applicazioni Internet

9 / 74

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 lha costruita, sul le richiesto (data di ultima modica). 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 Header Allow Content-Lentgth Content-Type Date Exprires Last-Modied Location Server WWW-authenticate Descrizione Metodi di richiesta accettati dal server Dimensione dei dati in bytes Mime type del contenuto della risposta Data e ora dellinvio della risposta Data di scadenza del documento Data dellultima modica effettuata sulla risorsa Per il redirect Contiene informazioni sul server Contiene informazioni per laccesso in caso di errore 401 Tabella 2: Header HTTP piu frequenti i dati (opzionali, come per la Richiesta). Questi possono essere in qualsiasi formato, anche binario, come specicato nellheader ContentType. Nel nostro caso, avendo richiesto una pagina HTML, il ContentType text/html e nel corpo del messaggio vediamo il codice della pagina.
2.1.4 Codica dei parametri

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-Modied: 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

Per effettuare le nostre richieste faremo uso principalmente dei metodi GET o POST. La differenza sostanziale sta nel modo in cui i dati sono codicati 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 codica 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 modicano 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

Corso di Laboratorio Applicazioni Internet

10 / 74

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. Unaltro 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> denisce text entry elds, checkboxes, radio buttons o pushbuttons <select> denisce dropdown menus e selection box <textarea> denisce 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 codicato 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 = wwwadm@cli.di.unipi.it 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

Corso di Laboratorio Applicazioni Internet

11 / 74

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

2.1.5

Eseguire query HTTP con JAVA

Abbiamo usato n 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 specica 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 difcolt sta nel fatto che nel parametro da passare ci sono caratteri che non possiamo inserire nella query string (il carattere & e gli spazi per lesattezza) quindi dobbiamo prima codicari. 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()); }

Corso di Laboratorio Applicazioni Internet

12 / 74

Eseguiamo la richiesta e stampiamo la risposta.


// Eseguiamo la richiesta e prendiamo la risposta HttpResponse response = httpclient.execute(httpget); // Stampiamo Status Line e Headers 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 specicata e usa quella porta come default per individuare il server. Qualora la porta del Web Server fosse diversa, allora necessario specicarla nellurl (es. http://www.link.it:8080/isi.html) Ricevura la richiesta, il server si occupa di reperire le risorse specicate nella URL di richiesta secondo il metodo specicato (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 allinterno 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 allinterno di un browser. Luso 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 dellutente. Quando digito un URL del tipo miosito/login, viene invocato un servlet che vericher che i dati inseriti siano corretti e in base a questa decisione mi potr indirizzare in una pagina di conferma o di errore. Sotto questottica un servlet un programma che deve rispettare determinate regole e che processa in un determinato modo una richiesta HTTP. Nulla vieta che allinterno 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) unestensione 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 presso servet nella URL
http://hostname:port/servet/Servlet.class[?args]

Il presso congurabile nellapplication server ed possibile avere pi pressi sul solito application server.

Corso di Laboratorio Applicazioni Internet

13 / 74

2.2.2

Ciclo di vita di una Servlet

Una servlet, nella sua forma pi generale, unistanza di una classe che implementa linterfaccia javax.servlet.Servlet. Molte servlet, comunque, estendono una delle implementazioni standard di questa interfaccia. Dovendo gestire richieste HTTP, useremo ed esamineremo limplementazione javax.servlet.http.HttpServlet. Quando viene istanziata/deployata una HttpServlet, lapplication 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 sufciente che implementi linterfaccia SingleThreadModel

. Quando necessario effettuare lunload 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 // allunload 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 limplementazione dellinterfaccia Servlet che dobbiamo estendere facendo loverride 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){ ...

Corso di Laboratorio Applicazioni Internet

14 / 74

} @Override public void doPost(HttpServletRequest req, HttpServletResponse res){ ... } @Override public void doPut(HttpServletRequest req, HttpServletResponse res){ ... } @Override public void doDelete(HttpServletRequest req, HttpServletResponse res){ ... } ... ... }

Se non viene fatto loverride di un metodo, viene usata limplementazione 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 allApplication Server. Questa la stuttura standard per le applicazioni web compatibili J2EE 1.2:

Corso di Laboratorio Applicazioni Internet

15 / 74

Per quanto riguarda il nostro esempio, dobbiamo mettere la classe appena compilata che implementa la servlet sotto WEB-INF/classes e creare il le WEB-INF/web.xml. Vediamone un esempio:
<web-app> <servlet> <servlet-name>hello</servlet-name> <servlet-class>HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>

Lelemento servlet denisce il nome della servlet da usare per riferirla nel resto del documento e la classe che la implementa. Lelemento servlet-mapping indica al server a quale URL il servizio deve rispondere. Completati i le necessari e organizzati secondo le speciche, copiamo tutto nella cartella webapps di Tomcat per fargli eseguire il deploy dellapplicazione.

2.3

Sessioni

Da html.it Una delle funzionalit pi richieste per unapplicazione 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 identicativo di riconoscimento che questo user per le richieste successive, permettendo al server di riconoscerlo. possibile quindi creare applicazioni su protocollo http che riconoscono lutente, 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. Lutente 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 identicare 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. Lutente visita il server, stavolta inserendo lheader Cookie contenente il SessionID salvato in precedenza. 2. Il server controlla se il client ha fornito un SessionID. 3. Il server verica se il SessionID ricevuto corrisponde ad una sessione. 4. Il server localizza i dati di sessione e li rende disponibili allapplicazione

Corso di Laboratorio Applicazioni Internet

16 / 74

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 modiche - ogni volta che il client accede allo stesso server. Il server invia lheader 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 lheader Cookie Cookie: <name>=<value>[; <name>=<value>]...
i parametri DOMAIN e PATH individuano le URL per cui un cookie valido il parametro expires specica 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 lidenticativo 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 identicativo di sessione.
Nota Per scoprire se un client supporta o meno luso dei cookies, non ce altro modo che eseguire un tentativo di scrittura di cookie e vericarne successivamente linvio 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 lid sessione contemporaneamente via cookie che via hidden eld. 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 lutente non necessariamente segue il percorso che abbiamo immaginato, ma che possa saltare passaggi, inserire dati errati, concorrere con altri utenti nelluso di risorse condivise. Alitalia ci fornisce un esempio di cattiva programmazione nelluso 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

17 / 74

In unaltra nestra facciamo unaltra ricerca, ad esempio per un volo A/R Roma - Napoli:

Corso di Laboratorio Applicazioni Internet

18 / 74

Torniamo adesso alla lista dei voli Roma - Milano e acquistiamo il primo.

Corso di Laboratorio Applicazioni Internet

19 / 74

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 lidenticativo 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 lindice di tabella, ma con un identicativo 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 laltra. Java tenta di gestire le sessioni tramite cookie. Se questo non possibile si pu alternativamente passare lID di sessione tra i parametri della pagina. Per questo scopo, il metodo encodeURL(String URL) di HttpServletResponse aggiunge il parametro con lid di sessione allURL 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

20 / 74

Modichiamo 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 lid di sessione e inserirlo nelle richieste successive o tramite header o tramite query string.
//Recupero lid sessione dagli header della risposta HttpResponse response = httpclient.execute(httpget); String cookie = response.getHeader("Set-Cookie"); //Inserisco lidSessione nelle richieste successive HttpRequest request = .... request.setHeader("Cookie", cookie);

In questo modo tutte le volte che effettueremo una richiesta inviando lid di sessione e un valore ad un parametro, riceveremo nella risposta tutti i valori inviati no a quel momento.

3
3.1

Introduzione allXML
XML

Supponiamo che nel nostro scenario di riferimento il cliente voglia comunicare i dati di unordine al rivenditore. Supponiamo per semplicit che il rivenditore abbia bisogno solo dei modelli dei prodotti scelti, delle quantit e del corriere preferito per gestire unordine. 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 specicare 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 le esterni che ne deniscano la semantica perch questinformazione gi contenuta al suo interno Semplice: ha una sintassi caratterizzata da poche e semplici regole. Estensibile: gli elementi gi sintatticamente deniti 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 limplementazione di servizi web

Corso di Laboratorio Applicazioni Internet

21 / 74

3.1.1

Sintassi XML

La sintassi dellXML piuttosto semplice e denita da poche, ma rigide, regole. Vediamole brevemente: buona norma cominciare il documento XML specicando la versione e la codica 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 lampersand (&) sono vietati allinterno degli elementi e devono essere codicati. E consigliabile farlo anche con altri caratteri speciali anche se non strettamente necessario: &lt; &gt; &amp; &apos; &quot; < > &

Tabella 3: Codica di caratteri speciali Si possono inserire commenti allinterno 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 deniamo CDATA (Character DATA) in questo modo:
<![CDATA[ .... ]]>

Corso di Laboratorio Applicazioni Internet

22 / 74

3.1.2

Strutturare i dati

Ora che conosciamo le regole sintattiche dellXML, 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 signicato a quei dati, ma sono ancora ambigui. Possiamo annidare gli elementi e strutturare ancora meglio le informazioni. Possiamo specicare che un codice ed una quantit sono per un articolo e che tanti articoli fanno unordine:
<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 deniti dallo sviluppatore. Questo pu causare dei conitti, ad esempio quando si lavora su documenti XML di provenienze diverse. Vediamo un esempio pratico: supponiamo che nellordine inseriamo anche il nome del produttore e del prodotto. Potrebbe vericarsi una situazione di questo tipo:
<ordine> <nome>Nintendo</nome> ... <nome>Wii</nome> ... </ordine>

Come fare a distinguerli? Per questo si deniscono due namespace e, grazie al presso, si risale al loro signicato 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 specicato un presso di un namespace, questo viene preso come default e assegnato a tutti i nodi discendenti privi di namespace. La denizione di un namespace visibile nel nodo in cui viene inserita e in tutti i suoi discendenti.

Corso di Laboratorio Applicazioni Internet

23 / 74

3.1.4

XML in Java, DOM

Abbiamo scritto lo schema di unordine. Vediamo come modicare il client HTTP afch lo invii alla servlet.
//Creo il documento XML dellordine 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 dellXML HttpClient httpclient = new DefaultHttpClient(); HttpPost httppost = new HttpPost(servletUrl); //Creo lentita 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. Lattuale versione la 1.4 inclusa in Java SE 6.0 ed una sua implementazione scaricabile allindirizzo 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

24 / 74

Simple API for XML Processing (SAX) Document Object Model (DOM) SAX unAPI di basso livello il cui principale punto di forza lefcienza. Quando un documento viene parsato usando SAX, una serie di eventi vengono generati e passati allapplicazione tramite lutilizzo di callback handlers che implementano lhandler 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 modicare 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 dutilizzo. Una volta ricevuto il documento, il parser si occupa di costruire un albero di oggetti che rappresentano il contenuto e lorganizzazione dei dati contenuti. In questo caso lalbero esiste in memoria e lapplicazione pu attraversarlo e modicarlo in ogni suo punto. Ovviamente il prezzo da pagare il costo di computazione iniziale per la costruzione dellalbero 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 allhandler per elaborarli, ma lutente a controllare il usso degli eventi. Questo signica 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 lutente ne abbia bisogno o sia in grado di elaborarli. Le librerie pull parsing sono molto pu semplici delle push parsing e questo permette di semplicare 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 nellelaborazione del documento XML. TrAX, con lutilizzo di un XSLT stylesheet, permette di trasformare lXML. 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 allaltro, infatti, possibile prendere un documento DOM, trasformarlo e scriverlo su le, oppure prendere lXML da le, 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 duso Efcenza CPU e Memoria Solo in avanti Legge XML Scrive XML CRUD (Create Read Update Delete) StAX Pull, streaming Alta Buona Si Si Si No SAX Push, streaming Media Buona Si Si No No DOM In memory tree Alta Dipende No Si Si Si TrAX XSLT Rule Media Dipende No Si 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

25 / 74

3.1.5.1

DOM Parser

Vediamo come ottenere un documento DOM partendo dallXML


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 limplementazione del nostro handler:

Corso di Laboratorio Applicazioni Internet

26 / 74

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 signicato grazie alluso dellXML, 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 denire uno schema si utilizza lXSD (XML Schema Denition), 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

27 / 74

</xs:schema>

Il nodo radice deve essere uno schema denito 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 denire avranno quel namespace, mentre con
elementFormDefault="qualified"

indichiamo che tutti gli elementi deniti saranno qualicati (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 denizione di XML Schema (boolean, string, date, etc.) oppure un tipo denito 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 denire un elemento semplice la seguente:
<xs:element name="xxx" type="yyy"/>

dove xxx il nome dellelemento e yyy il suo tipo. lXML Schema ha molti tipi di dato predeniti. I pi comuni sono: xs:string xs:decimal xs:integer xs:boolean xs:date xs:time Possiamo quindi specicare 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 dellarticolo ad una stringa di 7 caratteri alfanumerici maiuscoli, privi di spazi. Per far questo deniamo 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

28 / 74

<xs:pattern value="[A-Z0-9]{7}"/> </xs:restriction> </xs:simpleType> </xs:schema>

Deniamo a questo punto lelemento 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 deniamo un nuovo tipo complesso, alcuni degli elementi utilizzabili sono: sequence: richiede che gli elementi compaiono nellordine specicato 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 denendo lelemento ordine come sequenza di molti elementi articolo. Utilizzeremo nuovamente lelemento xs:sequence specicando questa volta il numero di occorrenze (di default impostate a 1) Per specicare un attributo nellelemento ordine basta inserire il seguente elemento nella sua denizione
<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

29 / 74

</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 denito lXSD e possiamo validare il contenuto del documento XML. Uno strumento di validazione online reperibile allindirizzo http://www.xmlforasp.net/SchemaValidator.aspx grazie ad esso possiamo controllare se un documento XML rispetta lo schema denito in un documento XSD.
3.2.2 Validazione con JAXP

JAXP, oltre ad effettuare il parsing e la costruzione dellalbero DOM, fornisce gli strumenti per validare i documenti XML rispetto ad uno o pi XML Schema. Per essere noticati di eventuali errori di validazione devono essere rispettati questi vincoli: La Factory deve essere congurata e settato lhandler dellerrore Al documento deve essere associato almento uno schema Vediamo come gestire la validazione con JAXP. Per prima cosa conguriamo 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 lXML 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 specicandolo nella factory

Corso di Laboratorio Applicazioni Internet

30 / 74

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 unordine. 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 lxml 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()); } } }

Inne rispondiamo al Client con un messaggio che confermi la ricezione dellordine.


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

31 / 74

res.setStatus(500); }

Web Service

Abbiamo visto nora 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 sufcienti ad indirizzare gli aspetti di interoperabilit tra applicazioni sviluppate indipendentemente. questo lobiettivo dei Web Services, un insieme di architetture e speciche condivise nalizzato a risolvere i problemi di interoperabilit nella cooperazione applicativa.

4.1

SOAP

Il protocollo piu usato per la strutturazione dei messaggi applicativi scambiati tra servizi Web il protocollo SOAP. SOAP (Simple Object Access Protocol) un protocollo basato su XML per lo scambio dinformazioni in un ambiente distribuito e denisce un formato comune per trasmettere dati tra client e service. SOAP prevede limbustamento dei contenuti applicativi da scambiare allinterno 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. Lelemento 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>

LEnvelope ha due gli: lHeader, opzionale, e il Body, obbligatorio. Lelemento opzionale SOAP Header estende il messaggio e contiene metadati, informazioni utili al processamento del messaggio, come ad esempio lidentit dellutente, 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"> <soap:Header> <myHeader soap:actor="..." soap:mustUnderstand="..."> ...

Corso di Laboratorio Applicazioni Internet

32 / 74

</myHeader> </soap:Header> <soap:Body ...> ... </soap:Body> </soap:Envelope>

Un elemento dellHeader pu avere alcuni attributi speciali: MustUnderstand: Talvolta alcuni header devono essere processati afnch lapplicazione possa procedere oltre. Si consideri ad esempio lheader 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 lattibuto 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 nale, ma anche a nodi intermedi. Lattributo opzionale actor serve appunto a specicare lendpoint al quale indirizzato lelemento. Il SOAP Body, obbligatorio, inne 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 lindirizzo sico del servizio, le operazioni supportate, tipologia dei messaggi per ogni operazione risultano ancora cablati nelle applicazioni. Il Web Service Denition Language (WSDL) un linguaggio basato su XML per specicare tali aspetti dei servizi. Mediante WSDL pu essere quindi descritta linterfaccia 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 allindirizzo - in formato URI - che rende disponibile il Web Service) Il WSDL puo essere suddiviso tra denizione logica e concreta. La prima descrive le interfacce, le operazioni ed i messaggi, mentre la seconda denisce il trasporto, il binding e gli endpoint. La struttura principale del WSDL apparir grossomodo cos
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> <!-- abstract definitions -->

Corso di Laboratorio Applicazioni Internet

33 / 74

<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 unoperazione che riceve i dati di unordine 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 denirsi i tipi degli elementi in esso contenuti. Abbiamo gi dento 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>

Importiamolo e aggiungiamo i tipi mancanti per loperazione di notica

Corso di Laboratorio Applicazioni Internet

34 / 74

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 denire i messaggi scambiati. Il nostro servizio ha un solo messaggio SOAP scambiato, quello di consegna dellordine.
<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 denire le interfacce. Lelemento portType denisce un gruppo di operazioni. Il nome portType sviante ci riferiremo a questo elemento spesso con il nome interfaccia. Ogni wsdl:operation dellinterfaccia contiene una combinazione di elementi wsdl:input e wsdl:output e opzionalmente un wsdl:fault. Lordine di questi elementi denisce il Message Exchange Pattern (MEP) delloperazione. MEP Request-Response OneWay Solicit-Response Notication wsdl:operation <wsdl:input ..> <wsdl:output ..> <wsdl:input ..> <wsdl:output ..> <wsdl:input ..> <wsdl:output ..> Tabella 5: Message Exchange Pattern

Corso di Laboratorio Applicazioni Internet

35 / 74

avvertimento La specica 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 Notication.

Come detto in precedenza, loperazione 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>

Linterfaccia sempre considerata astratta da momento che non sa come saranno rappresentati i messaggi sul cavo nch non sar denito il binding che specica, tra le altre cose, il protocollo di trasporto (nel nostro caso HTTP).
4.2.3 Binding

Lelemento wsdl:binding descrive i dettagli concreti per utilizzare una particolare interfaccia (portType) con uno specico protocollo. La struttura di base dellelemento 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>

Lattributo name, come per gli altri elementi, specica un nome per riferirsi al binding nel resto del documento. Il type deve specicare un portType precedentemente denito. Nel nostro esempio possiamo inserire un binding chiamato ordineInterfaceBinding per linterfaccia ordineInterface:

Corso di Laboratorio Applicazioni Internet

36 / 74

<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>

Lelemento wsdl:binding generico. Denisce solamente il framework per descrivere i dettaggli di binding. Questi dettagli sono forniti utilizzando degli elementi estensivi. Le speciche WSDL forniscono alcuni elementi prediniti 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>

lelemento soap:binding indica lo stile di default del servizio (i valori possibili sono document o rpc) rispetto al protocollo di trasporto richiesto (HTTP nel nostro caso). Lelemento soap:operation denisce il valore dellheader HTTP SOAPAction. Inne lelemento soap:body descrive come i part del messaggio devono apparire allinterno del messaggio (valori possibili literal o encoded). Utilizzare lo stile document in SOAP indica che il body conterr un documento XML, e che le parti del messaggio specicano gli elementi XML che vi saranno inseriti. Usare lo stile rpc in SOAP indica che il body conterr la rappresentazione XML di una chiamata di un metodo e che le parti del messaggio rappresentano i parametri del metodo. Lattributo use specica la codica che deve essere utilizzata per tradurre le parti astratte del messaggio in una rappresentazione concreta. Nel caso di encoded, le denizioni astratte sono tradotte in formato concreto applicando le regole di codica di SOAP. Nel caso di literal, le denizioni astratte divengono denizioni concrete (sono denizioni letterali). In questo caso basta ispezionale i tipi deniti nello Schema XML per determinare il formato concreto del messaggio. Utilizzare denizioni letterali rende il messaggio piu pulito ed permette agli strumenti una pi semplice interpretazione dello stesso. Le regole di codica introduce notevoli problemi di interoperabilit tra i vari toolkit. Pu portare ad esempio a spiacevoli situazioni in cui un messaggio concreto (quello che passa sul lo) non viene validato rispetto allo schema originale (dal momento che ne la rappresentazione astratta).

Corso di Laboratorio Applicazioni Internet

37 / 74

Nota Per fornire una migliore interoperabilit, il WS-I proibisce luso di codiche, inclusa quella SOAP, nel Basic Prole 1.0. Questo signica che se si vuole essere aderenti al Basic Prole 1.0 si possono utilizzare solo denizioni letterali.

La combinazione di style/use pi comune quella document/literal ed quella di default per la maggior parte dei toolkit e introduce il minor numero di problematiche sullinteroperabilit. Laltro come detto era lrpc/encoded, ma ora che stato bandito non pi unopzione utilizzabile. Lunica altra combinazione lrpc/literal. Ci sono numerose pubblicazioni che sconsigliano caldamente luso di questo binding sempre per problemi correlati alla interoperabilit quindi non andremo oltre sullargomento e applicheremo sempre il binding document/literal.
4.2.4 Services

Lelemento wsdl:service denisce 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>

Per ogni porta dobbiamo fornire un nome da utilizzare come riferimento ed un binding tra quelli deniti in precedenza. Poi possiamo aggiungere elementi estensivi che deniscono i dettagli per lindirizzamento specici 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 vericarne la correttezza sintattica e laderenza alle speciche 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 specici 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.

Corso di Laboratorio Applicazioni Internet

38 / 74

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 lannotazione 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 denizione dei Web Service in WSDL alle classi Java che implementano quel servizio. Linterfaccia logica, denita dallelemento wsdl:portType, e mappata in un service endpoint interface (SEI). Ogni tipo complesso denito in un WSDL viene mappato in classi Java che seguono il mapping denito dalla specica Java Architecture for XML Binding (JAX-B). Lendpoint denito dallelemento wsdl:service viene generato in una classe Java usata dai fruitori per accedere agli endpoint che implementano il servizio. Il tool wsdl2java automatizza la generazione di questo codice. Inoltre fornisce opzioni per la generazione del codice di partenza per limplementazione 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: Largomento -ant genera un makele per Ant, chiamato build.xml per eseguire e compilare lapplicazione Largomento -impl genera una classe che implementa ogni portType specicato dal WSDL Lopzione -server genera un semplice main() per lesecuzione del servizio come applicazione stand alone Lopzione -d specica 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 le contiene linterfaccia che il servizio implementa. serviceName.java lEndpoint. Questa classe verra usata dai fruitori per effettuare richieste al servizio. portTypeNameImpl.java lo Skeleton. Qeusta classe e limplementazione 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

Laltro 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); }

Corso di Laboratorio Applicazioni Internet

39 / 74

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 La classe che racchiude il messaggio di risposta Se loperazione 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 predeniti, ma specicare completamente ogni dettaglio del servizio fornisce un maggior controllo sul WSDL generato e ne migliora linteroperabilita.

Ecco alcune delle annotazioni introdotte dalla specica Jax-WS. @XmlAccessorType: Con questa annotazione specichiamo 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 specicare gli elementi inclusi e il loro ordine con la proprieta propOrder() @XmlElement: Indica che il campo va serializzato e permette di specicarne il nome locale ed il namespace. @XmlSchemaType: Mappa un tipo Java ad un tipo semplice predenito. @WebService (Required): Specica che il metodo seguente e limplementazione 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.

Corso di Laboratorio Applicazioni Internet

40 / 74

@WebResult: Indica il nome dellelemento della risposta. Se non e specicato utilizza [nomeoperazione]Response @Oneway: Indica che il metodo e di tipo Oneway. Per una lista esaustiva con utili esempi di utilizzo, consultare le speciche.
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 limplementazione e possibile vericarne la bonta generando il WSDL associato con lapposito comando java2wsdl
4.3.2 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 specica 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 sulloggetto 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 =

Corso di Laboratorio Applicazioni Internet

41 / 74

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(); //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); ...

Sicurezza nelle applicazioni WEB

Le minacce ad un Web Service coinvolgono minacce al sistema host, allapplicazione ed allintera infrastruttura di rete. Per rendere un Web Service sicuro, sono necessari molteplici tecnologie di sicurezza basate sullXML per risolvere problemi inerenti lautenticazione, la gestione degli accessi, le politiche di sicurezza distribuite, la presenza di intermediari. I tradizionali meccanismi per la sicurezza di rete come il Transport Layer Secutiry (SSL/TLS), Virtual Private Networks (VPNs), Internet Protocol Security (IPSec) ed il Multipurpose Internet Mail Exchange (S/MIME) sono tecnologie punto a punto (Pointto-Point). Nonostante queste tecniche siano utilizzare per la sicurezza dei Web Service, non sono sufcienti per fornire contesti di sicurezza da capo a capo (End-to-End). A differenza delle comunicazioni punto a punto infatti un messaggio pu passare attraverso degli intermediari prima di raggiungere la propria destinazione ed alcuni di questi intermediari potrebbero aver bisogno agire su porzioni di messaggio. In generale, un Web Service, per garantire la sicurezza delle comunicazioni, si serve di tecniche basate sul messaggio piuttosto che sul trasporto. Vediamo una rapida panoramica delle pi comuni tipologie di attacco utilizzate per violare le comunicazioni con Web Services.

Corso di Laboratorio Applicazioni Internet

42 / 74

5.1

Minacce per la sicurezza nelle comunicazioni

Le minacce alla sicurezza da cui dobbiamo difenderci sono quelle classiche alle comunicazioni point-to-point con laggiunta di nuove tipologie speciche per le comunicazioni end-to-end. Vediamo le minacce che possono attaccare la sicurezza di un messaggio: Alterazione del messaggio Queste minacce compromettono lintegrit del messaggio qualora un attaccante ne modichi, cancelli o aggiunga una parte. Le parti del messaggio attaccate possono essere gli headers, il body e/o gli attachment. Si contrasta rmando il messaggio. Condenzialit La condenzialit compromessa quando unentit non autorizzata ottiene accesso alle informazioni contenute in un messaggio o in una parte di esso. Questo attacco viene contrastato cifrando il messaggio. Man-in-the-middle Un attaccante pu intercettare il messaggio tra il richiedente e il ricevente nale compromettendo un intermediario SOAP. Le due parti in causa originali continueranno a pensare di comunicare tra loro ignorando lintruso. Lattaccante pu usare i messaggi in transito per alterare il messaggio o violerne la condenzialit. La minaccia di questo attacco pu essere mitigata da tecniche di mutua autenticazione. Spoong Lo spoong un complicato attacco che viola le relazioni di ducia (thrust relationiship). Lattaccante assume lidentit di unentit data dal bersaglio. In questo modo lattaccante pu comunicare con il bersaglio in quali di soggetto dato, anche se non lo e promuovere altri attacchi. Per difendersi sono necessarie robuste tecniche di autenticazione. Replay Attacks In questo attacco un intruso intercetta un messaggio e lo reinvia allagente bersaglio cercando di farlo processare nuovamente. Appropriate tecniche di autenticazione unite a tecniche come il timestamp e numerando la seguenza dei messaggi possono negare questo attacco.

5.2

Web Services Security Requirements

Abbiamo visto che per garantire la sicurezza dei Web Service dobbiamo prepararci a fronteggiare molteplici minacce. Lobiettivo quello di creare un ambiente sono le transazioni a livello messaggio possano essere condotti dal mittente al destinatario nale, assicurandone la sicurezza durante il transito, con o senza la presenza di nodi intermedi. Vediamo alcuni dei requisiti di un simile ambiente: Autenticazione Lautenticazione necessaria per vericare lidentit del fruitore e dellagente erogatore. In alcuni casi, luso della mutua autenticazione non necessaria dal momento che i partecipanti potrebbero non essere direttamente connessi da un singolo hop e attraversare dei nodi intermedi. Ad esempio tra il mittente ed un nodo intermedio solo il primo si autentica. A seconda della politica adottata possibile autenticare il richiedente, il ricevente o entrambi. Ci sono molteplici modi per autenticarsi, quello che analizzeremo lautenticazione tramite certicati su Infrastruttura a Chiave Pubblica (PKI) Autorizzazione Lautorizzazione necessaria per controllare laccesso ad una risorsa. Una volta autenticato, il meccanismo di autorizzazione controlla se il richiedente ha diritto alle operazioni che esegue. Integrit e condenzialit dei dati Le tecniche di integrit dei dati assicurano che le informazioni non sono state alterate o modicate durante la trasmissione. La condenzialit della comunicazione assicura invece che i dati sono accessibili solo ad i destinatari. Le tecniche di rma e codica dei dati possono essere utilizzati per questi scopi. Integrit delle transazioni e comunicazioni. Serve ad assicurare che il processo stato portato a termine correttamente e il usso delle operazioni stato eseguito nellordine corretto. Non-Repudiation La non-repudiation un servizio che garantisce la genuinit di unautenticazione. End-to-End Integrity and Condentiality of Messages Lintegrita e condenzialita dei messaggi deve essere assicurata anche in presenza di intermediari.

5.3

Public Key Infrastructure

Alla base di molte tecniche atte a garantire la sicurezza delle comunicazioni ci sono i principi di autenticazione e cifratura. Lo standard per la sicurezza dei web service WS-Security si appoggia alla Public Key Infrastructure (PKI) per garantire i servizi di cifratura e rma dei messaggi. Per meglio comprenderne il funzionamento, analizziamone larchitettura: Per capire come congurare e utilizzare unimplementazione del WS-Security, necessario aver ben chiaro gli strumenti che stiamo utilizzando. Dal momento che WS-Security fa un uso intenso della crittograa a chiave pubblica, altrimenti conosciuta

Corso di Laboratorio Applicazioni Internet

43 / 74

come crittograa asimmetrica, ricordiamo come funziona. Ogni utente ha una coppia di chiavi crittograche, chiamate chiave pubblica e chiave privata. La chiave pubblica a disposizione di chiunque, mentre quella privata deve essere mantenuta segreta.

Queste chiavi sono matematicamente correlate, ma non possono essere generate luna a partire dallaltra. Con queste chiavi possiamo cifrare e decifrare i messaggi. Ad esempio, se Bob vuole spedire un messaggio ad Alice, pu cifrare il messaggio utilizzando la chiave pubblica di Alice. Questo ci garantisce che solo Alice sar in grado di decifrare e leggere il messaggio grazie alla propria chiave privata.

I messaggi possono anche essere rmati per garantirne lautenticit. Se Alice desidera mandare un messaggio rmato non deve far altro che cifrarlo con la proria chiave privata. A questo punto chiunque pu decifrare il messaggio con la chiave pubblica di Alice vericando che effettivamente sia stato spedito da lei.

Ma cosa accade se David (utente che tenta un attacco) crea una coppia di chiavi e distribuisce la propria chiave pubblica a nome di Alice? Sarebbe in grado di decifrare tutti i messaggi inviati ad Alice o inviare messaggi identicandosi come Alice stessa. Bob quindi deve vericare che la chiave pubblica che utilizza per comunicare con Alice sia effettivamente quella di Alice. Per questo intervengono i certicati (o certicati didentit) che correlano una chiave pubblica e unidentit ed il supporto della Public Key Infrastructure (PKI).

Corso di Laboratorio Applicazioni Internet

44 / 74

La chiave pubblica viene quindi inclusa in un certicato che viene rmato da un terzo partecipante dato (Trent). Chiunque si da di Trent, pu vericare lidentit di Alice. Trent conosciuto come Certication Authority (CA). Pu succedere che Alice non conosce la CA scelta da Bob e quindi non si di, allora Bob pu includere la chiave pubblica della sua CA rmata da una CA di pi alto livello che Alice potrebbe riconoscere. Questo sistema porta ad una gerarchia di certicati ed a complesse relazioni di ducia. Nei sistemi a certicato X.509, il certicato standard pi comune, questa gerarchia sempre un albero top-down, con un certicato radice (root) che rappresenta una CA che non ha bisogno di essere certicata da nessuno. Pu capitare che un certicato venga revocato ed in tal caso viene aggiunto ad una Certicate Revocation List. Un certicato solitamente include: La chiave pubblica Un nome, che pu riferirsi ad una persona, computer o organizzazione Un periodo di validit La URL del revocation center La rma digitale del certicato, prodotta con la chiave privata della CA o, nel caso di autocerticazioni, con la chiave privata del soggetto a cui si riferisce il certicato Quindi, quello che Alice deve avere per rmare e decifrare i messaggi scambiati con Bob ha bisogno: Della propria chiave privata per rmare i messaggi in uscita e decifrare quelli in ingresso Del certicato di Bob per controllare la rma dei messaggi in ingresso o cifrare quelli in uscita Del certicato di Trent per vericare la genuinit del certicato di Bob

Corso di Laboratorio Applicazioni Internet

45 / 74

5.3.1

Setup con Keytool

Abbiamo descritto linfrastruttura a chiave pubblica, descrivendo cos un certicato, una chiave pubblica, una chiave privata. Sappiamo adesso come devono interagire questi strumenti per garantire condenzialit e integrit delle comunicazioni tra soggetti. Adesso vediamo come realizzare in concreto questa infrastruttura:

Per creare la coppia di chiavi pubblica e privata necessarie a garantire lautenticit della controparte e la riservatezza della comunicazione tra client server possibile utilizzare il programma keytool del JDK. Specicando lopzione -genkey il keytool crea una chiave privata e la sua corrispettiva chiave pubblica in un le criptato denominato keystore che viene generato nella directory dalla quale stato lanciato il comando. La chiave pubblica viene inclusa in un certicato self signed allinterno del le prodotto. Un certicato 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 dellunit aziendale [Unknown]: Unipi Specificare il nome dellazienda [Unknown]: Unipi Specificare la localit [Unknown]: Pisa Specificare la provincia [Unknown]: Pisa Specificare il codice a due lettere del paese in cui si trova lunit [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 certicato 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 certicato a Bob, questo lo includer nel suo truststore, ovvero un archivio JKS (lo stesso del keystore) contenente i certicati dei soggetti dati.
[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

Corso di Laboratorio Applicazioni Internet

46 / 74

Nota Per semplicit e possibile mettere certicati 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 certicato 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 dallinfrastruttura a chiave publica.

5.4

WS-Security

La specica WS-Security denisce unastenzione di SOAP che implementa autenticazione, integrit e condenzialit a livello messaggio. Lobiettivo di questa specica 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 rma e cifratura. Questo elemento pu essere presente pi volte allinterno del messaggio se le informazioni di sicurezza da inserire sono destinate a differenti destinatari (indicati con lattributo actor). Due header <Security> non possono avere il solito actor, mentre un header senza actor specicato, 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.4.1

Authentication

Uno dei requisiti centrali di uninfrastruttura di comunicazione la possibili di fornire e ricevere referenze attendibili sullidentit dei soggetti coinvolti nella comunicazione. Queste referenze sono informazioni aggiuntive che trovano naturale collocazione nellheader 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 Certicati X.509 su PKI Vediamo ad esempio di autenticazione tramite Username e Password utilizzata anche in HTTP. Le informazioni sono aggiunte allHeader SOAP tramite lelemento UsernameToken:

Corso di Laboratorio Applicazioni Internet

47 / 74

<!-- 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>

Nel primo caso inviamo il nome dellutente 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 codicata ed accompagnata da un digest (o impronta) in combinazione con la data di creazione per ovviare ad attacchi di tipo replay. Unaltra opzione quella di inviare un certicato X.509 che, avvalendosi dellinfrastruttura a chiave pubblica, fornisce lidentit di un soggetto e garantisce lattendibilit di tali informazioni. Il certicato X.509 viene incluso in un messaggio in un elemento WS-Security chiamato BinarySecurityToken. Lalgoritmo usato per la codica viene specicato nellattributo EncodingType mentre il tipo di certicato specicato in ValueType.
<wsse:BinarySecurityToken ValueType="wsse:X509v3" EncodingType="wsse:Base64Binary" Id="...">MIIHdjCCB...</wsse:BinarySecurityToken>

5.4.2

Signature

Il processo di autenticazione ci fornisce garanzie sullidentit 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 sullintegrit dei dati, garanzie che che possiamo fornire rmandoli. WS-Security si appoggia alla specica XML Signature per rmare un messaggio. Una volta che un messaggio stato rmato, praticamente impossibile poterlo modicare senza che il destinatario non se ne accorga. La rma non impedisce che il messaggio sia letto, ma assicura al ricevente che Le parti rmate del messaggio non sono state modicate dopo la rma Il soggetto che ha apposto la rma lo stesso identicato dal certicato. A grandi linee, quando viene rmata una parte del messaggio, viene aggiungo un ID alla parte in chiaro nel messaggio, il certicato del rmatario, anchesso identicabile univocamente, e un elemento Signature contenente lelemento rmato, i riferimenti al certicato del rmatario e allelemento in chiaro oltre ai dettagli del processo di rma.

Corso di Laboratorio Applicazioni Internet

48 / 74

<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 rmato deve esser prima portato in forma canonica. Per la natura dellXML, non esiste un modo di denire lordine degli attributi, ne di come trattare gli spazi. Il processo di canonizzazione elimina gli spazi bianchi e ordina gli attributi secondo uno specico schema. Il valore della rma contenuto nellelemento SignatureValue, SecurityTokenReference riferisce il certicato del rmatario, mentre Reference contiene un riferimento allelemento rmato. In questo modo possibile rmare parti diverse con certicati diversi, risolvendo i problemi addizionali inerenti alle comunicazioni end to end.
5.4.3 Encryption

Abbiamo adesso gli strumenti e le tecniche per garantire integrit e autenticit del contenuto delle informazioni scambiate. Dobbiamo ancora fornire garanzie di condenzialit 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, lXML Encryption. Quando si codicano i dati, si pu scegliere la codica simmetrica o asimmetrica. La prima richiede di condividere uninformazione segreta. Infatti la chiave usata per cifrare la medesima usata per decifrare. Questa soluzione efcace se si ha un buon controllo sulle parti in causa e un buon livello di ducia 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 codica asimmetrica che abbiamo usato anche per la rma. Mentre nella rma usavamo la chiave privata per rmare e il certicato pubblico per vericare lintegrit, adesso usiamo il certicato pubblico per cifrare e la chiave privata per riportare le parti cifrate in chiaro. [----------------------------------------] MANCA ESEMPIO DI MESSAGGIO CIFRATO [----------------------------------------]
5.4.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 denito lelemento 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:

Corso di Laboratorio Applicazioni Internet

49 / 74

Created: contiene listante di creazione Expires: Settato dal mittente o da un intermediario, determina la validit temporale dei dati. Received: Notica 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 Lelemento Received pu contenere un attributo Actor che indica la URI di chi ha impostato lora 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>

5.5

WS-Security con CXF

Esistono numerose implementazioni della specica WS-Security in Java. Alcune sono studiate per essere usare stand alone (es. XWSS) altre specicatamente per essere integrate in infrastrutture come CXF o Axis2. Vediamo come esempio come congurare 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 limplementazione della logica del servizio da quella dei componenti aggiuntivi dello stack WS-*. Il risultato che lutilizzo di questi componenti spesso si riduce ad un esercizio di congurazione. Dobbiamo modicare il le di congurazione 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>

Corso di Laboratorio Applicazioni Internet

50 / 74

</jaxws:endpoint> </beans>

Lendpoint specica i dettagli di un servizio. Lattributo implementor specica la classe che ne implementa la logica, mentra address specica a quale indirizzo il servizio deve rispondere. Il glio di endpoint, InInterceptor, denisce una sequenza di intercettori che il messaggio in ingresso deve attraversare e viene modicato. Il SAAJInInterceptor si occupa di trasformare il messaggio xml in un SOAPMessage, operazione necessaria per lintercettore successivo. Il WSS4JInInterceptor implementa la specica 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 congurazione che vengono forniti nella forma di coppie chiave/valore. Con una congurazione simile si abilit il client ad utilizzare limplementazione 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" 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 allinterceptor abiliteranno le funzionalit di WS-Security.


5.5.1 Autentication con Username Token

Analizziamo un esempio di congurazione che abilita linserimento di un UsernameToken per lautenticazione. Cominciamo con il Client. Dobbiamo indicare allinfrastruttura 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" />

Corso di Laboratorio Applicazioni Internet

51 / 74

a classe specicata in passwordCallbackClass indica unimplementazione di CallbackHandler e decide quale password inserire in relazione allutente 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 specicare 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:


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.5.2

Signature

Vediamo come congurare i nostri interlocutori per rmare una parte del messaggio. Per prima cosa dobbiamo creare un le di properties contenente le seguenti informazioni necessarie a leggere i keystore e truststore contenenti i certicati 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

Corso di Laboratorio Applicazioni Internet

52 / 74

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 les con le properties dei keystore e truststore, conguriamo gli interceptor afch Alice rmi parte del messaggio:
<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 lalias della chiave privata nel keystore, passwordCallbackClass la classe che deve fornire la password per leggere quella chiave privata ed il signaturePropFile il le di properties per il keystore di Alice. signatureParts indica quali parti si devono rmare. Se non viene specicato viene rmato tutto il Body del messaggio Conguriamo il servizio con le informazioni necessarie per la verica della rma:
<entry key="action" value="Signature" /> <entry key="signaturePropFile" value="bob.ts.properties" />

5.5.3

Encryption

Il processo di cifratura molto simile a quello di rma e le congurazioni si somigliano molto. Il client cos congurato 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 congurazione:


<entry key="action" value="Encrypt" /> <entry key="decryptionPropFile" value="bob.ks.properties" /> <entry key="passwordCallbackClass" value="ServerPasswordCallbackClass" />

5.5.4

Timestamp

Apporre il timestamp al messaggio loperazione pi semplice da congurare. Lato client indichiamo loperazione nella action 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" />

Corso di Laboratorio Applicazioni Internet

53 / 74

5.5.5

Timestamp Signature Encrypt

Vediamo un esempio tipico di congurazione 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 rma e del Body
<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 congurato 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 lordine con cui sono inserite le azioni di sicurezza da applicare al messaggio signicativo.

5.6

Le speciche WS-*

Con WS-* si intende linsieme di speciche in campo Web Service basate su SOAP, tra cui quella che abbiamo appena analizzato, WS-Security. Vista la pletora di speciche 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 lHTTP ha una URL che pu mappare uno specico 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 nellHTTP quando abbiamo pi servizi con la soluta URL HTTP e non abbiamo modo di distinguere a quale servizio destinato il messaggio. La specica WS-Addressing pone rimedio a questo problema. Invece di specicare lendpoint destinatario allesterno nel messaggio, denisce un set di header standard che consentono di specicarlo allinterno 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 specici 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.

Corso di Laboratorio Applicazioni Internet

54 / 74

WS-Security Le minacce ad un Web Service coinvolgono minacce al sistema host, allapplicazione ed allintera infrastruttura di rete. Per rendere un Web Service sicuro, sono necessari molteplici tecnologie di sicurezza basate sullXML per risolvere problemi inerenti lautenticazione, 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, insufcienti per comunicazioni end-to-end tipiche dei Web Service. WS-Security fornisce le speciche 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 lafdabilit della consegna, altri, come lHTTP, non sono afdabili. Di conseguenza abbiamo che lafdabilit deve esser implementata ad un livello superiore. WS-ReliableMessaging specica linfrastruttura necessaria a garantisce la consegna al destinatario del messaggio per livelli di trasporto non afdabili. WS-Transaction: Vedremo a breve i problemi derivanti dallaccesso concorrente ai database e dalluso di database distribuiti. Queste problematiche si spostano e complicano quando usiamo database remoti interfacciati con web service. La specica WSTransaction risolve questi problemi fornendo uninfrastruttura che garantisce la corretta esecuzioned della logica applicativa.

Gestione delle Transazioni in un Database

Abbiamo visto come due entit remote possono scambiarsi messaggi HTTP, poi come dare valore semantico ai dati scambiati con laiuto dellXML ed inne abbiamo visto come sia possibile esporre dei servizi grazie ai Web Service Framework e descriverli tramite WSDL, rendendoli potenzialmente fruibili da molteplici tipi di trasporto ed interoperabili con altre infrastrutture e linguaggi. Ora che sappiamo come veicolare i dati da un punto allaltro della rete, dobbiamo studiare come memorizzarli.

6.1

Gestione del Backend

Continuiamo a sviluppare la gestione delle ordinazioni del nostro shop online. Abbiamo bisogno di un database che memorizzi le informazioni degli ordini ricevuti dal cliente e della merce disponibile in magazzino aggiornata in tempo reale per vericare levadibilit degli ordini. Per prima cosa dobbiamo accedere al database che, per i nostri esempi, un db PostgreSQL (http://www.postgresql.org) installato sulla macchina dbserver con nome dbname.
psql -h dbserver dbname

Creiamo le tabelle necessarie a memorizzare i dati del sistema di ordinazione dello shop online

idOrdine e idArticolo sono chiavi primarie delle tabelle ordini e listino e chiavi esterne in articoli_in_ordine.
CREATE TABLE ordini (idOrdine SERIAL, corriere VARCHAR, PRIMARY KEY (idOrdine)); CREATE TABLE listino (idArticolo VARCHAR NOT NULL, nome VARCHAR NOT NULL, disponibilita SMALLINT DEFAULT (0) NOT NULL , PRIMARY KEY (idArticolo)); CREATE TABLE articoli_in_ordine (idOrdine INTEGER REFERENCES ordini, idArticolo VARCHAR REFERENCES listino, quantita SMALLINT NOT NULL, PRIMARY KEY (idOrdine,idArticolo));

Verichiamo la corretta creazione delle tabelle


user=> \dt List of relations Schema | Name | Type | Owner -------+--------------------+-------+-------public | articoli_in_ordine | table | user

Corso di Laboratorio Applicazioni Internet

55 / 74

public | listino public | ordini (3 rows)

| table | user | table | user

user=> \d ordini; \d listino; \d articoli_in_ordine;

Table "public.ordini" Column | Type | Modifiers ----------+-------------------+-------------------------------------------------------------- idordine | integer | not null default nextval(public.ordini_idordine_seq::text ) corriere | character varying | Indexes: "ordini_pkey" primary key, btree (idordine)

Table "public.listino" Column | Type | Modifiers ---------------+-------------------+-------------------idarticolo | character varying | not null nome | character varying | not null disponibilita | smallint | not null default 0 Indexes: "listino_pkey" primary key, btree (idarticolo)

Table "public.articoli_in_ordine" Column | Type | Modifiers ------------+-------------------+----------idordine | integer | not null idarticolo | character varying | not null quantita | smallint | not null Indexes: "articoli_in_ordine_pkey" primary key, btree (idordine, idarticolo) Foreign-key constraints: "$1" FOREIGN KEY (idordine) REFERENCES ordini(idordine) "$2" FOREIGN KEY (idarticolo) REFERENCES listino(idarticolo)

Popoliamo di dati la tabella Listino:


INSERT INTO listino (idArticolo , nome, disponibilita) VALUES (SONYPS3,Playstation3, 10); INSERT INTO listino (idArticolo , nome, disponibilita) VALUES (NINTWII,Wii, 10); INSERT INTO listino (idArticolo , nome, disponibilita) VALUES (MICRXBOX,Xbox, 10);

Verichiamo il corretto inserimento dei dati:


user=> SELECT * FROM listino; idarticolo | nome | disponibilita ------------+--------------+--------------SONYPS3 | Playstation3 | 10 NINTWII | Wii | 10

Corso di Laboratorio Applicazioni Internet

56 / 74

MICRXBOX (3 rows)

| Xbox

10

Vediamo come eseguire istruzioni SQL tramite Java.

6.2

JDBC e Gestione Backend con Java

da Wikipedia, Lenciclopedia libera JDBC (Java DataBase Connectivity), un connettore per database che consente laccesso alle basi di dati da qualsiasi programma scritto con il linguaggio di programmazione Java, indipendentemente dal tipo di DBMS utilizzato. costituita da una API, raggruppata nel package java.sql, che serve ai client per connettersi a un database. Fornisce metodi per interrogare e modicare i dati. orientata ai database relazionali ed Object Oriented. La piattaforma Java 2 Standard Edition contiene le API JDBC, insieme allimplementazione di un bridge JDBC-ODBC, che permette di connettersi a database relazionali che supportino ODBC. Questo driver in codice nativo e non in Java. Le connessioni JDBC supportano la creazione e lesecuzione delle istruzioni. Esse possono essere comandi SQL come INSERT, UPDATE, DELETE, interrogazioni come SELECT o chiamate a stored procedure. I tipi di istruzioni supportati sono: Statement - listruzione viene inviata al database di volta in volta Prepared Statement - listruzione viene compilata una sola volta, in modo che le chiamate successive siano pi efcienti Callable Statement - usati per chiamare le stored procedure I comandi di scrittura come INSERT, UPDATE e DELETE restituiscono un valore che indica quante righe sono state affette (inserite, modicate, cancellate) dallistruzione. Essi non restituiscono altre informazioni. Le interrogazioni (query) restituiscono un result set (classe ResultSet). possibile spostarsi nel result set riga per riga (tramite il metodo next()). Si pu accedere alle colonne di ogni singola riga chiamandole per nome o per numero. Il result set pu essere costituito da un numero qualsiasi di righe. Esso comprende dei metadati che indicano il nome, il tipo e le dimensioni delle colonne. Il metodo Class.forName() carica la classe del driver JDBC. La linea seguente carica il driver per PostgreSQL nellapplicazione.
Class.forName( "org.postgresql.Driver" );

Poi, il metodo DriverManager.getConnection() crea una connessione.


Connection conn = DriverManager.getConnection( "jdbc:postgresql:database", "username", "password" );

La stringa da utilizzare dipende dal driver JDBC che useremo. Inizia sempre con jdbc:, il resto varia a seconda del prodotto scelto. Una volta stabilita la connessione, occorre passare una istruzione.
Statement stmt = conn.createStatement(); stmt.executeUpdate( "INSERT INTO listino (idArticolo , nome, disponibilita) VALUES ( SONYPSP,PlayStation Portable, 10);" );

I dati vengono prelevati dal database col classico meccanismo delle query. Lesempio sottostante mostra come creare ed eseguire uninterrogazione:
Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery( "SELECT * FROM listino" ); while ( rs.next() ) { int numeroColonne = rs.getMetaData().getColumnCount(); for ( int i = 1 ; i <= numeroColonne ; i++ ) { // I numeri di colonna iniziano da 1.

Corso di Laboratorio Applicazioni Internet

57 / 74

// Vi sono diversi metodi che convertono il valore di una colonna in un certo tipo. // Vedi la documentazione per una lista delle conversioni valide. System.out.println( "COLONNA " + i + " = " + rs.getObject(i) ); } } rs.close(); stmt.close();

raro, per, che un programmatore Java scriva codice in questo stile. Il modo pi diffuso inserire la logica del database in una classe differente e passare le stringhe SQL gi elaborate (magari derivanti anchesse da unaltra classe) e la connessione ai metodi che ne hanno bisogno. Un esempio di Prepared Statement. Si utilizza la connessione dellesempio precedente.
try { Statement stmt = conn.createStatement(); ResultSet rs; PreparedStatement ps = conn.prepareStatement( "SELECT * FROM articoli_in_ordine ao, ordini o WHERE o.idOrdine = ao.idOrdine AND o.corriere = ? AND ao.quantita > ? " ); // Nel prepared statement ps, i punti di domanda denotano le variabili in input, // che possono essere passate attraverso una lista di parametri, per esempio. // Il codice seguente sostituisce i punti di domanda con stringhe o interi. // Il primo parametro indica la posizione in cui il valore va inserito, // il secondo parametro il valore da inserire. ps.setString(1, "FedEx"); ps.setInt(2, 10); // Il ResultSet rs riceve la risposta del database. rs = ps.executeQuery(); while ( rs.next() ) { int numeroColonne = rs.getMetaData().getColumnCount(); for ( int i = 1 ; i <= numeroColonne ; i++ ) { System.out.println( "COLONNA " + i + " = " + rs.getObject(i) ); } rs.close(); ps.close(); stmt.close(); } catch (SQLException e) { // gestione delle eccezioni }

Il rilascio delle risorse allocate durante le operazioni su database, in particolare loggetto connection, particolarmente critica, in quanto il numero totale delle connessioni disponibili limitato e normalmente la connessione al DB non viene rilasciata automaticamente quando non pi utilizzata. Per essere sicuri che una connessione sia chiusa correttamente, anche in caso di eccezione, conviene utilizzare la nally. Inoltre bisogna prestare particolare attenzione a non sollevare ulteriori eccezioni nel blocco finally:
Connection con=null; try { con = DriverManager.getConnection ( URL, "", ""); // utilizzo la connessione ... } catch(Exception e){ ... } finally { // con potrebbe essere null, ma una NullPointerException pur sempre unException try { con.close(); } catch( Exception ignoreMe ) {}

Corso di Laboratorio Applicazioni Internet

58 / 74

6.2.1

SQL Injection

Abbiamo visto due classi per eseguire istruzioni SQL una volta creata una connessione con il Database: gli Statement ed i PreparedStatement. Quale dobbiamo usare? Vediamo un esempio che esplicita la differenza tra le due classi. Supponiamo di esporre un servizio che verica la disponibilita di un prodotto nel listino in base al codice articolo passato dallutente:
public String ricerca(String idArticolo){ ... //Con lo statement: Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery( "SELECT disponibilita FROM listino WHERE idArticolo = " + idArticolo + "" ); //Con il preparedStatement PreparedStatement ps = conn.prepareStatement( "SELECT disponibilita FROM listino WHERE idArticolo = ?" ); ps.setString(1, idArticolo); ResultSet rs = ps.executeQuery(); ... }

la differenza sostanziale sta nel fatto che la prima prende il comando SQL come stringa o come concatenazione di stringhe, mentre il secondo inserisce i parametri effettuandone il type checking e, in caso di stringhe, ne effettua lescape per i caratteri speciali. Usare la classe Statement invece della PreparedStatement quando vengono inseriti parametri di provenienza non data pu esporci ad attacchi di tipo SQL Injection. La SQL injection una tecnica di hacking mirata a colpire le applicazioni web che si appoggiano su un database di tipo SQL. Questo exploit sfrutta linefcienza dei controlli sui dati ricevuti in input ed inserisce codice maligno allinterno di una query SQL. Le conseguenze prodotte sono imprevedibili per il programmatore: lSql Injection permette al malintezionato di autenticarsi con ampi privilegi in aree protette del sito pur non avendone diritto e di visualizzare e/o alterare dati sensibili. Se nellesempio precedente inserisco come valore del parametro idArticolo il valore pippo OR 1 = 1 la query SQL corrispondente risulta:
SELECT disponibilita FROM listino WHERE idArticolo = pippo OR 1=1

che risulta sempre vera selezionando tutti gli articoli. In questo caso risulta un attacco innoquo, ma immaginiamola applicata ad una tabella con dati riservati o ad una query di tipo DELETE. Nel caso della PreparedStatement i caratteri speciali verrebbero adeguatamente trattati prima di esser processati, e la query risultante sarebbe questa
SELECT disponibilita FROM listino WHERE idArticolo = pippo\ OR \1\=\1

che avrebbe i risultati attesi dal programmatore.

6.3

Le Transazioni

Finora abbiamo preso in considerazione le problematiche derivanti dallesecuzione delle singole query SQL. Quando si esegue una serie di query, si passa da uno consistente ad unaltro attraverso stati dinconsistenza. Se ad esempio viene inviato un ordine, quando viene inserito sono effettuate le seguenti operazioni: Verica della disponibilit Aggiunta degli articoli acquistati in articoli_in_ordine

Corso di Laboratorio Applicazioni Internet

59 / 74

Aggiornamento della disponibilit in listino Queste tre query costituiscono una transazione. 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 ununita 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 dellaggiunta di un ordine, se fallisce la query 3, laggiornamento della disponibilit, deve essere annullata anche la 2, linserzione 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, leffetto sulla base di dati dellesecuzione concorrente di pi transazioni equivalente ad unesecuzione seriale delle stesse, cio ad un esecuzione in cui le transazioni vengono eseguite una dopo laltra in un qualche ordine. Quindi, transazioni concorrenti non devono inuenzarsi reciprocamente. Supponiamo di ricevere due ordini contemporaneamente per 8 pezzi del solito articolo ciascuno quando in listino sono disponibili 10 pezzi. La sequenza di operazioni potrebbe essere questa:
-- Lettura dalla disponibilita SELECT disponibilita FROM listino WHERE ... -- dato in memoria temporanea var disponibilitaCorrente = $(disponibilita) -- verifico disponibilita if(disponibilitaCorrente >= 8){ -- Inserisco lerdine INSERT ordine .... UPDATE listino SET disponibilita=(disponibilitaCorrente-8) WHERE ... }

Se le ordinazioni vengono fatte in contemporanea pu succede che viene vericata la disponibilit nel solito momento, accettati entrambi gli ordini e aggiornata la disponibilit in maniera errata. Persistenza, Le modiche 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 afdamento senza gestirle in alcun modo

Lo stato di un database viene fatto evolvere per transazioni e questo signica che una transazione parte sempre da uno stato consistente e deve comunque terminare lasciando il DB in uno stato consistente. Una transazione pu avere due esiti: Commit, indica una terminazione corretta. Lapplicazione dopo aver eseguito le varie operazioni di lettura/scrittura sul database che formavano la transazione esegue una particolare istruzione SQL COMMIT che comunica ufcialmente al Transaction Manager il termine delle operazioni, e fa quindi evolvere la base di dati verso un nuovo stato consistente modicato 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 listruzione 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.

Corso di Laboratorio Applicazioni Internet

60 / 74

Nota Le transazioni devono essere sempre il pi corte possibile per avere il db il meno vincolato possibile (come vedremo in seguito) e con dati il pi aggiornati possibile.

Negli esempi nora visti, ogni singola operazione sul database era considerata una transazione a se stante, poich si stava operando in autoCommit mode. Quando si crea una connessione con JDBC, possibile scegliere in quale modalit si vuole effettuare il commit delle transazioni. Se lautocommit impostato a true ogni modica verr committata ogni volta che una query SQL viene eseguita. Di contro, impostare lautocommit a false permette di decidere quando effettuare il commit delle modiche al db. Viene di seguito mostrato un frammento di codice Java che mostra come avviare una transazione che possa contenere diverse operazioni di lettura e scrittura. Al termine dellapplicazione viene effettuato il commit, o in caso di errori un rollback:
java.sql.Connection connectionDB = null; try{ // inizializzazione driver JDBC Class.forName(...DRIVER...); connectionDB = DriverManager.getConnection(CONNECTION_URL , USERNAME , PASSWORD); // Avvio transazione, impostando lauto-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(); }

Il modello di transazioni usato dal DBMS presenta anche un altro meccanismo di rollback pi avanzato. E possibile denire dei cosidetti savepoint, che vengono utilizzati da una transazione per disfare solo parzialmente il lavoro svolto (il save-point deve essere istanziato in modo da lasciare il database in uno stato consistente). I savepoint sono stati introdotti solo in JDBC 3.0 e vengono usati piuttosto di rado. Comunque, una situazione per cui utile avere a disposizione i savepoint pu essere quando si hanno una serie di modiche, ma non possibile determinare quale sottoinsieme deve essere mantenuto nch non si ha il risultato nale. Il seguente frammento di codice Java, evidenzia come sia possibile utilizzare i savepoint. Lapplicazione precedente viene divisa in due parti, e il save-point permette di non perdere le modiche effettuate nella prima parte, per errori che avvengono nella seconda:
java.sql.Connection connectionDB = null; java.sql.Savepoint savepoint = null; try{ // inizializzazione driver JDBC Class.forName(...DRIVER...); connectionDB = DriverManager.getConnection(CONNECTION_URL , USERNAME , PASSWORD); // Avvio transazione, impostando lauto-commit a false connectionDB.setAutoCommit(false);

Corso di Laboratorio Applicazioni Internet

61 / 74

... LOGICA APPLICATIVA Parte1... // SavePoint savepoint = connectionDB.setSavepoint(); ... LOGICA APPLICATIVA Parte2... // transazione terminata con successo connectionDB.commit(); }catch(Exception e){ // transazione terminata con errore if(savepoint!=null) connectionDB.rollback(savepoint); else connectionDB.rollback(); ... GESTIONE ERRORE ... }finally{ // Chiusura connessione connectionDB.close(); }

6.4

Transazioni concorrenti

Abbiamo visto che una delle propriet che deve garantire un DBMS la serializzabilit delle transazioni ovvero risolvere transazioni concorrenti con i soliti risultati della risoluzione seriale. Nelle applicazioni reali, normalmente pi utenti operano contemporanemente sugli stessi dati e non realisticamente pensabile che le operazioni che effettuano gli utenti sulla base di dati (o comunque la maggior parte delle operazioni) siano esecuzioni seriali. Le esecuzioni parallele sono necessarie per garantire buone prestazioni visto la notevole differenza in diversi ordini di grandezza temporale che hanno le operazioni di I/O dalle operazioni in RAM. Mentre una transazione in attesa del completamento di una operazione di I/O, altre transazioni possono utilizzare la CPU. Lesecuzione concorrente di piu transazioni comporta per la gestione di diversi conitti che possono capitare sulla base dei dati. Di seguito vengono mostrati i quattro problemi base che nascono da transazioni eseguite concorrentemente.
6.4.1 Lost Update

Si ha una perdita di modiche da parte di una transazione a causa di un aggiornamento effettuato da unaltra transazione. Supponiamo ad esempio che due clienti A e B, vogliano vedere il listino del nostro rivenditore, e in caso sia presente una playstation comprarla. Le operazioni elementari comprese nella transazione effettuata da un rivenditore per conto di un cliente sono:
-- Lettura dalla base di dati SELECT disponibilita FROM listino WHERE idArticolo=SONYPS3 -- dato in memoria temporanea var disponibilitaPlaystation = $(disponibilita) if(disponibilitaPlaystation >= 1){ -- Scrittura sulla base di dati UPDATE listino SET disponibilita=(disponibilitaPlaystation-1) WHERE codArticolo= playstation }else{

Corso di Laboratorio Applicazioni Internet

62 / 74

PRINT_VIDEO("Larticolo richiesto non disponibile") } -- Transazione terminata con successo COMMIT

Supponiamo che la quantit di playstation disponibili nel momento in cui i clienti A e B leggano il listino sia 1, e che le transazioni dei due clienti inizino nello stesso istante. Indichiamo con Read(playstation) la lettura della disponibilit dellarticolo e con Write(playstation) lacquisto dellarticolo. La seguente tabella evidenzia il problema del lost update, mostrando la sequenza di operazioni temporali (ogni riga rappresenta un secondo) che lo vericano: sia lutente A che lutente B comprano la playstation nonostante nel magazzino ne esista solamente una. Il problema afora poich B legge la disponibilit di playstation presenti, prima che A (che ha gia letto tale informazione) la modica, comprando la playstation. Time 1 2 3 4 5 6 A Read(playstation) Disponibilit 1 1 0 0 0 0 B Read(playstation) Write(playstation)

Write(playstation) COMMIT

COMMIT

Tabella 6: Esempio Lost Update

6.4.2

Dirty Read

Esiste una dipendenza di una transazione da unaltra non ancora completata con successo che comporta una lettura di dati inconsistenti. Supponiamo di avere un cliente A che desidera vedere il listino del nostro negozio, e in caso sia presente una playstation comprarla. Le operazioni elementari effettuate dal rivenditore per conto del cliente A sono quelle indicate nellesempio precedente di Lost Update. Nello stesso istante abbiamo anche un Distributore che sta consegnando nel magazzino del nostro rivenditore una playstation. Il distributore non riesce per ad ultimare con successo la transazione per un qualsiasi motivo tecnico. Le operazioni elementari comprese nella transazione effettuata dal distributore sono le seguenti:
-- Lettura dalla base di dati SELECT disponibilita FROM listino WHERE codArticolo=playstation -- dato in memoria temporanea var disponibilitaPlaystation = $(disponibilita) -- Scrittura sulla base di dati UPDATE listino SET disponibilita=(disponibilitaPlaystation+1) WHERE codArticolo= playstation -- Transazione terminata con errore per motivi tecnici ROLLBACK

Supponiamo che la quantit di playstation disponibili prima delle transazioni illustrate sia 0, e che le transazioni del cliente e del distributore inizino nello stesso istante. Indichiamo con Read(playstation) la lettura della disponibilit dellarticolo, con Write(playstation) lacquisto dellarticolo e con Insert(Playstation) la consegna in magazzino della playstation da parte del distributore. La seguente tabella evidenzia il problema del dirty read, mostrando la sequenza di operazioni temporali (ogni riga rappresenta un secondo) che lo vericano: A compra una playstation che non esiste realmente nel magazzino. Il problema afora poich A legge la quantita di playstation presenti, dopo linserimento in magazzino da parte del distributore, ma prima che esso termini la transazione.

Corso di Laboratorio Applicazioni Internet

63 / 74

Time 1 2 3 4 5 6

Read(playstation) Write(Playstation) COMMIT

Disponibilit 0 1 1 0 0 0

Distributore Read(playstation) Insert(playstation) ROLLBACK

Tabella 7: Esempio Dirty Read

Nota Le varie implementazioni dei database esistenti non permettono il fenomeno del Dirty Read. Tipicamente, nei database, una transazione viene eseguita allinterno di una read safe transaction dove non possibile leggere dati modicati da altre transazioni che per non sono ancora stati committati (read uncommitted). Per questo motivo il test fornito con i sorgenti solo teoricamente in grado di riprodurre il fenomeno del dirty read.

6.4.3

Unrepeatable Read

Viene effettuata una analisi inconsistente di dati da parte di una transazione causata da aggiornamenti prodotti da unaltra. Supponiamo di avere un cliente A che desidera vedere il listino del nostro rivenditore per conoscere il numero di playstation presenti. Supponiamo che il cliente A legga il numero di playstation disponibili, dopodich si fermi un attimo a riettere se comprare o meno tutte le playstation valutando il costo di ognuna. Presa la decisione rilegge il numero di playstation disponibili e vede aumentata la disponibilit. Nello stesso istante abbiamo anche un Distributore che sta consegnando nel magazzino del nostro rivenditore una playstation. Le operazioni elementari comprese nella transazione effettuata dal rivenditore sono le seguenti:
-- Lettura dalla base di dati SELECT disponibilita FROM listino WHERE codArticolo=playstation -- dato in memoria temporanea var disponibilitaPlaystation = $(disponibilita) -- Simulazione Attesa in secondi SLEEP(5) -- Lettura dalla base di dati SELECT disponibilita FROM listino WHERE codArticolo=playstation

Mentre le operazioni elementari comprese nella transazione effettuata dal distributore sono le seguenti:
-- Lettura dalla base di dati SELECT disponibilita FROM listino WHERE codArticolo=playstation -- dato in memoria temporanea var disponibilitaPlaystation = $(disponibilita) -- Scrittura sulla base di dati UPDATE listino SET disponibilita=(disponibilitaPlaystation+1) WHERE codArticolo= playstation -- Transazione terminata con successo COMMIT

Supponiamo che la quantit di playstation disponibili prima delle transazioni illustrate sia 1, e che le transazioni del cliente e del distributore inizino nello stesso istante. Indichiamo con Read(playstation) la lettura della disponibilit dellarticolo e con Insert(Playstation) la consegna in magazzino della playstation da parte del distributore. La seguente tabella evidenzia il problema

Corso di Laboratorio Applicazioni Internet

64 / 74

dellunrepeatable read, mostrando la sequenza di operazioni temporali (ogni riga rappresenta un secondo) che lo vericano: A legge in due intervalli di tempo diversi allinterno della stessa transazione due disponibilit diverse per lo stesso articolo. Le due letture sono tra loro inconsistenti. In questo esempio le conseguenze non sono gravi, ma se pensiamo ad un ambiente bancario questo fenomento puo provocare problemi gravi. Ad esempio se un banker A somma limporto di due conti correnti mentre un banker B esegue un trasferimento di fondi dalluno allaltro, il broker A potrebbe riportare un totale errato. Time 1 2 3 4 5 6 A Read(playstation) Disponibilit 1 1 2 2 2 2 Distributore Read(playstation) Insert(playstation) COMMIT

Read(playstation)

Tabella 8: Esempio Unrepeatable Read

6.4.4

Phantom Row

(Caso particolare di Dirty Read). Si presenta quando una transazione inserisce o cancella tuple che unaltra transazione dovrebbe logicamente considerare. Supponiamo di avere un cliente A che desidera vedere il listino del nostro rivenditore. Le operazioni elementari effettuate dal rivenditore per conto del cliente A sono quelle indicate nellesempio precedente di Unrepeatable Read. Nello stesso istante abbiamo anche un Distributore che sta consegnando nel magazzino del nostro rivenditore un articolo non ancora presente nel listino: un personal computer. Le operazioni elementari comprese nella transazione effettuata dal distributore sono le seguenti:
- Scrittura sulla base di dati INSERT INTO listino (codArticolo,descrizione,disponibilita,prezzo) VALUES (pc,personal computer,1,1200.00) -- Transazione terminata con successo COMMIT

Supponiamo che la quantit di playstation disponibili prima delle transazioni illustrate sia 2, che non esistano personal computer nel listino e che le transazioni del cliente avvia e del distributore inizino nello stesso istante. Indichiamo con Read(*) la lettura degli articoli presenti nel listino, con Insert(pc) la consegna in magazzino del personal computer da parte del distributore. La seguente tabella evidenzia il problema del phantom row, mostrando la sequenza di operazioni temporali (ogni riga rappresenta un secondo) che lo vericano: con una prima lettura A vede nel listino solo la playstation, mentre con una seconda lettura intrapresa sempre nella stessa transazione, ma dopo un intervallo temporale in cui il distributore ha inserito nel listino anche un pc, A vede oltre alla playstationa anche il personal computer. Con la stessa ricerca, il risultato ottenuto la seconda volta diverso poich A vede una phantom row rappresentata dallentry del personal computer. Le conseguenze potrebbero essere gravi se ad esempio la prima ricerca servisse per produrre una pagina HTML che visualizza il listino al cliente A, mentre la seconda servisse per acquistare realmente tutti gli articoli. Lutente A convinto di avere acquistato solo 2 playstation, ma si vedr arrivare via corriere anche un personal computer. Time 1 2 3 4 5 6 7 A Read(*) Disponibilit Playstation 2 2 2 2 2 2 2 Tabella 9: Esempio Phantom Row Disponibilit PC 0 0 0 1 1 1 1 Distributore

Insert(personal_computer COMMIT

Read(*)

Corso di Laboratorio Applicazioni Internet

65 / 74

6.5

Lock

Per ovviare alle anomalie dovute agli accessi concorrenti possibile utilizzare i lock che possono essere considerati come dei vincoli di uso esclusivo che un utente pu ottenere su determinate tabelle per il tempo necessario a svolgere le transazioni. Ogni transazione, prima di accedere ad un dato, deve richiedere il permesso al DBMS tramite loperazione di lock e rimane in attesa no a quando il DBMS non gli concede tale lock e quindi il permesso di proseguire: il lock un semaforo per laccesso ai dati. Esistono due tipi di lock: lock in lettura (Share), le transazioni lo richiedono ogni volta che devono svolgere delle operazioni di lettura. Questo lock condivisibile con altre transazioni di sola lettura, ma non con transazioni che modicano la tabella. Questo lock garantisce che i dati nella tabella non verranno in alcun modo alterati nch non viene rilasciato. lock in scrittura (Exclusive), le transazioni lo richiedono ogni volta che devono compiere operazioni di scrittura. Questo lock esclusivo viene ottenuto solo da una transazione per volta e impedisce alle altre transazioni qualsiasi tipo di accesso alla tabella mentre consente alla transazione che lha ottenuto operazioni di lettura e scrittura. Visti vincoli di fruizione che introducono i lock importante farne uso solo quando strettamente necessario e per il minor tempo possibile. I lock possono essere realizzati sia a livello applicativo che forniti come istruzioni SQL dai dabase. Lo standard SQL non prevede le istruzioni per acquisire/rilasciare lock, quindi le varie implementazioni fornite dai database sono proprietarie e possono avere una diversa sintassi. Inoltre alcuni database forniscono altri lock che possiedono una semantica classicabile tra i due principali lock descritti in questo paragrafo. La tabella sottostante mostra i comandi forniti dai database PostgreSQL, MySQL e Oracle per acquisire e rilasciare lock, e inoltre indica come acquisire lock in share e exclusive mode. Database PostgreSQL MySQL Oracle Lock LOCK TABLE tab1[,tab2,...] IN lockmode MODE LOCK TABLES tab1 lockmode [,tab2 lockmode] LOCK TABLE tab1 IN lockmode MODE Unlock Automatico unlock alla ne della transazione UNLOCK TABLES Automatico unlock alla ne della transazione

Tabella 10: Implementazioni dei lock per i database pi diffusi Vediamo come i lock possano risolvere i problemi dovuti agli accessi concorrente dei dati visti in precedenza.
6.5.1 Lost Update

Lock share. Nella Sezione 6.4.1 sia lutente A che lutente B comprano la playstation nonostante nel magazzino ne esista solamente una. Il problema afora poich B legge la disponibilit di playstation presenti, prima che A (che ha gia letto tale informazione) la modica, comprando la playstation. Se proviamo a risolvere il problema con il lock share, otteniamo che sia A che B possono leggere il listino, ma poi ne A ne B riescono ad acquisire il lock per poter poi acquistare realmente la playstation (modicare la disponibilita nella tabella listino) e si crea una situazione di deadlock. Quando B prova a modicare il listino cerca di acquisire il lock esclusivo in scrittura e nello stesso tempo aspetta che A lasci il lock share in lettura. Lo stesso vale per A. Il DBMS rilever il deadlock e decider come intervenire. La tabella sottostante evidenzia lesempio provato con PostgreSQL, il quale rileva il deadlock facendo terminare con errore la transazione del cliente A e lanciando il seguente messaggio:
ERROR: deadlock detected Dettaglio: Process 29771 (!B!) waits for RowExclusiveLock on relation 20599 (!listino!) of database 20542; blocked by process 29772 (!A!). Process 29772 (!A!) waits for RowExclusiveLock on relation 20599 (!listino!) of database 20542; blocked by process 29771 (!B!).

Lock exclusive. Se proviamo a risolvere il problema con il lock exclusive, otteniamo che le transazioni A e B vengono eseguite in maniera seriale e quindi solo uno dei due clienti riesce a comprare la playstation. Il problema del lost update non si verica.

Corso di Laboratorio Applicazioni Internet

66 / 74

Time 1 2 3 4 5 6 7 8 9

A LOCK SHARE listino Read(playstation)

Disponibilit 1 1 1 1 1 1 1 0 0

Write(playstation)... LOCK EXCLUSIVE listino DEADLOCK: kill A

Tabella 11: Esempio Lost Update con lock share Time 1 2 3 4 5 6 7 8 A LOCK EXCLUSIVE listino Read(playstation) Disponibilit 1 1 1 1 0 0 0 0

Write(playstation) COMMIT

Tabella 12: Esempio Lost Update con lock exclusive


6.5.2 Unrepeatable Read

Lock share. Nella Sezione 6.4.3 stato mostrato un esempio del problema. Il cliente A legge il numero di playstation disponibili, dopodich si ferma un attimo a riettere se comprare o meno tutte le playstation valutando il costo di ognuna. Presa la decisione rilegge il numero di playstation disponibili e vede aumentata la disponibilit. Nello stesso istante abbiamo anche un Distributore che sta consegnando nel magazzino del nostro rivenditore una playstation. Il problema risolvibile utilizzando i lock share. Se lutente A acquisisce il lock share per leggere il listino. A questo punto il distributore non riesce ad ottenere un lock exclusive per inserire una nuova playstation no a che la transazione dellutente A non e completata. Time 0 1 2 3 4 5 6 7 8 9 A LOCK SHARE listino Read(playstation) Disponibilit 1 1 1 1 1 1 1 1 2 2

Read(playstation) COMMIT

Tabella 13: Esempio Unrepeatable Read con lock share Lock exclusive. Se proviamo a risolvere il problema con il lock exclusive, otteniamo che le transazioni del cliente A e del distributore vengono eseguite in maniera seriale. Il problema del unrepeatable read non si verica.

Corso di Laboratorio Applicazioni Internet

67 / 74

6.5.3

Phantom Row

Lock share. Nella Sezione 6.4.4 stato mostrato un esempio del problema.(Caso particolare di Dirty Read). Un cliente A desidera vedere il listino del nostro rivenditore. Nello stesso istante abbiamo anche un Distributore che sta consegnando nel magazzino del nostro rivenditore un articolo non ancora presente nel listino: un personal computer. Con una prima lettura A vede nel listino solo la playstation, mentre con una seconda lettura intrapresa sempre nella stessa transazione, A vede una phantom row rappresentata dallentry del personal computer. Il problema risolvibile utilizzando i lock share. Se lutente A acquisisce il lock share per leggere il listino. A questo punto il distributore non riesce ad ottenere un lock exclusive per inserire un nuovo personal computer no a che la transazione dellutente A non e completata. Time 1 2 3 4 5 6 7 8 9 10 11 A LOCK SHARE listino Read(*) Disponibilit Playstation 2 2 2 2 2 2 2 2 2 2 2

Read(*) COMMIT

Tabella 14: Esempio Phantom Row Lock exclusive. Se proviamo a risolvere il problema con il lock exclusive, otteniamo che le transazioni del cliente A e del distributore vengono eseguite in maniera seriale. Il problema del phantom row non si verica.
6.5.4 SELECT ... FOR UPDATE

Nella Sezione 6.5.1 si visto come il lock share non utilizzabile in un contesto di lost update, poich permette ad entrambi i clienti A e B di leggere la tabella listino e solo ad uno dei due di aggiornarla, ma possiede la particolari di fare terminare con errore la transazione di uno dei due, in seguito al rilevamento di un deadlock. Nello stesso tempo, il lock exclusive non efcente poich mette un lock su tutta la tabella. Con il comando SELECT ... FOR UPDATE possibile invece leggere lultimo dato disponibile e consistente nella base di dati, settando un lock esclusivo su ciasuna riga letta. Il fenomeno del lost update non avviene, entrambe le transazioni terminano con successo (solo uno dei 2 clienti riesce a comprare la playstation) e la tabella non viene comunque negata alle transazioni che vogliono accedere ad altre righe. Con la SELECT FOR UPDATE quindi possibile risolvere il problema del lost update e dellunrepeatable read in maniera piu efcente del lock esclusivo, anche se in pratica la soluzione la stessa ma mirata alla riga della tabella che veniva modicata con le due anomalie. Il problema del phantom row non invece risolvibile con questa istruzione, poich il fenomeno inuenza altre righe della tabella.

6.6

Livelli di isolamento

Nella precedente sezione stato illustrato come risolvere i problemi di accesso concorrente alla base di dati, attraverso dei lock sulle tabelle o direttamente sulle righe delle tabelle. E stato per anche illustrato come il meccanismo dei lock non SQL Standard e quindi ogni database ne possiede una implementazione differente. Un meccanismo SQL standard per risolvere i problemi dovuti ad accesso concorrente dei dati sono i livelli di isolamento. A seconda del livello di isolamento con il quale vengono eseguite le transazioni viene garantito dallo standard che determinati problemi di accesso concorrente non possono vericarsi. I livelli di isolamento sono quattro e li elenchiamo in ordine crescente: READ UNCOMMITTED. In questo livello per una transazione sono visibili gli aggiornamenti effettuati da altre transazioni, anche se non ancora consolidati. E un comportamento non propriamente transazionale, che pu comportare seri problemi di

Corso di Laboratorio Applicazioni Internet

68 / 74

consistenza dei dati.Inoltre quasi un livello di isolamento teorico, visto che le pi diffuse implementazioni dei database esistenti non lo implementano. Tipicamente, nei database, una transazione viene eseguita allinterno di una read safe transaction dove non possibile leggere dati modicati da altre transazioni che per non sono ancora stati committati (read uncommitted). Se implementato da un database, sarebbe utile per velocizzare le letture anche se andrebbe comunque utilizzato solo se non vi sono rischi su letture inconsistenti. READ_COMMITED. In questo livello per una transazione sono visibili gli aggiornamenti effettuate da altra transazioni solo dopo che sono stati consolidati. REPEATABLE_READ. In questo livello perch un aggiornamento diventi visibile deve essere non solo consolidato, ma anche che la transazione che legge deve essere terminata. Con questo livello una stessa lettura effettuata allinterno di una transazione ritorner sempre lo stesso risultato, anche se la entry letta stato aggiornata (con commit) da unaltra transazione. SERIALIZABLE. Questo livello contiene le propriet del livello precedente di repeatable_read, con in pi il fatto che una semplice lettura di un dato da parte di una transazione provoca il blocco degli aggiornamenti a tutte le altre transazioni no alla sua terminazione. E come se ogni SELECT venisse effettuata con la clausola LOCK IN SHARE MODE (vedi Sezione 6.5). La seguente tabella illustra quali anomalie, dovute ad accesso concorrente ai dati, possono avvenire con i livelli di isolamento descritti. Livello di Lost Update isolamento READ Possibile UNCOMMITTED READ_COMMITTED Possibile UNREPEATABLE_READ Possibile SERIALIZABLE Non possibile Dirty Read Possibile Non possibile Non possibile Non possibile Unrepeatable Read Possibile Possibile Non possibile Non possibile Phantom Row Possibile Possibile Possibile Non possibile

Tabella 15: Livelli di isolamento e le anomale dovute ad accesso concorrente ai dati Il comando SQL standard per impostare un livello di isolamento il seguente:
SET TRANSACTION ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }

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.TRANSACTION_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 lauto-commit a false connectionDB.setAutoCommit(false); ... LOGICA APPLICATIVA ... // transazione terminata con successo connectionDB.commit(); }catch(Exception e){

Corso di Laboratorio Applicazioni Internet

69 / 74

// transazione terminata con errore connectionDB.rollback(); ... GESTIONE ERRORE ... }finally{ // Chiusura connessione connectionDB.close(); }

6.6.1

READ COMMITTED e SERIALIZABLE

Nei pi diffusi database (es. PostgreSQL, Oracle) possibile richiede qualsiasi dei quattro livelli di isolamento delle transazioni, ma internamente allimplementazione 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 isolamento deniscono solamente quali anomalie, dovute agli accessi concorrenti dei dati, NON DEVONO succedere, ma non deniscono quale fenomeno POSSONO succere. Viene di seguito descritto ulteriormente il comportamento tipico delle implementazioni dei due livelli di isolamento Read Committed e Serializable. Read Committed. Normalmente il livello di default dei database. SELECT. Quando una transazione viene eseguita con questo livello, una select vede solo i dati committati prima che la query inizi; non vedr mai dati non ancora committati o cambiamenti committati durante lesecuzione della query (vede comunque gli effetti di precedenti aggiornamenti eseguiti nella sua transazione, anche se non ancora committati). In pratica una select vede uno snapshot del database effettuato nellistante in cui la query inizia ad essere eseguita. Due successive select possono vedere differenti dati, anche se esse vengono eseguite allinterno della stessa singola transazione, se altre transazioni committano cambiamenti durante lintervallo che intercorre tra la prima e la seconda query. UPDATE,DELETE e SELECT...FOR UPDATE. Hanno lo stesso comportamento della SELECT per quanto riguarda la ricerca delle righe interessate alloperazione: troveranno solo le righe che sono state committate allinizio dellesecuzione del comando. Ogni modo, se una riga interessata stata gi aggiornata (o cancellata o selezionata con una select for update) da unaltra transazione, questo comporter che la transazione verr bloccata ad attendere che la transazione che ha effettuato la modica termini con un commit o con un rollback. Se la transazione che ha effettuato la modica termina con un rollback, la transazione in attesa pu procedere aggiornando la riga trovata. Se invece la transazione che ha effettuato la modica termina con un commit, la transazione in attesa ignorer la riga se questa stata cancellata, o cercher di applicare la sua operazione alla versione aggiornata della riga. La condizione di ricerca del comando (clausole WHERE) rivalutata per vedere se la nuova versione aggiornata della riga la soddisfa ancora e solo in tal caso procede con laggiornamento. Il livello di isolamento delle transazioni fornite dal Read Committed adeguato per molte applicazioni, ed il livello pi facile (essendo normalmente il default) e performante da utilizzare (non vi sono lock). Serializable. Fornisce il livello di isolamento pi stretto. Questo livello emula una serializzazione delle transazioni in esecuzione. Con questo livello per le applicazioni devono prepararsi a gestire eventuali fallimenti durante la serializzazione (eventualmente riprovando la stessa operazione no a che non viene eseguita con successo). SELECT. Quando una transazione viene eseguita con questo livello, una select vede solo i dati committati prima che la transazione inizi; non vede mai dati non ancora committati o dati cambiati e committati da transazioni concorrenti durante la sua esecuzione (vede comunque gli effetti di precedenti aggiornamenti eseguiti nella sua transazione, anche se non ancora committati). Il comportamento della select diverso da quello con il livello Read Commited; con il livello serializable una select vede uno snapshot del database effettuato prima dellinizio della transazione, mentre con il livello Read Committed lo snapshot riguarda listante in cui la query inizia ad essere eseguita. Successive esecuzioni della stessa query dentro la transazione forniscono quindi sempre il solito risultato. UPDATE,DELETE e SELECT...FOR UPDATE. Hanno lo stesso comportamento della SELECT per quanto riguarda la ricerca delle righe interessate alloperazione: troveranno solo le righe che sono state committate allinizio della transazione. Ogni modo, se una riga interessata stata gi aggiornata (o cancellata o selezionata con una select for update) da unaltra transazione, questo comporter che la transazione verr bloccata ad attendere che la transazione che ha effettuato la modica termini con un commit o con un rollback. Se la transazione che ha effettuato la modica termina con un rollback, la transazione in attesa

Corso di Laboratorio Applicazioni Internet

70 / 74

pu procedere aggiornando la riga trovata. Se invece la transazione che ha effettuato la modica termina con un commit, la transazione in attesa dovr terminare con errore, effettuando rollback. Il DBMS generer un errore dovuto al conitto di serializzazione, simile al seguente:
Could not serialize access due to concurrent update

Con il livello Serializable una transazione non pu modicare righe cambiate da altre transazioni dopo che la transazione serializzata iniziata. Quando una applicazione riceve questo messaggio di errore, pu scegliere di abortire la transazione corrente e riprovare lintera transazione dallinizio, oppure di riprovare solamente laggiornamento (se i dati interessati allapplicazione non vanno in conitto con i dati modicati dalle altre transazioni).
Nota Solo le updating transaction (transazioni che contengono tra le operazioni elementari, comandi di UPDATE,DELETE,SELECT...FOR UPDATE) possono avere bisogno di riprovare i vari comandi in seguito ad errore di serializzazione. Le read-only transaction non hanno mai conitti dovuti alla serializzazione

Di seguito viene illustrato un frammento di codice Java che gestisce un conitto di serializzazione e riprova ad eseguire la transazione no a che loperazione non va a buon ne:
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 lauto-commit a false connectionDB.setAutoCommit(false); boolean serializzazioneConConflitti = true; while(serializzazioneConConflitti){ try{ ... LOGICA APPLICATIVA ... // transazione terminata con successo connectionDB.commit(); // marco transazione terminata senza conflitti serializzazioneConConflitti = false; }catch(SQLException e){ // transazione terminata con errore connectionDB.rollback(); if("Could not serialize access due to concurrent update".equals(e.getMessage())) { // conflitto di serializzazione, devo riprovare ad eseguire interamente la logica applicativa serializzazioneConConflitti = true; }else{ // Ho un errore non dovuto alla serializzazione delle transazioni. // devo gestire lerrore senza riprovare ad eseguire la logica applicativa serializzazioneConConflitti = false; ... GESTIONE ERRORE ...

Corso di Laboratorio Applicazioni Internet

71 / 74

} } } // end-while }catch(Exception e){ ... GESTIONE ERRORE ... }finally{ // Chiusura connessione connectionDB.close(); }

Il livello di isolamento Serializable garantisce pienamente una vista consistente dei dati presenti nel database . Ognimodo le applicazioni devono gestire le i conitti di serializzazione e quindi non si ha una vera e propria serializzazione dellesecuzione delle transazioni. Dal momento che ri-effettuare complesse transazioni pu essere costoso, questo livello raccomandato solo quando le updating transactions contengono complessit che possono provocare errori se gestite con Read Committed.
6.6.2 Livello di isolamento Serializable vs Serializzazione Matematica

Lintuitivo termine di esecuzioni serializzate che due transazioni concorrenti vengono eseguite strettamente in maniera seriale. importante sottolineare che il livello di isolamento Serializable non sufcente a garantire una vera serializzazione. Il seguente esempio evidenzia questo aspetto. Consideriamo la seguente tabella: Supponiamo di avere due transazioni serializzate A e B. A Tipo 1 1 2 2 Quantita 10 20 100 200

Tabella 16: Livello di isolamento Serializable vs Serializzazione composta dalle seguenti operazioni elementari:
-- Effettua la somma delle quantita delle righe di tipo 1 SELECT SUM(Quantita) as somma FROM TABELLA WHERE Tipo=1 -- Memorizza la quantita in una variabile in memoria var quantita = $(somma) -- Insert nuova quantita=30 INSERT INTO TABELLA VALUES (2,quantita) COMMIT

B composta dalle seguenti operazioni elementari:


-- Effettua la somma delle quantita delle righe di tipo 2 SELECT SUM(Quantita) as somma FROM TABELLA WHERE Tipo=2 -- Memorizza la quantita in una variabile in memoria var quantita = $(somma) -- Insert nuova quantita=300 INSERT INTO TABELLA VALUES (1,quantita) COMMIT

Corso di Laboratorio Applicazioni Internet

72 / 74

Se le due transazioni vengono eseguite concorrentemente (supponiamo vengano avviate nello stesso istante) con un livello serializable, otterremmo che A inserisce una nuova riga (2,30) e B inserisce una nuova riga (1,300). Nessuno dei problemi dovuti allaccesso concorrente ai dati avvenuto, e il livello serializable ha mascherato le phantom-row che possono essere accadute durante le due transazioni. Ad esempio A poteva effettuare la lettura della somma quando B aveva gi committato, e in questo caso era presenta la phantom row (1,300). Lesempio con il livello serializable per non corrisponde con quanto succederebbe con una vera serializzazione matematica. In tale contesto si avrebbe sicuramente che il risultato dipende dallordine di esecuzione delle due transazioni, mentre con il livello serializable ci potrebbe non essere vero (e le due transazioni eseguite parallelamente senza che nessuna inuenza laltra). In una serializzazione matematica se A viene eseguita prima di B, B calcoler la somma 330, non 300; viceversa se B viene eseguita prima di A, sar A a calcoler la somma considerando la nuova entry inserita da B. Per garantire la vera serializzazione matematica, necessario per un DBMS utilizzare i LOCK (vedi Sezione 6.5), poich una transazione non pu inserire una riga che gi stata selezionata attraverso le condizioni di WHERE in unaltra transazione concorrente. Con i lock potremmo ottenere una serializzazione matematica del nostro esempio, considerando lordine. Ad esempio una volta che la transazione A ha iniziato la query SELECT ... WHERE Tipo=1, un lock deve negare alla transazione B di inserire una nuova riga con Tipo 1 no a che la transazione A non terminata. In pratica un lock previene una phantom row impedendo ad unaltra transazione di inserire una nuova entry che inuenzi le condizioni di WHERE, mentre il livello serializable permette linserimento, ma non lo visualizza durante la read (select) effettuata dalla transazione. Nella maggior parte delle applicazioni reali non vi la necessita di avere una serializzazione matematica, e quindi i livelli di isolamento sono pi che sufcenti per gestire correttamente lesecuzioni concorrenti delle transazioni.
6.6.3 Lost Update

Read Committed. Nella Sezione 6.4.1 sia lutente A che lutente B comprano la playstation nonostante nel magazzino ne esista solamente una. Il problema afora poich B legge la disponibilit di playstation presenti, prima che A (che ha gia letto tale informazione) la modica, comprando la playstation. Il problema si verica anche impostando un livello di isolamento Read Committed, poich con questo livello lupdate di A riguarder una riga aggiornata dalla transazione di B. Questo comporter che la transazione A verr bloccata ad attendere che la transazione B che ha effettuato la modica termini con un commit o con un rollback. Non appena la transazione B che ha effettuato la modica termina con un commit, la transazione in attesa applicher la sua operazione alla versione aggiornata della riga. Time 1 2 2 3 4 5 6 7 8 A SET TRANSACTION ISOLATION LEVEL READ COMMITTED Read(playstation) Disponibilit 1 1 1 1 0 0 0 0 0

Write(playstation)...WAIT... WAIT... WAIT... COMMIT Tabella 17: Esempio Lost Update con Read Committed

Serializable. Se proviamo a risolvere il problema con il livello Serializable, otteniamo che lupdate della transazione A interessa una riga gi aggiornata dalla transazione B; questo comporter che A verr bloccata ad attendere che la transazione B che ha effettuato la modica termini con un commit o con un rollback. Quando la transazione B che ha effettuato la modica termina con un commit, la transazione A in attesa terminer con errore, effettuando rollback e segnalando il conitto di serializzabilit.
6.6.4 Unrepeatable Read

Read Committed. Nella Sezione 6.4.3 stato mostrato un esempio del problema. Il cliente A legge il numero di playstation disponibili, dopodich si ferma un attimo a riettere se comprare o meno tutte le playstation valutando il costo di ognuna. Presa

Corso di Laboratorio Applicazioni Internet

73 / 74

Time 1 2 2 3 4 5 6 7 8

A SET TRANSACTION ISOLATION LEVEL SERIALIZABLE Read(playstation)

Disponibilit 1 1 1 1 0 0 0 0 0

Write(playstation)...WAIT... WAIT... WAIT... ERROR: could not serialize access due to concurrent update Tabella 18: Esempio Lost Update con Serializable

la decisione rilegge il numero di playstation disponibili e vede aumentata la disponibilit. Nello stesso istante abbiamo anche un Distributore che sta consegnando nel magazzino del nostro rivenditore una playstation. Il problema si verica anche impostando un livello di isolamento Read Committed poich sono visibili gli aggiornamenti effettuate da altre transazioni una volta che queste effettuano il commit. In pratica una select vede uno snapshot del database effettuato nellistante in cui la query inizia ad essere eseguita. Due successive select possono vedere differenti dati, anche se esse vengono eseguite allinterno della stessa singola transazione. Non viene mostrata una tabella esemplicativa, poich identica (tranne che per limpostazione del livello di isolamento a Read Committed) a quella descritta nella Sezione 6.4.3. Serializable. Se proviamo a risolvere il problema con il livello Serializable, il problema non si verica poich la select non vede mai dati non ancora committati o dati cambiati e committati da transazioni concorrenti durante la sua esecuzione. In pratica con il livello serializable una select vede uno snapshot del database effettuato prima dellinizio della transazione. Non viene mostrata una tabella esemplicativa, poich identica (tranne che per limpostazione del livello di isolamento a Serializable) a quella descritta nella Sezione 6.4.3.
6.6.5 Phantom Row

Read Committed. Nella Sezione 6.4.4 stato mostrato un esempio del problema.(Caso particolare di Dirty Read). Un cliente A desidera vedere il listino del nostro rivenditore. Nello stesso istante abbiamo anche un Distributore che sta consegnando nel magazzino del nostro rivenditore un articolo non ancora presente nel listino: un personal computer. Con una prima lettura A vede nel listino solo la playstation, mentre con una seconda lettura intrapresa sempre nella stessa transazione, A vede una phantom row rappresentata dallentry del personal computer. Il problema si verica anche impostando un livello di isolamento Read Committed poich sono visibili gli aggiornamenti effettuate da altra transazioni una volta che queste effettuano il commit. In pratica una select vede uno snapshot del database effettuato nellistante in cui la query inizia ad essere eseguita. Due successive select possono vedere differenti dati, anche se esse vengono eseguite allinterno della stessa singola transazione. Non viene mostrata una tabella esemplicativa, poich identica (tranne che per limpostazione del livello di isolamento a Read Committed) a quella descritta nella Sezione 6.4.4. Serializable. Se proviamo a risolvere il problema con il livello Serializable, il problema non si verica poich la select non vede mai dati non ancora committati o dati cambiati e committati da transazioni concorrenti durante la sua esecuzione. In pratica con il livello serializable una select vede uno snapshot del database effettuato prima dellinizio della transazione. Non viene mostrata una tabella esemplicativa, poich identica (tranne che per limpostazione del livello di isolamento a Serializable) a quella descritta nella Sezione 6.4.4.

6.7

Transazioni Distribuite

Abbiamo elencato le caratteristiche che un database deve garantire (Atomicit, Persistenza, Consistenza e Serializzabilit) e le problematiche derivanti dallesecuzione di transazioni concorrenti che minano queste qualit. Le transazioni nora prese in considerazione agivano su tabelle residenti sul solito database. Cosa accade se le nostre transazioni agiscono su tabelle

Corso di Laboratorio Applicazioni Internet

74 / 74

distribuite su pi database? Garantire latomicit della transazione diventa pi complicato, perch dobbiamo tener conto dei possibili fallimenti di pi database e non di uno soltanto. Uno degli algoritmi pi diffusi per risolvere questo problema il Protocollo di aggiornamento a due fasi.
6.7.1 Two Phase Commit (2PC)

da Wikipedia, lEnciclopedia Liberia Il protocollo lavora nella seguente maniera: un nodo designato come coordinatore mentre il resto dei nodi della rete sono identicati come gruppi. Il protocollo assume che ci sia un immagazzinamento stabile a ogni nodo con un write-ahead log, che nessun nodo sia corrotto per sempre, il dato nel write-ahead log non mai perso o corrotto, e che due nodi qualsiasi possano comunicare tra di loro. Lultimo presupposto non molto restrittivo, dato che la comunicazione di rete pu tipicamente essere deviata. I primi due presupposti invece sono pi forti; se un nodo viene totalmente distrutto i dati possono andare perduti. Il protocollo iniziato dal Coordinatore dopo che lultimo punto della transazione sia stato raggiunto. I gruppi quindi rispondono con un messaggio di concordo o con un messaggio di aborto a seconda del successo. Fase di preparazione/richiesta 1. Il coordinatore invia un messaggio di richiesta di validazione a tutti i gruppi. 2. I gruppi eseguono la transazione no al punto dove gli stata richiesta la convalida. Ognuno di essi scrive una nota al loro undo log e una al loro redo log. 3. Ogni gruppo risponde con un messaggio di concordo se la transazione ha avuto successo, o con un messaggio di aborto se la transazione fallita. 4. Il coordinatore aspetta di ricevere un messaggio da ogni singolo gruppo. Fase di validazione Successo. Se il coordinatore riceve un messaggio di concordo da tutti i gruppi durante la fase di richiesta di validazione: 1. Il coordinatore invia un messaggio di richiesta di validazione a tutti i gruppi. 2. I gruppi eseguono la transazione no al punto dove gli stata richiesta la convalida. Ognuno di essi scrive una nota nellundo log e una nel redo log. 3. Ogni gruppo risponde con un messaggio di concordo se la transazione ha avuto successo, o con un messaggio di aborto se la transazione fallita. 4. Il coordinatore aspetta di ricevere un messaggio da ogni singolo gruppo. Fallimento. Se almeno un gruppo invia un messaggio di aborto durante la fase di richiesta di validazione: 1. Il coordinatore invia un messaggio di contenimento a ogni gruppo. 2. Tutti i gruppi annullano le modiche temporanee usando l undo log, e rilasciano i blocchi applicati durante la transazione. 3. Tutti i gruppi inviano un riscontro al coordinatore. 4. Il coordinatore completa la transazione quando il riscontro stato ricevuto. Il pi grande svantaggio del protocollo di aggiornamento a due fasi quello di essere un protocollo bloccante. Ci signica che gli altri processi che competono per le risorse bloccate, dovranno attendere no a che i blocchi non saranno rilasciati. Se il coordinatore permanentemente fuori uso, i gruppi in attesa di una convalida/aborto saranno bloccati indenitamente, a meno che non ottenga una decisione globale da qualche altro gruppo. Di contro, se un gruppo a non rispondere, il coordinatore non sar bloccato, ma, dopo un intervallo temporale, pu determinare il fallimento del processo e liberare le risorse bloccate.

Potrebbero piacerti anche