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