Appunti integrativi di Tecnologie di progettazione di sistemi informatici e di telecomunicazione
Classe 5a a.s. 2017/2018
Maria Grazia Maffucci
Giuliano Bellucci
ottobre 2017 - maggio 2018
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
Contact mariagrazia@maffucci.cc
Indice
Programmazione per la comunicazione in rete usando i socket 7
A1 Tecnologie e protocolli delle reti di computer 8
A5 Socket programming in linguaggio Java 8
Protocolli applicativi di rete 8
Implementazione del servizio 9
Progettazione di un protocollo 9
Frammentazione e riassemblaggio 10
Controllo della connessione 11
Servizio confermato o non confermato 11
Multiplexing e demultiplexing 12
Esempi di protocolli applicativi di rete 12
Protocolli applicativi di rete esistenti 12
Protocolli applicativi di rete implementabili 13
Esempi di protocolli applicativi di rete implementabili 14
CLIL Writing and Speaking - Networking, TCP and UDP protocols 15
CLIL Writing and Speaking - Java sockets 15
I sistemi distribuiti: modelli architetturali hardware e software 16
Caratteristiche dei sistemi distribuiti 18
Storia dei sistemi distribuiti e modelli architetturali 22
CLIL Listening and Speaking - Quantum Computers 22
Architetture distribuite hardware 23
MIMD a memoria fisica condivisa 25
MIMD a memoria fisica distribuita 26
CLIL Listening and Speaking - Flynn Taxonomy and Cluster Architecture 27
Architetture distribuite software 27
CLIL Listening and Speaking - n-Tier Architecture 30
CLIL Listening and Speaking - Middleware 32
A6 Gestione dei documenti in formato XML 34
Esempi di parsing di un documento XML con DOM 34
A8 Realizzazione di web-service di tipo REST in linguaggio Java 47
CLIL Reading - What is a servlet and HTTP 47
A7 Web-service di tipo REST: interazione con linguaggio Java 60
Ragioni dell’uso dei Web service 60
Accoppiamento debole (loosely coupled) 60
A8 Realizzazione di web-service di tipo REST in linguaggio Java 61
I principi dell’architettura RESTful 61
CLIL Listening and Writing- Introduction to Web Services REST 62
Identificazione delle risorse 62
CLIL Listening and Writing - REST and HTTP 63
CLIL Listening and Writing - Resource URIs 63
CLIL Listening and Writing - Collection URIs 64
Utilizzo esplicito dei metodi HTTP 64
CLIL Listening and Writing - HTTP Methods 66
Idempotenza dei metodi HTTP 66
CLIL Listening and Writing - Method Idempotence 67
CLIL Listening and Writing - REST Response 68
Collegamenti tra risorse e il principio HATEOAS 68
CLIL Listening and Writing - HATEOAS 71
Stato delle risorse e dell’applicazione 72
CLIL Listening - The Richardson Maturity Model 73
CLIL Listening - Developing RESTful APIs with JAX-RS 74
REST e sicurezza HTTP (cenni) 75
Sicurezza con “sessioni REST” e tramite servizi di terze parti (cenni) 76
OpenID e OAuth: gestione esterna della sicurezza 77
Esempi di Web service di tipo REST in linguaggio Java 78
Web service per operazioni CRUD su database 84
Server di connessione al database EsempioREST 95
Web service REST SimpleRestDb 110
Esercizi sui protocolli di comunicazione 124
Aspetti progettuali di un protocollo di comunicazione 127
Esercizi su XML, Java parser e Web service 129
Esercizi di progettazione di architetture distribuite 133
Grande Fratello (trasporre) 142
I socket e la comunicazione in rete 144
I sistemi distribuiti: modelli architetturali hardware e software 144
STUDIARE: G. Meini, F. Formichi, Tecnologie di progettazione di sistemi informatici e di telecomunicazioni, vol. 3, Zanichelli, 2017, pp.2-39.
STUDIARE: G. Meini, F. Formichi, Tecnologie di progettazione di sistemi informatici e di telecomunicazioni, vol. 3, Zanichelli, 2017, pp.94-140.
E’ l’insieme di regole utilizzate da due entità per scambiarsi informazioni, specificando cosa deve essere comunicato, in che modo e quando.
Degli esempi di protocolli sono quelli utilizzati nell’architettura di rete a strati TCP/IP.
La pila protocollare TCP/IP si occupa di fornire i servizi associati ad ogni applicazione di rete, come ad es. SMTP, FTP, HTTP, ecc.
Il livello delle applicazioni supporta diverse applicazioni e, per ognuna di esse offre determinati servizi.
Se il servizio è di tipo client-server il protocollo dovrà prevedere sempre la gestione di queste due diverse entità e delle relative comunicazioni. Ciò vuol dire che i processi dovranno essere almeno due, il client e il server.
Nella pila protocollare TCP/IP la comunicazione avviene tra strati adiacenti tramite una interfaccia che definisce quali servizi offrire allo strato inferiore e quali sono le operazioni corrispondenti che lo strato inferiore può invocare per ottenere il servizio.
I servizi offerti da uno strato sono disponibili presso un SAP (Service Access Point) che identifica il punto di accesso (lo “sportello”) dove è disponibile il servizio, e generalmente viene implementato come una coda di messaggi. Ogni SAP ha un indirizzo univoco che deve essere specificato dall’utente per richiedere il servizio.
L’utente invia una IDU (Interface Date Unit) al SAP contenente informazioni di controllo e dati.
Il livello protocollare preleva una IDU dal SAP ed esegue la corrispondente primitiva del servizio creando una o più PDU (Protocol Data Unit) da inviare ai suoi pari. Il livello protocollare per trasmettere i PDU richiederà un servizio al livello sottostante.
Ogni protocollo prevede lo scambio di pacchetti denominati in modo generale PDU (Protocol Data Unit). Di solito un PDU conterrà:
Lo header e il tail sono usati dal protocollo per controllare la comunicazione, ad esempio, lo header contiene informazioni utilizzate dall’entità del protocollo come:
Ogni protocollo deve definire le regole per gestire i seguenti aspetti (non necessariamente un protocollo deve gestirli tutti):
L’indirizzamento permette di identificare univocamente una entità nella rete. Esistono diversi livelli di indirizzamento e ogni livello ha una propria visibilità:
Gli indirizzi assegnati agli host o ai processi possono essere di tipo unicast, multicast o broadcast. Diversamente si possono creare circuiti virtuali usando degli identificatori assegnati alla connessione, che permettono di ridurre l’overhead di comunicazione e stabiliscono un instradamento fissato per la connessione.
La frammentazione divide un messaggio in blocchi di dimensione fissata e ogni blocco sarà inserito in un particolare PDU. Se viene prevista questa fase nel protocollo dovrà essere necessariamente prevista anche la fase di riassemblaggio per riottenere il dato originale.
Vantaggi:
Svantaggi:
Definisce quali informazioni di controllo devono essere aggiunte ai dati e come vengono organizzate all’interno di un PDU, ad es. indirizzi, controllo degli errori, controlli del protocollo. Tramite il processo di incapsulamento viene aggiunto uno header ed eventualmente un tail affinché l’entità protocollare paritaria possa capire come gestire il payload (dati) del PDU.
Se il protocollo non è orientato alla connessione (connectionless) questa fase non è prevista.
Se il protocollo è invece orientato alla connessione (connection-oriented) deve prevedere le seguenti fasi:
Il protocollo può essere affidabile prevedendo una tecnica di conferma dei PDU ricevuti oppure può non essere affidabile se non prevede tale conferma. Le modalità di conferma possono essere molto semplici come la conferma di ogni singolo PDU appena questo viene ricevuto, oppure prevedere tecniche più raffinate come la conferma cumulativa adottata dal protocollo TCP.
Il controllo degli errori serve ad individuare eventuali alterazioni dei dati o delle informazioni di controllo. Può essere implementato in due fasi, prevedendo inizialmente l’individuazione degli errori e poi la ritrasmissione dei dati. A volte vengono usati dei codici che riescono ad individuare e correggere automaticamente gli errori.
Il controllo del flusso è utilizzato dal ricevente per limitare la velocità con cui la sorgente gli invia i dati. Infatti la sorgente non deve inviare più dati di quanti il ricevente possa riceverne.
Per multiplexing si intende la corrispondenza di più connessioni ad un determnato livello protocollare, che confluiscono in una sola connessione al livello inferiore. Il multiplexing consente di aggregare i dati e di ottimizzare l’efficienza della trasmissione.
Il ricevente dovrà necessariamente implementare un meccanismo di disaggregazione (demultiplexing) dei dati per consegnare i dati al corretto destinatario.
Se la trasmissione richiede determinati livelli di servizi, questi dovranno essere negoziati nella fase di creazione della connessione (caso complesso). I servizi di trasmissione possono prevedere:
I protocolli applicativi di rete consentono la comunicazione tra processi utente su sistemi diversi e forniscono diversi servizi di comunicazione
Di seguito sono elencati alcuni protocolli applicativi di rete comunemente usati su Internet:
Oltre ai normali protocolli applicativi di rete già standardizzati, è possibile crearne dei propri, come ad esempio:
Esempi di descrizioni di protocolli applicativi di rete li potrete trovare nelle seguenti specifiche di progetto per l’avvio della relativa progettazione:
Answer to the following questions.
Answer to the following questions.
Le architetture dei sistemi informativi si sono sviluppate ed evolute nel corso degli anni passando da schemi centralizzati a modelli distribuiti, più aderenti alla necessità di decentralizzazione e cooperazione delle moderne organizzazioni.
Si parla di sistema informatico centralizzato quando i dati e le applicazioni risiedono in un unico nodo elaborativo.
Un sistema informatico distribuito invece è costituito da un insieme di applicazioni logicamente indipendenti che collaborano per il perseguimento di obiettivi comuni attraverso una infrastruttura di comunicazione hardware e software.
Le applicazioni che costituiscono un sistema distribuito hanno ruoli diversi all’interno del sistema stesso, e sono identificate nel seguente modo:
Alcuni esempi di sistemi distribuiti sono a livello macroscopico Internet, mentre a livello di singolo individuo può essere una PAN (Personal Area network) che fa riferimento al wearable computing,
o una rete di sensori.
I sistemi distribuiti presentano una serie di caratteristiche che possono essere interpretate sia come vantaggi, sia come svantaggi, ma indubbiamente, vista l’attuale affermazione dell’uso dell'architettura distribuita per tutti i grandi sistemi informatici, è evidente che gli svantaggi sono considerati come un prezzo adeguato da pagare per garantirsi i vantaggi che derivano dall’uso di un sistema distribuito.
Uno dei principali vantaggi dei sistemi distribuiti è l’affidabilità in quanto la ridondanza dei sistemi hardware e software, quando prevista ed implementata in modo sistematico, permette al sistema di sopravvivere al guasto di un suo componente, introducendo al limite un livello di inefficienza nei tempi di risposta.
Questa caratteristica è raggiungibile solo se vengono predisposti gli strumenti hardware e software in grado di intervenire automaticamente al verificarsi di situazioni indesiderate. Ad esempio, l’implementazione di algoritmi operativi che permettano alle entità non guaste di sostituire quella danneggiata per non interrompere il funzionamento del sistema sono un esempio di utilizzo di sistemi di backup che vengono attivati nel momento in cui l’elemento principale non sia più operativo.
Risulta però evidente che la difficoltà di realizzazione e i costi connessi di un simile sistema crescono con l’aumentare del livello di affidabilità che si vuole raggiungere, quindi non è sempre implementabile un sistema completamente affidabile.
Un’altra importante caratteristica dei sistemi distribuiti è la capacità di integrare componenti eterogenei tra loro, sia per tipologia hardware (dai PC ai mainframe, dagli smartphone ai tablet), sia per sistema operativo.
Per garantire questa caratteristica è fondamentale che ogni componente si possa interfacciare allo stesso modo con il sottosistema di comunicazione del sistema distribuito, indipendentemente dalle differenze hardware e di sistema operativo. L’interfaccia di comunicazione gioca quindi un ruolo fondamentale per permette di rendere trasparenti all’intera rete i componenti dello strato inferiore del sistema.
Un classico esempio di integrazione è la possibilità di connettere dispositivi di nuova generazione con sistemi legacy[1], che di fatto utilizzano tecnologie obsolete e spesso incompatibili con quelle più recenti.
Altri esempi di interfacce di comunicazione che soddisfano il criterio di integrazione è la tecnologia Ethernet per la connessione di dispositivi di rete, anche prodotti da società diverse, o la piattaforma Web che permette a sistemi operativi e architetture hardware di interconnettersi e scambiarsi informazioni.
Un altro esempio di interfaccia volta al perseguimento dell’integrazione è costituita dalle API (Application Program Interface) che rappresentano un insieme di procedure, utilizzabili dal programmatore, utili alla stesura di applicazioni di rete. Le API forniscono l’interfaccia tra l’applicazione e lo strato di trasporto realizzando quella che si chiama astrazione tra hardware e programmazione, svincolando in tal modo gli sviluppatori dalle problematiche di comunicazione e trasferimento dati che sono i compiti degli strati inferiori. In particolare i socket API mettono a disposizione del programmatore gli strumenti necessari a sviluppare programmi di connessione che implementano il protocollo di comunicazione desiderato.
Il linguaggio XML (eXtensible Markup Language) è un’altra tecnologia che permette di integrare sistemi diversi in quanto è stato creato appositamente per favorire lo scambio di informazioni nel Web e permettere un’agevole ed efficiente pubblicazione di dati complessi.
In sostanza la caratteristica di integrazione è resa possibile dalla definizione di protocolli standard o sistemi architetturali de facto che favoriscono la portabilità di applicazioni fra sistemi operativi diversi, conservando la medesima interfaccia utente, e la interoperabilità tra componenti diversi.
Con il termine trasparenza si intende il concetto di considerare il sistema distribuito non come un insieme di componenti ma come un sistema di elaborazione unico.
Lo scopo è quello di rendere trasparente all’utente la presenza di un sistema distribuito composto da molteplici entità, anche fisicamente distanti fra loro e con configurazioni mutevoli nel tempo, in modo che abbia invece la percezione di utilizzare un singolo elaboratore.
L’ANSA nell’ISO 10746, Reference Model of Open Distributed Processing, identifica otto forme di trasparenza, delle quali le prime due risultano fra le più importanti da implementare nell’ambito un sistema distribuito:
Il perseguimento di ognuna di queste forme di trasparenza necessita di investimenti e competenze tecniche per realizzarle.
Il rapporto costo-prestazione di un sistema distribuito permette effettivamente di considerare economico un investimento, anche ingente, per la sua realizzazione. Di fatti i costi, spesso notevoli, per realizzare un simile sistema vengono normalmente ripartiti fra una massa considerevole di utilizzatori, che beneficiano a tal punto dei servizi offerti dal sistema distribuito da essere disposti a sostenerne la spesa.
Rispetto ai costi di realizzazione di un sistema centralizzato di un tempo, basato su mainframe, il costo di un sistema distribuito risulta sicuramente maggiore di diversi ordini di grandezza, ma a differenza della vecchia tecnologia, il costo procapite dell’uso del sistema viene ripartito su un maggior numero di soggetti, fornendo anche un margine di guadagno di molto superiore.
Inoltre la possibilità di condividere risorse hardware e software comporta vantaggi economici dovuti alla condivisione di apparecchiature speciali di costo elevato, che diversamente potrebbero risultare sotto-utilizzate.
Infine un sistema distribuito ben progettato dovrebbe risultare facilmente scalabile permettendo l’aggiunta di componenti al fine di migliorarne le prestazioni realizzando un bilanciamento di carico.
La complessità di un sistema distribuito ha richiesto lo sviluppo di hardware opportuno per garantire l’interconnessione dei suoi componenti e l’instradamento dei messaggi, in modo da fronteggiare adeguatamente la crescente richiesta dell’utenza.
Inoltre ha portato allo sviluppo di nuovi paradigmi di programmazione e di nuovi linguaggi, modificando radicalmente l’idea di programmazione e di sviluppo di sistemi software.
Infine sono sorte molteplici problematiche legate alla sicurezza che un tempo non era possibile neanche prevedere; l’accesso remoto alle risorse e lo sviluppo delle transazioni commerciali ha reso necessario lo sviluppo di sofisticate tecniche volte alla protezione dei dati e delle infrastrutture.
L’obiettivo primario della progettazione dei computer è stato sempre quello di aumentarne le prestazioni, soprattutto in termini di velocità, in modo che si potessero eseguire il maggior numero di istruzioni nel minore tempo possibile.
Le velocità raggiungibili hanno però un limite superiore imposto dalle leggi della fisica sulla velocità della luce, che nel vuoto raggiunge i 300.000 Km/s, mentre nel rame è possibile approssimarla a 200.000 Km/s. Da ciò si deduce che per raggiungere frequenze di lavoro dell’ordine dei GigaHertz, e quindi tempi di esecuzione delle istruzioni dell’ordine dei ns, è necessario che le distanze percorse nei conduttori interni non superino i 20 cm (v = (200.000*103)/109 = 0,2 m/s). Le dimensioni dei componenti dei computer sono quindi importanti per non introdurre ritardi nell’esecuzione delle istruzioni.
L’eccessiva riduzione delle dimensioni degli elaboratori ha però provocato problematiche legate sia alla dissipazione dell’energia, sia alla meccanica quantistica che entra in gioco pesantemente provocando fenomeni non controllabili, ma che allo stesso tempo ha indirizzato lo sviluppo dei nuovi computer quantistici.
L’evoluzione dei computer non quantistici ha optato per una riorganizzazione dell’elaborazione che consentisse di costruire macchine sempre più performanti gestendo le informazioni in modo diverso, e quindi definendo architetture di elaborazione diverse, sia dal punto di vista costruttivo (hardware), sia dal punto di vista logico (software).
Dal punto di vista hardware si è passati alla realizzazione di macchine e sistemi dotati di più CPU in modo da avere più potenza di calcolo senza esasperare i limiti di velocità di ogni singola CPU, sviluppando di macchine parallele o macchine ad architettura parallela.
Per il software lo sviluppo delle architetture distribuite hanno permesso di seguire, se non addirittura anticipare, i cambiamenti delle architetture hardware e dei loro sistemi operativi.
In these videos you will find how normal computer evolution has taken a strange path due to quantum mechanics. Watch the three videos and discuss the topic with your classmate.
Esistono diverse possibilità per classificare le architetture hardware a seconda dei fattori che si prendono come riferimento.
La tassonomia di Flynn[2] è un sistema di classificazione delle architetture dei calcolatori ideata da Michael J. Flynn fra gli anni ‘60 e ‘70 che permette di identificare i sistemi di calcolo a seconda della molteplicità del flusso di istruzioni e del flusso dei dati che possono gestire. In seguito questa classificazione è stata estesa con una sottoclassificazione per considerare anche il tipo di architettura della memoria.
A seconda di come si combinano il flusso di dati e il flusso delle istruzioni abbiamo quattro possibili macro-gruppi, che possono ulteriormente suddividersi considerando anche l’architettura della memoria.
L’architettura SISD (Single Instruction Single Data) è quella presente negli elaboratori che prevedono l’uso di una singola CPU, nei quali il flusso di istruzioni è unico e quindi viene eseguito un solo programma alla volta che agisce su un singolo flusso di dati. Dopo l’esecuzione di una istruzione si passa alla successiva seguendo un processo esecutivo rigorosamente sequenziale.
Un esempio teorico di questo tipo di architettura è costituito dalla macchina di Von Neumann, mentre nella realtà tutti i PC e i mainframe di vecchia generazione erano macchina ad una sola CPU e quindi la loro architettura era SISD.
Le architetture SIMD (Single Instruction Multiple Data) sono composte da molte unità di elaborazione che eseguono contemporaneamente la stessa istruzione ma lavorano su insiemi di dati diversi. Generalmente, il modo di implementare le architetture SIMD è quello di avere un processore principale che invia le istruzioni da eseguire contemporaneamente ad un insieme di elementi di elaborazione che provvedono ad eseguirle. Il processore principale spesso è ospitato all'interno di un calcolatore convenzionale che provvede a supportare anche l'ambiente di sviluppo.
I sistemi SIMD sono utilizzati principalmente per supportare computazioni specializzate in parallelo, come ad esempio i supercomputer vettoriali usati per particolari applicazioni scientifiche dove si lavora su grandi matrici, gli array processor che in origine erano progettati per applicazioni di intelligenza artificiale e di calcolo simbolico, anche se versioni successive ebbero successo nelle scienze applicate che richiedevano elevate potenza di calcolo, e le GPU (Graphics Processing Unit) utilizzate per la grafica ad alto livello.
Le architetture MISD (Multiple Instruction Single Data) prevedono molteplici sistemi che eseguono processi diversi che agiscono su un unico flusso di dati. Questo tipo di architettura, pur non essendo particolarmente diffusa, viene usata generalmente in termini di tolleranza ai guasti (fault tolerance), in cui sistemi eterogenei devono operare sullo stesso flusso di dati dovendo concordare sul risultato finale. Un esempio reale è presente nel computer di controllo del volo dello Space Shuttle.
L’architettura MIMD (Multiple Instruction Multiple Data) comprende tutte le tipologie di elaboratori composti da più unità centrali di elaborazione indipendenti che possono lavorare su stream di dati anch’essi indipendenti, raggiungendo un parallelismo a livello di thread.
Per questa architettura viene effettuata una ulteriore classificazione delle in base alla suddivisione della memoria fisica:
Le prime sono anche conosciute con il nome di multiprocessor, mentre le seconde con quello di multicomputer.
I sistemi MIMD multiprocessori sono architetture a memoria fisica condivisa (shared memory) che costituisce un unico spazio di indirizzamento condiviso tra tutti i processori. Dato che la comunicazione tra processi avviene mediante variabili condivise, risulta necessario implementare opportuni meccanismi di sincronizzazione per regolare gli accessi alla memoria, in modo da coordinare i diversi processi per gestire la competizione alle risorse comuni.
In questo tipo di architettura non necessariamente i processori devono avere un’unica memoria in comune centralizzata, possono anche avere ciascuno una propria memoria e condividerne una parte con gli altri processori, in modo da realizzare una memoria condivisa distribuita.
I sistemi MIMD multicomputer sono architetture che non hanno una memoria condivisa e quindi la comunicazione avviene mediante lo scambio di messaggi (message passing) esplicito effettuato mediante apposite procedure (send e receive), come avviene utilizzando i socket. Ogni computer possiede una propria area di memoria privata, non indirizzabile da parte dei processori remoti.
Un tipico esempio di questa architettura è fornito dai cluster di PC.
Un computer cluster è un insieme di computer connessi tra loro tramite una rete, e nasce dall’esigenza di distribuire fra i vari computer del cluster elaborazioni molto complesse, scomponendole in sotto-elaborazioni separate che vengono eseguite in parallelo. Questo ovviamente aumenta la potenza di calcolo del sistema, solitamente garantendo un’alta disponibilità di servizio. Per contro la gestione dell’infrastruttura può avere maggiori costi e complessità di gestione, pur risultando economico nella sua implementazione, fortemente scalabile e affidabile.
Si può parlare propriamente di cluster di PC quando un insieme di computer completi e interconnessi ha le seguenti proprietà:
Teoricamente un cluster di PC ha una potenza di calcolo pari alla somma di quelle dei singoli computer che lo costituiscono, e differisce da una normale rete di PC principalmente:
In base alla definizione e considerando le unità centrali come entità, un sistema cluster di PC corrisponde all’insieme delle macchine MIMD a memoria privata. L’enorme vantaggio dei cluster di PC è quello di affrontare calcoli particolarmente onerosi che sarebbero molto lunghi o impossibili con un solo computer e di ridurre il gravoso inconveniente del tempo di elaborazione anche per quei problemi che si potrebbero risolvere con un singolo computer ma al prezzo di un tempo molto elevato.
In these videos you will understand the Flynn’s taxonomy and why cluster architecture is widely used in big enterprise. Watch the videos and discuss the topics with your classmate.
Come per l’hardware, anche per il software si è avuta avuto una evoluzione nelle architetture distribuite che spesso hanno anticipato i cambiamenti delle architetture hardware e dei loro sistemi operativi, passando attraverso tre fasi principali: l’architettura centralizzata, l’architettura client-server e l’architettura multi-tier.
La prima architettura di elaboratori, definita anche 1 tier o ad un livello (anni ‘70), prevedeva un’unica unità centrale di elaborazione (mainframe) a cui erano collegati terminali privi di capacità computazionali. Lo scopo dei terminali remoti era quello di inviare i dati all’unità centrale e di ricevere i relativi risultati per visualizzarli, mentre l’unità centrale gestiva i dati, la logica di business e l’interfaccia utente.
I terminali remoti avevano solitamente caratteristiche omogenee e l’unità centrale replicava per ciascuno di essi un’area di memoria riservata, facendo evolvere singolarmente i singoli task avviati dai diversi terminali remoti.
I principali vantaggi di questa architettura erano l’elevata sicurezza delle funzioni, l’efficienza esecutiva, in quanto non era previsto un overhead per la comunicazione remota in fase elaborativa e uno sviluppo architetturale relativamente semplice. Per contro l’hardware era estremamente costoso, poco scalabile e il sistema presentava molti problemi di integrazione in quanto erano solitamente sistemi chiusi.
Alla base dell’evoluzione di questa architettura c’è principalmente la nascita della rete Internet, che pur non essendo inizialmente quella che oggi conosciamo, presenta possibilità che spingono fortemente verso una maggiore distribuzione. Contemporaneamente inizia una spinta di informatizzazione presso le piccole e medie imprese vista la massiva produzione di client più prestanti e a costi contenuti se paragonati ai mainframe usati nelle grandi aziende.
L’architettura client-server, definita anche architettura a due livelli o 2 tier (anni ‘80), a differenza della precedente prevede dei client con una loro capacità di elaborazione in grado di iniziare la richiesta di un servizio ad un server, che invece si occupa di elaborare la richiesta e inviare la risposta al client.
Un client può richiedere più servizi a server diversi e un server può ricevere più richieste da molteplici client. Inoltre un server può assumere il ruolo anche di client nel momento in cui necessita di un servizio offerto da un altro server per soddisfare la richiesta iniziale del client.
In questo tipo di architetture due client possono collaborare tra loro unicamente attraverso uno o più server che permettono la coordinazione e la condivisione dei dati. Client e server possono essere tecnologicamente diversi, sia come hardware che come sistema operativo, e in generale questa architettura è quella che meglio si presta a far comunicare e cooperare entità non omogenee.
La comunicazione tra client e server avviene mediante protocolli ben definiti e generalmente sono sistemi aperti che riescono a raggiungere un buon livello di integrazione. Generalmente l’hardware può o meno essere costoso a seconda della potenza di calcolo richiesta, richiede un discreto costo di amministrazione, soprattutto per questioni di sicurezza e la scalabilità dipende dalla progettazione effettuata a monte.
Nel tempo questa architettura ha previsto lo sviluppo di due modelli:
Le principali ragioni che hanno portato un’evoluzione rispetto a questa architettura sono ascrivibili alla standardizzazione dei protocolli di rete, al crescente ampliamento della banda disponibile, alla necessità di distribuire l’informazione e i servizi su media diversi e di gestire molte transazioni on line, oltre alla necessità di evitare, per questioni di sicurezza, il single point of failure.
Per alleggerire il carico elaborativo dei server vennero sviluppati sistemi multilivello (anni ‘90), nei quali avvenne la separazione delle funzionalità logiche del sistema in livelli software diversi. Allo scopo vennero introdotti un insieme di strumenti, complessivamente identificati con il termine middleware, che rappresenta uno strato software che si colloca sopra al sistema operativo ma sotto i programmi applicativi, rappresentando l’evoluzione dei sistemi operativi distribuiti.
Il modello a multi-tier richiede una chiara definizione delle interfacce e dei protocolli di comunicazione usati fra i vari livelli, in modo che possano richiedere ed offrire servizi fra loro. Inoltre questa separazione logica delle funzionalità permette di apportare modifiche ai vari livelli, limitando al minimo l’impatto sugli altri livelli.
In genere l’architettura multi-tier prevede lo sviluppo di applicazioni su tre livelli distinti:
I principali vantaggi di questa architettura sono la scalabilità, la possibilità di applicare sistemi di sicurezza a livello di singolo servizio e con livelli diversi a seconda del layer considerato.
Sicuramente un’architettura di questo tipo è complicata sia a livello progettuale, sia a livello di sviluppo e amministrazione.
In ogni caso questo modello permette lo sviluppo di architetture distribuite che prevedono la specializzazione dei server per servizi diversi, bilanciare il carico di lavoro tra varie entità e l’integrazione di servizi che risiedono su server diversi.
In these videos you will understand how n-tier architecture works. Watch the videos and discuss the topics with your classmate.
Il middleware ha lo scopo di realizzare la comunicazione e le interazioni tra i diversi componenti software di un sistema distribuito e rappresenta una classe di tecnologie software sviluppate per aiutare gli sviluppatori nella gestione della complessità e della eterogeneità presenti nei sistemi distribuiti.
Il middleware è quindi un software di connessione che consiste di un insieme di servizi e/o ambienti di sviluppo di applicazioni distribuite che permettono a più entità (processi, oggetti, ecc.), residenti su uno o più elaboratori, di interagire attraverso una rete di interconnessione a dispetto di differenze nei protocolli di comunicazione, architetture dei sistemi locali e sistemi operativi.
Di conseguenza il middleware garantisce:
Di fatto la presenza di questo strato rende facilmente programmabili i sistemi distribuiti offrendo una specifica modalità di interazione, che può essere per esempio un paradigma di interazione basato sulla chiamata di procedure remote (RPC Remote Procedure Call) oppure un paradigma di programmazione basata sullo scambio di messaggi (Socket).
Tra le funzionalità del middleware ricordiamo:
Il middleware offre molti servizi, anche più di quanti in realtà potrebbero essere necessari per una applicazione, e quindi a livello progettuale si dovrà mediare prestazioni dell’applicazione e efficienza esecutiva, cercando di includere solo i servizi necessari per non appesantire troppo il sistema finale.
In this video you will understand what middleware is. Watch the videos and discuss the topics with your classmate.
STUDIARE: G. Meini, F. Formichi, Tecnologie di progettazione di sistemi informatici e di telecomunicazioni, vol. 3, Zanichelli, 2017, pp.141-146.
LEGGERE: G. Meini, F. Formichi, Tecnologie di progettazione di sistemi informatici e di telecomunicazioni, vol. 3, Zanichelli, 2017, pp.146-163.
Consideriamo il seguente documento XML che utilizzeremo per fare un esempio di parsing.
<?xml version="1.0" encoding="ISO-8859-1" ?> |
Il DOM del documento XML sarà il seguente.
Di seguito viene fornito un esempio di un semplice parser del file XML mostrato in precedenza, presupponendo di conoscere i nomi dei nodi attraversati.
import java.io.IOException; |
L’esecuzione del parser fornirà il seguente output.
Genere: fantascienza |
Di seguito viene fornito un esempio alternativo in cui i nodi del DOM vengono visualizzati tramite un ciclo, presupponendo di non conoscerne il nome.
import java.io.IOException; // Estrazione di tutti gli elementi figli il cui nome è "libro". |
L’esecuzione del parser fornirà il seguente output.
Genere: fantascienza |
Di seguito viene fornito un esempio alternativo in cui i nodi del DOM vengono visualizzati tramite un ciclo, presupponendo di non conoscerne il nome. La visualizzazione riflette la struttura del documento XML. Il codice è più complesso dei precedenti in quanto utilizza, fra le altre cose, il concetto di ricorsione.
import java.io.IOException; |
L’esecuzione del parser fornirà il seguente output.
libri : |
STUDIARE: G. Meini, F. Formichi, Tecnologie di progettazione di sistemi informatici e di telecomunicazioni, vol. 3, Zanichelli, 2017 pp.225-231.
Nel metodo GET la coppia nome/valore, detta query string, è inviata all’interno dell’URL della richiesta GET. In una richiesta GET non esistendo il body, i dati necessari per la richiesta vengono trasportati direttamente come parametri nell’URL.
http://localhost:8084/Dati/Utente?nome=Maria+Grazia&cognome=Maffucci |
Un header possibile della richiesta GET è il seguente:
Host: localhost:8084 |
Un possibile header della risposta del server è il seguente:
Content-Length: 251 |
Mentre il body della risposta del server potrebbe essere la pagina HTML seguente:
<!DOCTYPE html> |
Nel metodo POST la coppia nome/valore, detta query string, è inviata all’interno del body del messaggio HTTP della richiesta POST.
http://localhost:8084/Dati/Utente |
Un header possibile della richiesta POST è il seguente:
Host: localhost:8084 |
Un possibile header della risposta del server è il seguente:
Content-Length: 251 |
Mentre il body della risposta del server potrebbe essere la pagina HTML seguente:
<!DOCTYPE html> |
Le coppie di valori status-code di HTTP sono delle risposte standard restituiti dai Web server. Questi codici aiutano ad identificare la causa di un problema nel caso non si riuscisse a trovare la pagina Web o la risorsa richiesta.
Gli status-code HTTP includono sia un codice numerico, sia una frase esplicativa. Ad esempio, la linea di stato HTTP 500: Internal Server Error segnala l’impossibilità del server di soddisfare la richiesta.
Un elenco degli HTTP status code è possibile trovarlo a questo link.
Il risultato finale, in entrambi i casi, dovrà essere simile al seguente.
Si effettui opportunamente il passaggio dei parametri fra le due servlet utilizzando i parametri di sessione.
STUDIARE: G. Meini, F. Formichi, Tecnologie di progettazione di sistemi informatici e di telecomunicazioni, vol. 3, Zanichelli, 2017 pp.198-224.
I Web service permettono di usare il vecchio software e di implementare le sole funzionalità necessarie per gestire una infrastruttura. Ad esempio, è possibile sviluppare un nuovo programma finanziario in Java per una compagnia che già disponeva di un vecchio software per la gestione dei salari degli impiegati, sviluppato tramite una vecchia piattaforma .net. Questo vecchio programma sarà integrato con il nuovo software che userà delle funzionalità apposite per interfacciarsi con il sistema legacy. Questo è possibile proprio perché i dati scambiati fra le applicazioni sono nettamente isolati. I Web service sono il collante che permettono una semplice comunicazione fra applicazioni o anche fra organizzazioni diverse.
I Web service permettono di esporre sul Web il Business logic layer di molti sistemi usando API specifiche, fornendo alle applicazioni client remote la possibilità di scegliere il Web service di cui necessitano. In questo modo, sul lato client potranno essere aggiunti i servizi che si vogliono, sviluppati utilizzando i linguaggi e i tool preferiti.
I Web service offrono una soluzione non proprietaria per la risoluzione di problemi, proprio perché sono spesso offerti al di fuori di una rete privata. Consentono agli sviluppatori di usare il linguaggio di programmazione che preferiscono e sono virtualmente indipendenti dalla piattaforma proprio per l’uso di metodi di comunicazione basati su standard riconosciuti.
Ogni servizio offerto da un Web service esiste indipendentemente dagli altri servizi che insieme costituiscono l’applicazione. Ciò permette di modificare parti dell’applicazione senza impattare in aree che non sono correlate con la parte modificata.
I Web service sono messi in opera utilizzando delle tecnologie standard su Internet. Questo permette di effettuare il deploy dei Web service anche remotamente su Internet, e di utilizzare sistemi di sicurezza integrati e comunemente accettati.
STUDIARE: G. Meini, F. Formichi, Tecnologie di progettazione di sistemi informatici e di telecomunicazioni, vol. 3, Zanichelli, 2017 pp.225-274.
REST definisce un insieme di principi architetturali per la progettazione di un sistema in quanto è uno stile architetturale che specifica come dovrebbe essere scritto un software di interazione in un sistema distribuito che risponda a determinati criteri e principi di funzionamento. REST non fa riferimento ad un sistema concreto e ben definito, e non è uno standard stabilito da un organismo di standardizzazione.
La sua definizione è apparsa per la prima volta nel 2000 nella tesi di Roy Fielding, Architectural Styles and the Design of Network-based Software Architectures, discussa presso l’Università della California, ad Irvine. In questa tesi venivano analizzati alcuni principi alla base di diverse architetture software, tra cui appunto i principi di un’architettura software che consentisse di vedere il Web come una piattaforma per l’elaborazione distribuita.
È bene precisare che i principi REST non sono necessariamente legati al Web, nel senso che si tratta di principi astratti di cui il World Wide Web ne risulta essere un esempio concreto.
All’epoca questa visione del Web non fu adeguatamente considerato, ma negli ultimi anni l’approccio REST è venuto alla ribalta come metodo per la realizzazione di Web Service altamente efficienti e scalabili ed ha al suo attivo un significativo numero di applicazioni.
La ragione per questa inversione di tendenza deriva dal fatto che il Web ha tutto quello che serve per essere considerata una piattaforma di elaborazione distribuita secondo i principi REST, e non sono necessarie altre sovrastrutture per realizzare quello che è il Web programmabile, idea palasemente in conflitto con i Web Service basati su SOAP.
I principi che rendono il Web adatto a realizzare Web Service secondo l’approccio REST possono essere riassunti nei seguenti cinque punti:
Questi principi rappresentano in realtà concetti ben noti perché ormai insiti nel Web che conosciamo. Li analizzeremo tuttavia sotto una prospettiva diversa, quella della realizzazione di Web Service.
Si osservi che è comunque possibile realizzare Web service utilizzando il protocollo HTTP senza rispettare tutti i principi esposti in precedenza. I Web service che però implementano l’architettura REST in modo integrale sono definiti RESTful.
Analizziamo ora i cinque principi dell’approccio REST elencati precedentemente.
Watch the video REST Web Services 01 - Introduction and answer to the following questions. Send by email your answers to your teacher.
Le risorse sono gli elementi fondamentali su cui si basano i Web Service REST, in netta contrapposizione ai Web Service SOAP-oriented che sono basati sul concetto di chiamata remota.
Per risorsa si intende un qualsiasi elemento oggetto di elaborazione. Per fare qualche esempio concreto, una risorsa può essere un cliente, un libro, un articolo, un qualsiasi oggetto su cui è possibile effettuare operazioni. Per fare un parallelo con la programmazione ad oggetti possiamo dire che una risorsa può essere assimilata ad una istanza di una classe.
Il principio che stiamo analizzando stabilisce che ciascuna risorsa deve essere identificata univocamente. Dato che le interazioni avvengono in ambito Web, il meccanismo più naturale per individuare una risorsa è dato dal concetto di URI.
Il principale beneficio nell’adottare lo schema URI per identificare le risorse consiste nel fatto che esiste già, è ben definito e collaudato e non occorre pertanto inventarsene uno nuovo.
I seguenti sono esempi di possibili identificatori di risorse:
http://www.myapp.com/clienti/1234 |
Gli URI sono abbastanza autoesplicativi: il primo identifica un determinato cliente, il secondo un ordine e il terzo un prodotto. Il quarto URI identifica l’insieme degli ordini del 2016, mentre l’ultimo URI identifica l’insieme dei prodotti di colore rosso.
L’interpretazione che abbiamo dato a questi URI è però desunta dalla semantica delle parole contenute nelle sue parti. Dal punto di vista di un Web service un URI è soltanto una stringa che identifica una risorsa, per cui anche http://www.myapp.com/tgw34/2099ww può essere un URI valido per identificare un cliente.
Watch the video REST Web Services 02 - REST and HTTP and answer to the following questions. Send by email your answers to your teacher.
Watch the video REST Web Services 03 - Resource URIs and answer to the following questions. Send by email your answers to your teacher.
Watch the video REST Web Services 04 - Collection URIs and answer to the following questions. Send by email your answers to your teacher.
Una volta spiegato come individuare una risorsa abbiamo bisogno di un meccanismo per indicare quali operazioni effettuare su di esse. Il principio dell’uso esplicito dei metodi HTTP ci indica di usare i metodi predefiniti di questo protocollo, e cioè GET, POST, PUT e DELETE.
Facciamo un semplice esempio per provare a chiarire questo concetto. Quando inseriamo un URI nella barra degli indirizzi di un browser stiamo in realtà chiedendo al browser di eseguire un metodo HTTP sulla risorsa individuata dall’URI. Il metodo che implicitamente stiamo eseguendo è GET, il cui effetto è l’accesso ad una rappresentazione della risorsa identificata dall’URI.
Dal punto di vista del codice, non si avrà bisogno di un metodo del tipo getCliente(1234) per ottenere la rappresentazione del cliente con codice 1234, sarà infatti sufficiente sfruttare il metodo standard GET del protocollo HTTP sull’URI che identifica quel determinato cliente.
Questo rende uniforme l’invocazione di operazioni sulle risorse, cioè il client non ha bisogno di sapere qual è la specifica interfaccia da utilizzare per invocare il metodo che consente di ottenere la rappresentazione di una risorsa. In un contesto non RESTful potremmo avere metodi come getCliente() o getCustomer() o altra specifica dipendente dalle scelte di chi ha sviluppato il Web Service, ma in ambito REST per ottenere lo stato della risorsa basterà usare il metodo GET applicato all’URI che identifica la risorsa stessa.
Quindi in un contesto RESTful l’accesso alla risorsa viene effettuata direttamente con i metodi HTTP, mappando uno a uno le tipiche operazioni CRUD e i metodi HTTP:
Metodo HTTP | Operazione CRUD | Descrizione |
POST | Create | Crea una nuova risorsa |
GET | Read | Ottiene lo stato di una risorsa esistente |
PUT | Update | Aggiorna una risorsa o ne modifica lo stato |
DELETE | Delete | Elimina una risorsa |
È opportuno notare che questo principio è in contrasto con quella che è la tendenza generale nell’utilizzo dei metodi HTTP. Infatti, molto spesso viene utilizzato il metodo GET per eseguire qualsiasi tipo di interazione con il server. Ad esempio, spesso per l’inserimento di un nuovo cliente nell’ambito di un’applicazione Web viene eseguita una richiesta di tipo GET su un URI del tipo:
http://www.myapp.com/addCustomer?name=Rossi |
Questo approccio non è conforme ai principi REST, perchè secondo questo stile architetturale il metodo GET serve per accedere alla rappresentazione di una risorsa e non per crearne una nuova. Nella pratica questo uso del metodo GET introduce un effetto collaterale che può avere delle conseguenze indesiderate se, ad esempio, lo URI viene invocato più volte o se la risorsa viene memorizzata in uno dei vari livelli di cache esistenti tra client e server.
Come principio generale, nel progettare un Web service in modalità REST è utile evitare l’uso di verbi negli URI e limitarsi ad utilizzare nomi, ricordandosi che un URI identifica una risorsa o una collezione di risorse.
Allo stesso modo è opportuno sottolineare che il corpo di una richiesta HTTP con metodi PUT e POST è pensato per il trasferimento della rappresentazione di una risorsa e non per eseguire chiamate remote o altre attività simili.
Per comprendere il principio dell’architettura RESTful per l’accesso alle risorse, si consideri la gestione di una rubrica telefonica. Le operazioni CRUD effettuabili sulla rubrica potrebbero essere esemplificate nel modo seguente.
Creazione (CREATE) di una nuova risorsa Rossi nella rubrica telefonica:
POST http://rubrica.it/ |
e in questo caso il server invierà come risposta al client l’identificativo della nuova voce in rubrica.
Acquisizione (READ) della rappresentazione di una risorsa Rossi dalla rubrica telefonica:
GET http://rubrica.it/Rossi |
che restituirà:
<VoceRubrica> |
Modifica (UPDATE) di una risorsa Rossi nella rubrica telefonica:
PUT http://rubrica.it/Rossi |
Cancellazione (DELETE) di una risorsa Rossi dalla rubrica telefonica:
DELETE http://rubrica.it/Rossi
Watch the video REST Web Services 05 - HTTP Methods and answer to the following questions. Send by email your answers to your teacher.
La ragione per cui vengono utilizzati due metodi distinti per effettuare la modifica (PUT) e l’aggiunta (POST) di una risorsa, risiede nel concetto di idempotenza dei metodi HTTP.
Generalmente è possibile classificare i metodi HTTP utilizzati per effettuare operazioni CRUD in due gruppi distinti:
Alla luce di questa prima classificazione si può dire che il metodo GET, essendo un metodo di sola lettura, può essere ritenuto sicuro; è possibile rieffettuare più volte la stessa richiesta GET sul server senza nessun effetto collaterale, in quanto nulla verrà modificato sul server. Detto in altre parole, il metodo GET è un metodo idempotente, e quindi ripetibile.
L’idempotenza risiede esattamente su questo concetto: un metodo è ritenuto idempotente se è possibile ripetere più volte la stessa richiesta al server senza che vi siano effetti collaterali, tranne eventualmente la prima volta che è stato invocato il metodo.
Dalla definizione di idempotenza segue che anche il metodo PUT è idempotente in quanto, dopo il primo aggiornamento della risorsa, la ripetizione del medesimo aggiornamento non avrà alcun effetto collaterale; eventualmente verrà riscritto più volte il medesimo valore.
Analogamente, il metodo DELETE risulta essere idempotente in quanto, dopo aver cancellato la risorsa identificata da un particolare URI, univoco per ogni risorsa, la ripetizione della medesima cancellazione, usando sempre lo stesso URI, non avrà alcun effetto collaterale.
Invece il metodo POST non è idempotente in quanto la ripetizione della creazione di una risorsa, anche se identica nei contenuti, provocherà l’aggiunta successiva di risorse identificate da URI diversi.
Seguendo il concetto di idempotenza è quindi possibile riclassificare i metodi HTTP utilizzati per effettuare operazioni CRUD nei seguenti due gruppi:
L’architettura REST richiede esplicitamente che i metodi GET, PUT e DELETE siano idempotenti, mentre il metodo POST deve essere non idempotente.
Il fatto che il metodo GET sia idempotente permette di poter memorizzare nella cache una risposta da parte del server originata da una richiesta GET, e il reinvio ripetuto tramite il refresh del browser non deve avere effetti collaterali. Con il metodo POST, che non è idempotente, si dovrebbe evitare di memorizzare in cache la risposta del server, in quanto il reinvio ripetuto tramite refresh del browser provocherebbe la successiva creazione di nuove risorse sul server.
Watch the video REST Web Services 06 - Method Idempotence and answer to the following questions. Send by email your answers to your teacher.
Il concetto di risorse autodescrittive prevede che queste siano concettualmente separate dalle rappresentazioni restituite al client. Ad esempio, un Web Service non invia al client direttamente un record del suo database, ma una sua rappresentazione in una codifica dipendente dalla richiesta del client e/o dall’implementazione del servizio, ad esempio in XML o in JSON.
I principi REST non pongono nessun vincolo sulle modalità di rappresentazione di una risorsa. Virtualmente è possibile utilizzare il formato che si preferisce senza essere obbligati a seguire uno standard. Di fatto, però, è opportuno utilizzare formati il più possibile standard in modo da semplificare l’interazione con i client. Inoltre, sarebbe opportuno prevedere rappresentazioni multiple di una risorsa, per soddisfare client di tipo diverso, ad esempio sia in XML, sia in JSON.
Il tipo di rappresentazione inviata dal Web service al client è indicato nella stessa risposta HTTP tramite un tipo MIME, così come avviene nella classica comunicazione tra Web server e browser, utilizzando lo header HTTP Content-type.
Un client a sua volta ha la possibilità di richiedere una risorsa in uno specifico formato sfruttando l’attributo Accept di una richiesta HTTP, come mostrato nel seguente esempio:
GET /clienti/1234 |
In questo caso il client richiede la rappresentazione del cliente identificato dal codice 1234 in formato XML. Ovviamente, se il Web service supportasse ulteriori formati, la stessa risorsa potrebbe essere richiesta in formati diversi, come ad esempio in JSON o in HTML.
La possibilità di rappresentazioni multiple produce alcuni benefici pratici: ad esempio, se abbiamo un output sia in HTML, sia in XML, possiamo consumare il servizio sia con un’applicazione sia con un comune browser. In altre parole, seguendo i principi REST nel progettare un’applicazione Web è possibile costruire sia una Web API che una Web UI.
Watch the video REST Web Services 07 - REST Response and answer to the following questions. Send by email your answers to your teacher.
Un altro vincolo dei principi REST consiste nella necessità che le risorse siano tra loro messe in relazione tramite link ipertestuali. Questo principio è anche noto come HATEOAS, dall’acronimo di Hypermedia As The Engine Of Application State, e pone l’accento sulle modalità di gestione dello stato dell’applicazione.
In sostanza questo principio afferma che tutto quello che un client deve sapere su una risorsa, e sulle risorse ad essa correlate, deve essere contenuto nella rappresentazione della risorsa stessa o deve essere accessibile tramite collegamenti ipertestuali.
Ad esempio, la rappresentazione di un ordine in un linguaggio XML-based deve contenere gli eventuali collegamenti al cliente e agli articoli correlati:
<ordine> |
In questo modo il client può accedere alle risorse correlate seguendo semplicemente i collegamenti contenuti nella rappresentazione XML della risorsa corrente.
Il fatto di utilizzare un URI come identificatore di una risorsa, quindi un meccanismo standard e consolidato, consente al client di accedere anche a risorse messe a disposizione da altre applicazioni, che potrebbero trovarsi eventualmente su altri server.
L’architettura REST (REpresentational State Transfer) enfatizza proprio il concetto di trasferimento di stato, cioè il fatto che un’applicazione possa passare velocemente da uno stato all’altro. Nell’architettura REST lo stato di una applicazione è rappresentato dallo stato delle sue risorse e, grazie al principio HATEOAS, la relativa transazione di stato delle risorse viene attivata attraverso l’uso di semplici collegamenti ipermediali.
Nella visione REST l’esecuzione di un’applicazione può quindi essere rappresentata da una rete di risorse fra cui un client “naviga” seguendo i collegamenti ipermediali ammessi tra una risorsa e l’altra. Questa visione richiama esattamente la navigazione tra pagine Web diverse, visitabili tramite link.
Il principio HATEOAS cerca di incoraggiare l’uso di collegamenti ipermediali sia per rappresentare risorse composte, sia per definire qualsiasi altra relazione tra le risorse e per controllare le transizioni ammissibili tra uno stato e l’altro dell’applicazione.
Per fare un esempio, consideriamo la seguente rappresentazione di un ordine in un ipotetico negozio online :
<ordine id=”123” data=”10/03/2017”> |
In esso non è riportato alcun collegamento con altre risorse gestite dal sistema. Affinché un cliente possa risalire alla fattura associata a questo ordine dovrà effettuare un’apposita richiesta al Web Service, utilizzando l’URI della risorsa fattura che il cliente dovrebbe procurarsi in qualche modo. Nell’ottica del principio HATEOAS sarebbe opportuno includere un collegamento direttamente nella rappresentazione dell’ordine:
<ordine id=”123” data=”10/03/2017”> <links> <link rel="self" mediaType="application/xml" </links> |
In questo modo il client ha già nella rappresentazione dell’ordine tutte le informazioni necessarie per accedere sia all’ordine (self), sia alla fattura associata. Inoltre, la presenza o meno del collegamento alla fattura associata all’ordine fornisce implicitamente un’altra informazione al client: se il collegamento non è presente nella rappresentazione dell’ordine vuol dire che la fattura non è ancora stata emessa, se invece è presente vuol dire che è stata emessa ed è accessibile. In questo modo si avrà un maggior controllo della transizione da uno stato all’altro dell’applicazione, evitando transizioni non ammissibili in un dato momento.
Sfruttando pienamente il principio HATEOAS è possibile creare servizi Web con accoppiamento debole tra client e server. Infatti, se il server riorganizza le relazioni tra le risorse, il client è in grado di trovare tutto ciò che serve nelle rappresentazioni ricevute. Potenzialmente tutto quello che servirebbe ad un client è solo l’URI della risorsa iniziale, ad esempio quello dell’ordine. Come avanzare tra uno stato e l’altro dell’applicazione verrà indicato man mano che si seguono i collegamenti incorporati nelle successive rappresentazioni delle risorse.
Il principio HATEOAS, che sancisce il legame tra transizioni di stato e collegamenti tra risorse, è purtroppo scarsamente sfruttato nelle attuali implementazioni di Web service di tipo REST. La maggior parte di queste implementazioni si limita a definire le rappresentazioni delle risorse e le modalità di interazione tramite i metodi HTTP, non sfruttando a pieno le potenzialità dei principi REST.
Watch the video REST Web Services 08 - HATEOAS and answer to the following questions. Send by email your answers to your teacher.
Il principio della comunicazione stateless è ben noto a chi lavora con il Web. Questa è infatti una delle caratteristiche principali del protocollo HTTP, cioè ciascuna richiesta non ha alcuna relazione con le richieste precedenti e successive.
Lo stesso principio si applica ad un Web service REST, cioè le interazioni tra client e server devono essere senza stato, rendendo ciascuna richiesta indipendente dalle altre.
La comunicazione stateless prevede che una richiesta effettuata dal client non richieda al server il recupero dello stato dell’applicazione o di un suo contesto. Il Web service o il client REST devono includere nelle intestazioni o nel corpo HTTP tutti i parametri, il contesto e i dati necessari al server per generare una risposta.
È importante sottolineare che sebbene REST preveda la comunicazione stateless, non vuol dire che un’applicazione non debba avere uno stato. Ciò che si richiede è che la responsabilità della gestione dello stato dell’applicazione non debba essere conferita al server, ma deve rientrare nei compiti del client.
La principale ragione di questa scelta è la scalabilità: mantenere lo stato di una sessione ha un costo in termini di risorse sul server e, all’aumentare del numero di client, tale costo può diventare insostenibile.
Inoltre, con una comunicazione senza stato è possibile creare cluster di server che possono rispondere ai client senza vincoli sulla sessione corrente, ottimizzando le prestazioni globali dell’applicazione.
Infine una comunicazione senza stato consente di ottimizzare le prestazione del Web service e di semplificare la progettazione e l’implementazione lato server, dato che l’assenza della gestione dello stato rimuove la necessità di sincronizzare i dati di sessione con una applicazione esterna.
Il protocollo HTTP è intrinsecamente senza stato, ma molto spesso, nello sviluppo di applicazioni Web, vengono artificialmente ricreate le condizioni per una comunicazione con stato. Ad esempio, l’uso di chiavi di sessione, utilizzate in diversi framework di sviluppo per il Web, non fa altro che introdurre lo stato della comunicazione al di sopra del protocollo HTTP, in genere sfruttando i cookie.
In una architettura REST l’uso di comunicazioni con stato sarebbe vietato; il server non dovrebbe tenere traccia delle relazioni tra le diverse richieste. Se fosse necessario gestire uno stato della comunicazione, questo compito spetterebbe completamente al client.
Il fatto che i principi REST escludano la gestione dello stato della comunicazione non deve però far pensare che i Web Service REST siano senza stato. L’acronimo REST sta per REpresentational State Transfer, sottolineando proprio la centralità della gestione dello stato in un sistema distribuito. Lo stato che REST prende in considerazione è però quello delle risorse e dell’intera applicazione, come enfatizzato in precedenza relativamente al principio HATEOAS.
Lo stato delle risorse è dato dall’insieme dei valori che caratterizzano una risorsa in un dato momento. Un Web Service è responsabile della gestione dello stato delle risorse. Un client può accedere allo stato di una risorsa tramite le diverse rappresentazioni della risorsa stessa utilizzando il metodo GET di HTTP, e contribuire a modificarlo per mezzo dei metodi PUT, POST e DELETE di HTTP.
Lo stato del client è rappresentato dall’insieme del suo contesto e delle risorse ottenute in uno specifico momento. Il server può influenzare le transizioni di stato del client inviando differenti rappresentazioni delle risorse in risposta alle sue richieste.
Lo stato dell’applicazione, cioè del risultato dell’interazione tra client e server, è dato dallo stato del client e delle risorse gestite dal server, e determina le modalità di modifica dello stato delle risorse e del client. A differenza di quanto avviene in buona parte delle applicazioni Web, dove lo stato dell’applicazione viene spesso mantenuto dal server insieme allo stato della comunicazione, lo stato dell’applicazione in un’architettura REST è il frutto della collaborazione di client e server, ciascuno con i propri ruoli e responsabilità. Nella progettazione di un Web Service REST questa separazione dei ruoli deve essere chiara e spesso occorre ripensare opportunamente la gestione dello stato in termini di gestione di risorse.
Ad esempio, per gestire un carrello in una comune applicazione Web di e-commerce, si ricorre tipicamente ad una sua rappresentazione lato server associata alla sessione corrente dell’utente. Per quanto abbiamo detto finora, questo approccio viola i principi REST dal momento che richiede il mantenimento dello stato della sessione. Per rimanere nell’ambito dei principi REST abbiamo a disposizione due possibili soluzioni: demandare al client il compito di gestire il carrello, o gestire il carrello sul server come una risorsa.
Nel primo caso il client avrà una struttura dati in cui terrà traccia degli articoli a cui l’utente è interessato. Nel momento della generazione della richiesta di un nuovo ordine da inviare al server, gli articoli annotati nel carrello verranno riportati nell’ordine.
In alternativa è possibile che il nostro Web Service preveda una risorsa carrello dedicata al mantenimento degli articoli scelti dall’utente durante la fase di acquisto. È da sottolineare il fatto che il carrello è una risorsa come le altre e non è associata alla sessione corrente. È quindi accessibile tramite URI in qualsiasi momento, è persistente ed è gestibile tramite i metodi HTTP.
La trasformazione in risorse di elementi tradizionalmente gestiti tramite lo stato della sessione, è un approccio da tenere presente durante la progettazione di un Web Service REST.
Watch the video REST Web Services 09 - The Richardson Maturity Model to complete your theoretical preparation about Web Service RESTful.
The following video are optional material and the subject is not part of the course o this year. If you would like to improve your understanding about RESTful APIs development using JAX-RS, watch the following video.
Uno degli aspetti cruciali di qualsiasi applicazione Web è la gestione della sicurezza. I Web service REST, rendendo accessibili le risorse tramite i meccanismi del Web, non possono sottrarsi a questa necessità.
In linea di massima la sicurezza di un sistema coinvolge almeno i seguenti aspetti:
Nel Web tutti questi aspetti sono stati efficacemente affrontati da diverso tempo, rendendo il modello sufficientemente maturo per decidere di gestire la sicurezza dei Web service REST basandosi largamente sul protocollo HTTP, ereditandone le caratteristiche ormai ampiamente collaudate.
In sostanza, per gestire l’identità nell’ambito di un Web Service REST possono essere utilizzati i meccanismi propri del protocollo HTTP, come ad esempio HTTP Basic Authentication o HTTP Digest Authentication.
Ad esempio, se un client richiede l’accesso ad una risorsa protetta, il server può richiedere l’autenticazione con una risposta HTTP analoga alla seguente:
401 Unauthorized |
La risposta richiede di autenticarsi utilizzando HTTP Basic Authentication. Il client invierà una risposta di questo tipo:
GET /ordini/?123 HTTP/1.1 |
dove le credenziali di autenticazione vengono passate sotto forma di stringa codificata in Base64.
Da questo momento in poi, rispettando le caratteristiche dell’architettura REST, ciascuna richiesta successiva inviata al server conterrà queste credenziali, mantenendo la natura stateless delle comunicazione HTTP.
Naturalmente l’invio delle credenziali con la semplice codifica Base64 non è sufficiente garanzia di riservatezza. L’uso di HTTP Digest Authentication è sicuramente un passo avanti per evitare la trasmissione in chiaro della password, ma non è esente da possibili attacchi.
La soluzione che in genere andrebbe adottata consiste nella creazione di un canale di trasmissione sicuro, come può essere HTTPS, cioè HTTP over SSL/TLS. L’adozione di HTTPS garantisce la riservatezza e l’integrità nella trasmissione delle informazioni, coprendo gli altri aspetti di sicurezza.
Nel caso in cui i meccanismi di sicurezza propri del protocollo HTTP non siano sufficienti o adeguati per il tipo di Web Service da sviluppare, è sempre possibile gestire la sicurezza in modo personalizzato. In genere questo accade per la gestione dell’autenticazione e dell’autorizzazione dei client, dove i criteri possono essere diversi in base allo specifico dominio del problema.
Non c’è un obbligo nell’utilizzare i meccanismi standard del protocollo HTTP, ma è bene sottolineare che ogni eventuale soluzione personalizzata dovrebbe rispettare i principi REST. Infatti il rischio di cadere in una soluzione non-REST per gestire l’autenticazione e l’autorizzazione dei client è alto, dal momento che di solito si tenterebbe di introdurre nuovamente il concetto di sessione associata al client appena autenticato.
In realtà il problema non sta nel concetto di sessione, ma nella modalità di gestione. Dal momento che REST non ammette il mantenimento dello stato della sessione tra una richiesta e le altre, occorre che il client e il server si scambino tutte le informazioni necessarie per ricreare la sessione ad ogni interazione. Questo vuol dire rigenerare la sessione ad ogni richiesta utilizzando le credenziali fornite dal client, con potenziale perdita di prestazioni, anche se i criteri di autenticazione ed autorizzazione non sono molto complessi.
Un’alternativa potrebbe essere trasformare la sessione in risorsa, utilizzando un approccio analogo a quello adottato per l’implementazione del carrello del negozio online visto in precedenza. L’autenticazione di un client corrisponde alla richiesta di creazione di una risorsa di tipo sessione: se le credenziali fornite dal client sono valide, allora viene creata la risorsa ed inviato il corrispondente URI al client, che da questo momento in poi lo invierà al server con ogni richiesta.
Ad ogni richiesta il server recupererà la sessione per effettuare le necessarie verifiche e in caso positivo soddisferà le richieste del client.
Il server avrà la responsabilità di gestire opportunamente la sessione, come ad esempio eliminarla dopo un determinato periodo di inattività, ma non la manterrà in memoria, la gestirà come una normale risorsa persistente.
Come è possibile immaginare, la gestione dell’autenticazione e dell’autorizzazione dei client rischia di complicare la progettazione e l’implementazione di un Web Service. Una soluzione potrebbe essere quella di delegare ad un servizio esterno il compito di gestire i client, utilizzando un Web Service specializzato nella gestione dell’autenticazione e/o dell’autorizzazione.
Negli ultimi tempi sono venuti alla ribalta servizi aperti che si pongono proprio l’obiettivo di decentralizzare la gestione di alcuni aspetti della sicurezza. Tra i servizi di questo tipo i più noti e diffusi nell’ambito della realizzazione di Web Service REST sono OpenID e OAuth.
OpenID è un protocollo per la gestione dell’identità online. Esso consente ad un client di presentare ad un Web Service o ad un sito Web un URI come dichiarazione della propria identità. L’applicazione Web chiede conferma della validità al relativo servizio di autenticazione (OpenID provider) il quale, dopo aver interagito con il client, conferma o smentisce l’autenticità dell’identità dichiarata.
OAuth è un protocollo per la concessione a terze parti di autorizzazioni per l’accesso a risorse protette. In pratica, un client può consentire ad un Web Service l’accesso ad una risorsa protetta, ospitata da un service provider, senza fornire le proprie credenziali.
È naturale che l’utilizzo di servizi esterni per la gestione di alcuni aspetti della sicurezza presuppone che il Web Service riponga piena fiducia nel fornitore del servizio, senza la quale questo tipo di servizi non avrebbero senso.
La seguente servlet Java realizza un semplice Web service che implementa una calcolatrice che restituisce il risultato di operazioni aritmetiche. La richiesta avviene mediante URL che terminano con una query string costituita da due valori numerici (op1 per il primo operando e op2 per il secondo operando) e l’operazione da effettuare (add per la somma, sub per la sottrazione, mul per la moltiplicazione e div per la divisione assegnati al parametro operation).
La query string viene costruita automaticamente compilando un form iniziale.
Il codice per generare il form è il seguente.
<%-- |
Selezionando l’operazione add verrà generato il seguente URL di richiesta:
http://localhost:8080/ArithmeticOperations/getxml?op1=5&op2=6&operation=add |
ottenendo il seguente risultato in formato XML.
Gli URL generati in corrispondenza delle altre possibili richieste corrisponderebbero ai seguenti:
http://localhost:8080/ArithmeticOperations/getxml?op1=5&op2=6&operation=sub |
L’identificazione delle risorse utilizzato negli URI di questo Web service potrebbero apparire in contrasto con i principi RESTful. La presenza di parametri nello URI potrebbe far pensare ad una chiamata di procedura piuttosto che ad un identificatore di risorsa.
In realtà l’approccio è comunque legittimo nell’ambito dell’architettura REST in quanto la stringa è univoca ed identifica esattamente la risorsa che vogliamo ottenere senza alcun effetto collaterale. La presenza dei parametri ci dà la sensazione di una chiamata di procedura, ma da un punto di vista REST non si tratta che di una sequenza di caratteri per l’identificazione di una risorsa.
L’implementazione del Web service che genera la risposta XML con il risultato dell’operazione aritmetica, è realizzato utilizzando la servlet mostrata di seguito. Nella servlet non è stato utilizzato il deployment descriptor web.xml per configurare la servlet, bensì si è preferito utilizzare le annotazioni di Java.
L’annotazione @WebServlet permette di impostare il nome della servlet attraverso l’elemento name (WriteXml), mentre l’URL di invocazione della servlet viene impostato attraverso l’elemento urlPatterns (/getxml). Se in quest’ultimo elemento fosse stato impostato il valore /* si sarebbe potuto associare all’invocazione della servlet una qualsiasi sezione finale dell’URL, permettendo al codice della servlet stessa di modificare la propria risposta in base all’analisi della struttura dello URL.
import application.Application; * <code>POST</code> methods. HttpServletResponse response) throws ServletException, IOException { op2.trim().isEmpty()) { /** * richiesta. HttpServletResponse response) throws ServletException, IOException { HttpServletResponse response) throws ServletException, IOException { |
La classe Application svolge l’operazione aritmetica richiesta.
/** |
Per configurare la servlet si sarebbe potuto utilizzare il deployment descriptor web.xml riportato di seguito.
<?xml version="1.0" encoding="UTF-8"?> |
Per rendere persistenti le risorse esposte da un Web service può essere utilizzato un database relazionale gestito da un DBMS, implementando in questo modo una architettura a tre livelli:
Un’architettura di questo tipo, definita three-tier, garantisce un ampio disaccoppiamento tra i vari elementi del sistema software, permettendo una diversa gestione della sicurezza sui diversi layer, rendendoli facilmente scalabili e manutenibili e permettendo aggiornamenti mirati per ogni tier.
P. Camagni, R. Nikolassy, Tecnologie e progettazione di sistemi informatici e di telecomunicazioni, vol. 3, ed. Hoepli, 2014, pp.212-213 e pp.215-218.
P. Camagni, R. Nikolassy, Tecnologie e progettazione di sistemi informatici e di telecomunicazioni, vol. 3, ed. Hoepli, 2014, pp.250-254.
Il seguente esempio mostra alcune funzionalità di un Web service REST che manipola i dati di un database (EsempioREST) di prodotti (Products) venduti presso diversi negozi (Showrooms) di una catena commerciale. I prodotti sono organizzati per categorie (Categories). Il Web service permetterà di interrogare il database utilizzando esclusivamente i metodi HTTP e un URL che identifichi la risorsa, come richiesto dall’architettura REST.
Il server SimpleRestDb è stato creato in modo tale da presentare all’avvio le modalità di costruzione degli URL per poter effettuare una richiesta di visualizzazione del contenuto di una tabella del database EsempioREST. Questa leggenda, fornita in formato XML, permetterà di effettuare la visualizzazione dei contenuti delle singole tabelle del database, sia globalmente, sia in modo parametrico usando un identificatore.
Ad esempio, per visualizzare tutti i prodotti presenti nella tabella Products del database EsempioREST si dovrà utilizzare lo URL:
http://localhost:8080/SimpleRestDb/products |
Il metodo GET invocato con questo URL verrà gestito dal Web service tramite il metodo doGet() che, effettuando un’analisi dello URL, selezionerà la visualizzazione di tutti i prodotti. Nell’immagine seguente viene riportata una porzione della risposta XML restituita dal Web service.
Invece, effettuando la richiesta di un singolo prodotto tramite il suo id utilizzando il seguente URL
http://localhost:8080/SimpleRestDb/product/5 |
si otterrà la visualizzazione del singolo prodotto, sempre in formato XML.
Per testare le operazioni di aggiunta (CREATE - POST), aggiornamento (UPDATE - PUT) e cancellazione (DELETE - DELETE) di una istanza, verrà utilizzato un semplice client, RestClient.
Nel seguito della trattazione verrà fornita una possibile implementazione del database, del Web service e del client.
Lo schema concettuale, semplificando la realtà gestita, è il seguente:
Analizzando lo schema E-R (concettuale) si deduce:
Lo schema logico sarà il seguente:
Categories(id, description)
Products(id, name, description, price, id_category)
Showrooms(id, name, address, city, phone, site)
ProductsShowrooms(id_products, id_showroom)
Il DDL e DML della relativa progettazioe fisica per la creazione del database, delle tabelle e della relativa popolazione delle stesse, sarà il seguente:
-- phpMyAdmin SQL Dump |
Nel server per la gestione della connessione al database viene definita una interfaccia per specificare i metodi utilizzabili per la conversione dei dati del database in formato XML o JSON (quest’ultimo non è imlementato). I due metodi dichiarati saranno poi definiti nelle classi relative alle tre entità del database. Il server implementa diversi metodi per l’interrogazione del database EsempioREST.
L’intera implementazione del server di gestione del database viene inserita come libreria nel Web service che offre il servizio, il cui codice è mostrato nel seguito della trattazione.
Bointerface.java
package simple.bo; public interface BoInterface { |
Di seguito vengono definite le classi per manipolare i dati del database come oggetti.
Category.java
La classe Category sarà usata per creare oggetti derivanti dalla tabella Categories che contiene le diverse categorie dei prodotti.
package simple.bo;
public class Category implements BoInterface {
private int id = -1;
private String description;
public Category(int id, String description) {
this.id = id;
this.description = description;
}
public Category(String description) {
this.description = description;
}
public int getId() {
return id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public int hashCode() {
int hash = 7;
hash = 59 * hash + this.id;
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Category other = (Category) obj;
if (this.id != other.id) {
return false;
}
return true;
}
// Restituisce un elemento XML
public String toXml() {
return "<category id=\"" + id + "\"><description>" + description
+ "</description></category>";
}
// Restituisce un elemento Json
public String toJson() {
return "{\n\"category\"\n{\n\"id\":\"" + id + "\",\n\"description\":\""
+ description + "\"\n}\n}";
}
}
Product.java
La classe Product sarà usata per creare oggetti derivanti dalla tabella Products che contiene tutti i prodotti gestiti, ognuno appartenente ad una specifica categoria.
package simple.bo;
public class Product implements BoInterface {
private int id = -1;
private String name;
private String description = null;
private float price;
private Category category;
// Costruttore di un prodotto.
// L’attributo che conterrà la categoria è un riferimento all’oggetto che
// identifica la categoria stessa, non un identificatore, come avviene nel db.
// La rappresentazione dei dati usando un linguaggio ad oggetti, non
// necessariamente deve essere riprodotta esattamente. In questo caso è stato
// scelto di memorizzare direttamente il riferimento alla categoria invece di
// salvarne il codice.
public Product(int id, String name, String description, float price, Category category) {
this.id = id;
this.name = name;
this.price = price;
this.description=description;
this.category = category;
}
public Product(String name, String description, float price, Category category) {
this.name = name;
this.description = description;
this.price = price;
this.category = category;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public float getPrice() {
return price;
}
public Category getCategory() {
return category;
}
public void setDescription(String description) {
this.description = description;
}
public void setPrice(float price) {
this.price = price;
}
public void setCategory(Category category) {
this.category = category;
}
@Override
public int hashCode() {
int hash = 7;
hash = 71 * hash + this.id;
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Product other = (Product) obj;
if (this.id != other.id) {
return false;
}
return true;
}
// Restituisce un elemento XML
public String toXml() {
String s = "<product id=\"" + id + "\"><name>" + name + "</name>";
s += "<description>" + description + "</description>";
s += "<price>" + price + "</price>";
s += category.toXml() + "</product>";
return s;
}
// Restituisce un elemento Json
public String toJson() {
String s = "{\n\"product\"\n{\n\"id\":\"" + id + "\",\n";
s += "\"name\":\"" + name + "\",\n";
s += "\"description\":\"" + description + "\",\n";
s += "\"price\":\"" + price + "\",\n";
s += category.toJson();
s += "\n}\n}";
return s;
}
}
Showroom.java
La classe Showroom sarà usata per creare oggetti derivanti dalla tabella Showrooms che contiene tutti i negozi della catena.
package simple.bo;
public class Showroom implements BoInterface {
private int id = -1;
private String name;
private String address;
private String city;
private String phone;
private String site;
public Showroom(int id, String name, String address, String city) {
this.id = id;
this.name = name;
this.address = address;
this.city = city;
}
public Showroom(String name, String address, String city, String phone, String site) {
this.name = name;
this.address = address;
this.city = city;
this.phone = phone;
this.site = site;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
public String getCity() {
return city;
}
public String getPhone() {
return phone;
}
public String getSite() {
return site;
}
public void setPhone(String phone) {
this.phone = phone;
}
public void setSite(String site) {
this.site = site;
}
@Override
public int hashCode() {
int hash = 3;
hash = 79 * hash + this.id;
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Showroom other = (Showroom) obj;
if (this.id != other.id) {
return false;
}
return true;
}
// Restituisce un elemento Json
public String toXml() {
String s = "<showroom id=\"" + id + "\"><name>" + name + "</name>";
s += "<address>" + address + "</address><city>" + city + "</city>";
if (phone != null) {
s += "<phone>" + phone + "</phone>";
}
if (site != null) {
s += "<site>" + site + "</site>";
}
s += "</showroom>";
return s;
}
// Restituisce un elemento Json
@Override
public String toJson() {
throw new UnsupportedOperationException("Not supported yet.");
}
}
Server.java (finire di commentare)
Nel server mancano le interrogazioni al database:
Provare ad implementarli come esercizio.
package simple.db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import simple.bo.Category;
import simple.bo.Product;
import simple.bo.Showroom;
/**
*
* @author palma
* Mancano le interrogazioni al database di:
* - Prodotto per Categoria
* - Negozi che vendono un prodotto
* - Tutti i prodotti venduti da un negozio
*/
public class Server {
// La classe Connection permette di connettersi ad un database.
Connection connection;
// La classe Statement permette di eseguire una query al database.
Statement statement;
// La classe ResultSet contiene il risultato della query effettuata sul
// database.
ResultSet resultSet;
/**
* Il costruttore prende come parametro formale la stringa di connessione
* al database.
*/
public Server(String connectionString) {
try {
// La classe DriverManager è il servizio di base per la gestione
// dei driver JDBC, mentre il metodo getConnection() permette di
// creare una connessione al database identificato dalla stringa
// fornita come parametro attuale.
connection = DriverManager.getConnection(connectionString);
// Il metodo createStatement() crea un oggetto Statement utilizzato
// per inviare istruzioni SQL al database.
statement = connection.createStatement();
} catch (SQLException ex) {
throw new RuntimeException("Impossibile connettersi al database");
}
}
/**
* Il metodo memorizza in un ArrayList l'elenco di tutte le categorie
* presenti nella tabella Categories.
* @return elenco di oggetti Category
*/
public List<Category> selectCategories() {
List<Category> list = new ArrayList();
try {
// Il metodo executeQuery() dell'interfaccia Statement esegue una
// istruzione SQL fornita come parametro attuale, e restituisce un
// singolo oggetto ResultSet.
resultSet = statement.executeQuery("select * from Categories");
while (resultSet.next()) {
// I metodi getInt() e getString() dell'interfaccia ResultSet
// recuperano il valore nella colonna passata come parametro
// attuale ai metodi, dell'attuale riga dell'oggetto ResultSet.
// Il metodo getInt() trasforma il valore in un intero, mentre
// il metodo getString() lo trasforma in una stringa.
// Il parametro passato nei metodi può essere un numero o una
// stringa. Il primo identifica la posizione della colonna, il
// secondo l'attributo della tabella nel database.
list.add((Category) (new Category(resultSet.getInt(1),
resultSet.getString(2))));
}
} catch (SQLException ex) {
throw new RuntimeException("Errore nell'esecuzione della query");
}
return list;
}
/**
* Il metodo memorizza in un ArrayList l'elenco di tutti i negozi
* presenti nella tabella Showrooms.
* @return elenco di oggetti Showroom
*/
public List<Showroom> selectShowrooms() {
List<Showroom> list = new ArrayList();
Showroom ss = null;
try {
resultSet = statement.executeQuery("select * from Showrooms");
while (resultSet.next()) {
ss = new Showroom(resultSet.getInt(1), resultSet.getString(2),
resultSet.getString(3), resultSet.getString(4));
ss.setPhone(resultSet.getString(5));
ss.setSite(resultSet.getString(6));
list.add((Showroom) ss);
}
} catch (SQLException ex) {
throw new RuntimeException("Errore nell'esecuzione della query");
}
return list;
}
/**
* Il metodo memorizza in un ArrayList l'elenco di tutti i prodotti
* presenti nella tabella Products, ognuno associato alla relativa
* categoria.
* @return elenco di oggetti Product
*/
public List<Product> selectProducts() {
List<Product> list = new ArrayList();
Category sc = null;
Product sp = null;
try {
resultSet = statement.executeQuery("select * from Products, "
+ "Categories where id_category=Categories.id");
while (resultSet.next()) {
if (sc == null || sc.getId() != resultSet.getInt(6)) {
sc = new Category(resultSet.getInt(6), resultSet.getString(7));
}
sp = new Product(resultSet.getInt(1), resultSet.getString(2),
resultSet.getString(3),resultSet.getFloat(4), (Category) sc);
list.add((Product) sp);
}
} catch (SQLException ex) {
throw new RuntimeException("Errore nell'esecuzione della query");
}
return list;
}
/**
* Il metodo seleziona dal database l'istanza di Categories identificata
* dalla chiave primaria passata come parametro al metodo.
* @param id chiave primaria che identifica l'istanza nella tabella
* Categories.
* @return l'oggetto Category con i dati estratti dal database.
*/
public Category selectCategory(int id) {
Category c = null;
try {
resultSet = statement.executeQuery("select * from Categories where id=" + id);
if (resultSet.next()) {
c = (Category) (new Category(resultSet.getInt(1), resultSet.getString(2)));
}
} catch (SQLException ex) {
throw new RuntimeException("Errore nell'esecuzione della query");
}
return c;
}
/**
* Il metodo seleziona dal database l'istanza di Showroom identificata
* dalla chiave primaria passata come parametro al metodo.
* @param id chiave primaria che identifica l'istanza nella tabella
* Showrooms.
* @return l'oggetto Showroom con i dati estratti dal database.
*/
public Showroom selectShowroom(int id) {
Showroom ss = null;
try {
resultSet = statement.executeQuery("select * from Showrooms where id=" + id);
if (resultSet.next()) {
ss = new Showroom(resultSet.getInt(1), resultSet.getString(2),
resultSet.getString(3), resultSet.getString(4));
ss.setPhone(resultSet.getString(5));
ss.setSite(resultSet.getString(6));
}
} catch (SQLException ex) {
throw new RuntimeException("Errore nell'esecuzione della query");
}
return (Showroom) ss;
}
/**
* Il metodo seleziona dal database l'istanza di Products identificata
* dalla chiave primaria passata come parametro al metodo.
* @param id chiave primaria che identifica l'istanza nella tabella
* Products.
* @return l'oggetto Product con i dati estratti dal database.
*/
public Product selectProduct(int id) {
Category sc = null;
Product sp = null;
try {
resultSet = statement.executeQuery("select * from Products, Categories "
+ "where id_category=Categories.id and Products.id=" + id);
if (resultSet.next()) {
sc = new Category(resultSet.getInt(6), resultSet.getString(7));
sp = new Product(resultSet.getInt(1), resultSet.getString(2),
resultSet.getString(3),resultSet.getFloat(4), (Category) sc);
}
} catch (SQLException ex) {
throw new RuntimeException("Errore nell'esecuzione della query");
}
return (Product) sp;
}
public int deleteProduct(Product p){
return deleteProduct(p.getId());
}
public int deleteProduct(int id) {
int n = 0;
try {
n = statement.executeUpdate("delete from Products where id=" + id);
statement.executeUpdate("delete from ProductsShowrooms where id_product=" + id);
} catch (SQLException ex) {
throw new RuntimeException("Errore nell'esecuzione della query");
}
return n;
}
public int deleteShowroom(Showroom s){
return deleteShowroom(s.getId());
}
public int deleteShowroom(int id) {
int n = 0;
try {
n = statement.executeUpdate("delet from Showroom where id=" + id);
statement.executeUpdate("delete from ProductsShowrooms where id_showroom=" + id);
} catch (SQLException ex) {
throw new RuntimeException("Errore nell'esecuzione della query");
}
return n;
}
public int deleteCategory(Category c){
return deleteCategory(c.getId());
}
public int deleteCategory(int id) {
int n = 0;
try {
statement.executeUpdate("delete from Products "
+ " where id_category=" + id);
n = statement.executeUpdate("delete from Categories where id=" + id);
} catch (SQLException ex) {
throw new RuntimeException("Errore nell'esecuzione della query");
}
return n;
}
public int updateCategory(Category c) {
try {
return statement.executeUpdate("update Categories set description='"
+ c.getDescription() + "' where id=" + c.getId());
} catch (SQLException ex) {
throw new RuntimeException("Errore nell'esecuzione della query");
}
}
public int updateShowroom(Showroom s) {
try {
return statement.executeUpdate("update Showrooms set phone='"
+ s.getPhone() + "', site='" + s.getSite() + "' where id=" + s.getId());
} catch (SQLException ex) {
throw new RuntimeException("Errore nell'esecuzione della query");
}
}
public int updateProduct(Product p) {
try {
return statement.executeUpdate("update Products set description='"
+ p.getDescription() + "', price=" + p.getPrice() + ", id_category="
+ p.getCategory().getId() + " where id=" + p.getId());
} catch (SQLException ex) {
throw new RuntimeException("Errore nell'esecuzione della query");
}
}
public int insertProduct(Product p) {
try {
return statement.executeUpdate("insert into Products (name,description"
+ ",price,id_category) values ('" + p.getName() + "','" + p.getDescription()
+ "'," + p.getPrice() + "," + p.getCategory().getId() + ")");
} catch (SQLException ex) {
throw new RuntimeException("Errore nell'esecuzione della query");
}
}
public int insertShowroom(Showroom s) {
try {
return statement.executeUpdate("insert into Showrooms (name,address"
+ ",city,phone,site) values ('" + s.getName() + "','" + s.getAddress()
+ "','" + s.getCity() + "','" + s.getPhone() + "','" + s.getSite() + "')");
} catch (SQLException ex) {
throw new RuntimeException("Errore nell'esecuzione della query");
}
}
public int insertCategory(Category c) {
try {
return statement.executeUpdate("insert into Categories (description)"
+ " values ('" + c.getDescription() + "')");
} catch (SQLException ex) {
throw new RuntimeException("Errore nell'esecuzione della query");
}
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
<servlet>
<servlet-name>Crud</servlet-name>
<servlet-class>simplerestdb.Crud</servlet-class>
<init-param>
<param-name>connectionString</param-name>
<param-value>
jdbc:mysql://localhost:3306/EsempioRest?user=root&password=password
</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Crud</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
</web-app>
Crud.java
package simplerestdb;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import parser.CategoryParser;
import parser.ProductParser;
import simple.bo.Category;
import simple.bo.Product;
import simple.bo.Showroom;
import simple.db.Server;
/**
*
* @author palma
* Manca il parser degli Showroom, implementatelo per esercizio.
*/
public class Crud extends HttpServlet {
/**
* Processes requests for both HTTP <code>GET</code> and <code>POST</code>
* methods.
*
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
private Server server;
/**
* Handles the HTTP <code>GET</code> method.
*
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
PrintWriter out = response.getWriter();
response.setContentType("application/xml;charset=UTF-8");
String[] parts = request.getRequestURI().split("/");
if (parts != null && (parts.length == 4 || parts.length == 3
|| parts.length == 2)) {
if (parts.length == 4) {
selectOne(parts, out, response);
} else {
if (parts.length == 3) {
selectAll(parts, out, response);
} else {
istructions(out);
}
}
} else {
response.sendError(400);
}
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
insertOrUpdate(request, response, 1);
}
@Override
protected void doPut(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
insertOrUpdate(request, response, 2);
}
@Override
protected void doDelete(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String[] parts = request.getRequestURI().split("/");
if (parts != null && parts.length == 4) {
String typeOfObject = parts[2];
String idObject = parts[3];
if (idObject != null) {
int n = -1;
try {
PrintWriter out = response.getWriter();
switch (typeOfObject) {
case "product":
n = server.deleteProduct(Integer.parseInt(idObject));
break;
case "category":
n = server.deleteCategory(Integer.parseInt(idObject));
break;
case "showroom":
n = server.deleteShowroom(Integer.parseInt(idObject));
break;
}
out.println("<result status=\"200\">" + n
+ " record</result>");
} catch (IllegalArgumentException ex) {
response.sendError(400);
}
} else {
response.sendError(400);
}
} else {
response.sendError(400);
}
}
private void selectAll(String[] parts, PrintWriter out,
HttpServletResponse response)
throws IOException, IllegalArgumentException {
String typeOfObject = parts[2];
switch (typeOfObject) {
case "products":
List<Product> products = server.selectProducts();
out.println("<result status=\"200\">");
out.println("<" + typeOfObject + ">");
for (Product product : products) {
out.println(product.toXml());
}
out.println("</"+ typeOfObject + ">");
out.println("</result>");
break;
case "categories":
out.println("<result status=\"200\">");
out.println("<" + typeOfObject + ">");
List<Category> categories = server.selectCategories();
for (Category product : categories) {
out.println(product.toXml());
}
out.println("</"+ typeOfObject + ">");
out.println("</result>");
break;
case "showrooms":
out.println("<result status=\"200\">");
out.println("<" + typeOfObject + ">");
List<Showroom> showrooms = server.selectShowrooms();
for (Showroom showroom : showrooms) {
out.println(showroom.toXml());
}
out.println("</"+ typeOfObject + ">");
out.println("</result>");
break;
default:response.sendError(400);
}
}
private void selectOne(String[] parts, PrintWriter out,
HttpServletResponse response) throws IOException {
String typeOfObject = parts[2];
String idObject = parts[3];
if (idObject != null) {
try {
switch (typeOfObject) {
case "product":
Product product = server.selectProduct(Integer.parseInt(idObject));
if (product != null) {
out.println("<result status=\"200\">");
out.println(product.toXml());
out.println("</result>");
} else{
out.println("<result status=\"200\"/>");
}
break;
case "category":
Category category = server.selectCategory(Integer.parseInt(idObject));
if (category != null) {
out.println("<result status=\"200\">");
out.println(category.toXml());
out.println("</result>");
}else{
out.println("<result status=\"200\"/>");
}
break;
case "showroom":
Showroom showroom = server.selectShowroom(Integer.parseInt(idObject));
if (showroom != null) {
out.println("<result status=\"200\">");
out.println(showroom.toXml());
out.println("</result>");
}else{
out.println("<result status=\"200\"/>");
}
break;
default:response.sendError(400);
}
} catch (IllegalArgumentException ex) {
response.sendError(400);
}
} else {
response.sendError(400);
}
}
private void insertOrUpdate(HttpServletRequest request,
HttpServletResponse response, int index) throws IOException {
String url = request.getRequestURI();
String[] parts = url.split("/");
if (parts != null && parts.length == 3) {
String typeOfObject = parts[2];
try {
String input = "";
BufferedReader br = new BufferedReader(request.getReader());
String line;
while ((line = br.readLine()) != null) {
input += line;
}
if (!input.isEmpty()) {
int n = -1;
switch (typeOfObject) {
case "product":
ProductParser productParser = new ProductParser(input, true);
Product product = productParser.parse();
if (index == 1) {
n = server.insertProduct(product);
} else {
n = server.updateProduct(product);
}
break;
case "category":
CategoryParser categoryParser = new CategoryParser(input, true);
Category category = categoryParser.parse();
if (index == 1) {
n = server.insertCategory(category);
} else {
n = server.updateCategory(category);
}
break;
case "showroom":
//da fare
break;
}
PrintWriter out = response.getWriter();
response.setContentType("application/xml;charset=UTF-8");
out.println("<result status=\"200\">" + n + " record</result>");
} else {
response.sendError(400);
}
} catch (Exception ex) {
response.sendError(500, ex.getLocalizedMessage());
}
} else {
response.sendError(400);
}
}
@Override
public void init() {
try {
Class.forName("com.mysql.jdbc.Driver");
server = new Server(getInitParameter("connectionString"));
} catch (ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
/**
* Returns a short description of the servlet.
*
* @return a String containing servlet description
*/
@Override
public String getServletInfo() {
return "Short description";
}
private void istructions(PrintWriter out) {
out.println("<SelectObjects>");
out.println("<product>");
out.println("<one>applicationaddress/product/id</one>");
out.println("<all>ApplicationAddress/products</all>");
out.println("</product>");
out.println("<product>");
out.println("<one>applicationaddress/showroom/id</one>");
out.println("<all>ApplicationAddress/showrooms</all>");
out.println("</product>");
out.println("<product>");
out.println("<one>ApplicationAddress/category/id</one>");
out.println("<all>ApplicationAddress/categories</all>");
out.println("</product>");
out.println("</SelectObjects>");
}
}
CategoryParser.java (commentare)
package parser;
import java.io.ByteArrayInputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import simple.bo.Category;
/**
*
* @author palma
*/
public class CategoryParser {
private final boolean xml;
private final String s;
private Element root = null;
public CategoryParser(String s, boolean xml) throws Exception {
this.xml = xml;
this.s = s;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new ByteArrayInputStream(s.getBytes()));
root = document.getDocumentElement();
}
public Category parse() throws Exception {
Category parsed = null;
if (xml) {
parsed = parseXml();
} else {
parsed = parseJson();
}
return parsed;
}
private Category parseXml() throws Exception {
Category c = null;
int id = -1;
NodeList list = root.getElementsByTagName("category");
if (list != null && list.getLength() > 0) {
String description = list.item(0).getFirstChild().getFirstChild().getNodeValue();
if (description != null && !description.isEmpty()) {
Node nId = list.item(0).getAttributes().getNamedItem("id");
String sid = null;
if (nId != null) {
sid = nId.getNodeValue();
try {
id = Integer.parseInt(sid);
} catch (NumberFormatException ex) {
throw new Exception(ex);
}
c = new Category(id, description);
} else {
c = new Category(description);
}
} else {
throw new Exception("XMl error: incorrect description ");
}
} else {
if (root.getNodeName().equals("category")) {
list = root.getElementsByTagName("description");
if (list != null && list.getLength() > 0) {
String description = list.item(0).getFirstChild().getNodeValue();
String sid = root.getAttribute("id");
if (sid != null && !sid.isEmpty()) {
try {
id = Integer.parseInt(sid);
} catch (NumberFormatException ex) {
throw new Exception(ex);
}
c = new Category(id, description);
} else {
c = new Category(description);
}
} else {
throw new Exception("XML error: no description");
}
} else {
throw new Exception("XML error: no category");
}
}
return c;
}
private Category parseJson() {
throw new UnsupportedOperationException("Not supported yet.");
}
}
ProductParser.java (commentare)
package parser; |
ShowroomParser.java
Manca, da implementare per esercizio.
RestClient.java
package restclient;
import java.awt.HeadlessException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.JOptionPane;
/**
*
* @author palma
*/
public class RestClient {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
//Per lavorare sulle tre tabella
String product="product";
String showroom="showroom";
String category="category";
//modifica ha l'id
String modifyProduct="<product id=\"20\"><name>xxxxx</name><description>"
+ "cambio description</description><price>23.45</price>"
+ "<category id=\"3\"><description>qqqqqq</description>"
+ "</category></product>";
String modifyShowroom="<showroom id=\"4\"><name>cambiato</name><address>address 8"
+ "</address><city>umpapa</city><phone>12345</phone><site>"
+ "www.puntopunto.it</site></showroom>";
String modifyCategory="<category id=\"2\"><description>modifica</description>"
+ "</category>";
//insert non ha id che viene messo dal db
String insertProduct="<product><name>nuovo prodottoo</name><description>cambio"
+ " description</description><price>23.45</price>"
+ "<category id=\"3\"><description>qqqqqq</description>"
+ "</category></product>";
String insertShowroom="<showroom><name>show8</name><address>address 8"
+ "</address><city>umpapa</city><phone>12345</phone><site>"
+ "www.puntopunto.it</site></showroom>";
String insertCategory="<category><description>nuova</description>"
+ "</category>";
insertOrModify(category, modifyCategory);
//id dell'oggetto da cancellare
//verificare sul db gli id presenti
//delete(category, "3");
}
private static void insertOrModify(String table,String object) throws HeadlessException {
try{
URL url = new URL("http://localhost:8080/SimpleRestDb/"+table);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Accept", "application/xml");
//conn.setRequestMethod("POST");
conn.setRequestMethod("PUT");
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "text/xml");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("charset", "utf-8");
PrintWriter out = new PrintWriter(conn.getOutputStream());
out.println(object);
out.flush();
out.close();
BufferedReader br = new BufferedReader(new InputStreamReader(
(conn.getInputStream())));
String output;
String s = "";
while ((output = br.readLine()) != null) {
s += output;
}
System.out.println(s);
conn.disconnect();
} catch (MalformedURLException ex) {
JOptionPane.showMessageDialog(null, "Malformed url");
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, "Connection problem");
}
}
private static void delete(String object,String id) {
try{
URL url = new URL("http://localhost:8080/RestDb/"+object+"/"+id);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Accept", "application/xml");
conn.setRequestMethod("DELETE");
conn.setRequestProperty("Content-Type", "text/xml");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("charset", "utf-8");
BufferedReader br = new BufferedReader(new InputStreamReader(
(conn.getInputStream())));
String output;
String s = "";
while ((output = br.readLine()) != null) {
s += output;
}
System.out.println(s);
conn.disconnect();
} catch (MalformedURLException ex) {
JOptionPane.showMessageDialog(null, "Malformed url");
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, "Connection problem");
}
}
}
Si prevedano anche opportuni messaggi di errore inviati dal server.
Si facciano tutte le ipotesi aggiuntive ritenute necessarie, purché giustificate.
Del protocollo comunicativo si dovranno definire:
Si prevedano anche opportuni messaggi di errore inviati dal server.
Si facciano tutte le ipotesi aggiuntive ritenute necessarie, purché giustificate.
Del protocollo comunicativo si dovranno definire:
Si prevedano anche opportuni messaggi di errore inviati dal server.
Progettare il protocollo di comunicazione giustificando opportunamente tutte le ipotesi aggiuntive.
Del protocollo comunicativo si dovranno definire:
Si prevedano anche opportuni messaggi di errore inviati dal server.
Le eventuali ipotesi aggiuntive dovranno essere opportunamente giustificate.
Prova del protocollo comunicativo
Si ipotizzi uno schema di sequenza temporale (se si conosce si può usare un sequence diagram UML) per verificare se la sequenza di comandi progettata funziona logicamente. A tale scopo si prevedano due entità, l’insieme dei centri di accettazione e smistamento e il server, che si scambiano i messaggi.
Si partirà con il centro di accettazione che invierà al server l’avvenuta accettazione di un nuovo pacco, seguito poi da tutti i centri di smistamento fino alla consegna finale. Ogni comando inviato da uno dei centri dovrà essere seguito dalla eventuale risposta del server.
La sequenza dovrà prevedere almeno una richiesta dell’elenco dei centri di accettazione/smistamento attraversati da parte di uno dei centri di accettazione/smistamento.
Tutti i dispositivi possono accettare due tipi di comandi:
Per entrambi i comandi si pensi ad una modalità per effettuare l’accensione o lo spegnimento immediato appena ricevuto il relativo comando.
Il protocollo di comunicazione deve essere confermato, quindi il dispositivo che ha ricevuto uno dei due comandi precedenti dovrà inviare una conferma al programma mittente. Inoltre si dovrà prevedere un controllo di errore nel caso il comando fosse stato rifiutato per qualsiasi ragione.
Si progetti il protocollo comunicativo corrispondente facendo tutte le ipotesi aggiuntive che si ritengono necessarie, purché siano opportunamente giustificate.
Il seguente link (in inglese qui) potrà esservi utile per migliorare il protocollo.
TFTP Formats
Type Op # Format without header
2 bytes string 1 byte string 1 byte
-----------------------------------------------
RRQ/ | 01/02 | Filename | 0 | Mode | 0 |
WRQ -----------------------------------------------
2 bytes 2 bytes n bytes
---------------------------------
DATA | 03 | Block # | Data |
---------------------------------
2 bytes 2 bytes
-------------------
ACK | 04 | Block # |
--------------------
2 bytes 2 bytes string 1 byte
----------------------------------------
ERROR | 05 | ErrorCode | ErrMsg | 0 |
----------------------------------------
Initial Connection Protocol for reading a file
1. Host A sends a "RRQ" to host B with source= A's TID, destination= 69.
2. Host B sends a "DATA" (with block number= 1) to host A with source= B's TID, destination= A's TID.
Il file XML deve descrivere le seguenti figure geometriche:
Una compagnia aerea ha una serie di voli low cost fra capitali europee. Per ogni tratta sono noti la capitale di partenza, la capitale di arrivo, i giorni della settimana in cui si tiene (ad esempio lunedì, mercoledì e venerdì), la capienza dell’aereo (si suppone che su una tratta ci siano sempre aerei con la stessa capienza), l’orario e il prezzo base. Si progetti il Web service che permetta le operazioni CRUD sulle tratte e il relativo client che lo interroga.
Esempio di operazioni
Richiesta di tutte le tratte
GET http://localhost:8080/Esercitazione2
Richiesta della tratta con id = 5
GET http://localhost:8080/Esercitazione2/5
Inserimento di una nuova tratta
POST http://localhost:8080/Esercitazione2
dati inviati dal POST in formato XML
<tratta> </tratta> |
Modifica dei dati della tratta con id = 5. I dati modificati sono evidenziati in grassetto.
PUT http://localhost:8080/Esercitazione2
<tratta id="5"> <partenza>Roma</partenza> |
Eliminazione della tratta con id = 5
DELETE http://localhost:8080/Esercitazione2/5
La nuova società MyShow vuole fornire via Internet un nuovo servizio agli utenti registrati sul suo sito. Un utente registrato, dopo aver fornito le credenziali di accesso al sito, seleziona una città per ricevere la lista dei film proiettati.
Selezionando uno dei cinema elencati, l’utente otterrà la lista dei film proiettati nella giornata con le relative informazioni per ogni proiezione (titolo del film, orari di proiezione, ecc.)
Per fornire l’elenco dei cinema, il sito della compagnia MyShow utilizza un servizio di terze parti offerto dalla compagnia MovieDB, che per ogni cinema di una data città fornisce il nome del cinema, l’indirizzo, il numero di telefono, l’e-mail e gli orari di apertura. Inoltre, relativamente alle proiezioni dei singoli cinema, il servizio offre l’elenco di tutti i film proiettati, e per ogni film fornisce il titolo, la descrizione, il rating, la durata, gli orari di proiezione e il periodo di proiezione.
Si progetti una architettura distribuita basata sulla tecnologia dei Web service REST utilizzando le servlet, che implementi il servizio offerto dalla compagnia MovieDB alla società MyShow, e il relativo servizio offerto da quest’ultima ai suoi utenti.
Il progetto dovrà comprendere l’architettura complessiva del sistema suddivisa in tier, dei quali si dovrà fornire una rappresentazione grafica e una descrizione di massima dei tier coinvolti e della relativa funzione, considerando i punti di seguito indicati.
Infine si proponga e si illustri un progetto di massima del sistema hardware/software che comporti la installazione del server web:
Per ciascun scenario si illustrino le tecniche di sicurezza che rendono sicuro l'accesso al server.
Se utilizzato durante le lezioni di informatica, o durante gli anni precedenti, potrete illustrare la collaborazione fra i vari elementi del sistema distribuito utilizzando dei diagrammi UML[5].
Si realizzi un prototipo funzionante del sistema sopra progettato. Se possibile si utilizzino tanti computer quanti sono i sistemi informativi coinvolti.
Un’azienda produttrice di automobili ha circa 10000 centri di assistenza distribuiti in tutto il mondo. Ogni cliente che acquista un’automobile può richiedere la manutenzione della stessa in garanzia nel caso in cui essa presenti dei problemi di funzionamento. Tutte le spese relative alla gestione della manutenzione dell’automobile sono a totale carico dell’azienda produttrice.
Per consentire una migliore integrazione tra la dirigenza dell’azienda ed i centri di assistenza, l’azienda permette ai centri di poter accedere rapidamente ed in tempo reale ai dati principali riguardanti il veicolo che necessita assistenza. Tali dati comprendono, ad esempio, i dati del proprietario, la validità del contratto di assistenza, gli interventi di manutenzione precedenti per quel veicolo, i difetti frequenti riscontrati per il modello di veicolo, eccetera.
L’azienda ha quindi due necessità:
I centri di assistenza, nei vari continenti, possiedono ognuno un portale Internet, indipendente da quello dell’azienda, ma che segue un layout comune fornito da essa, attraverso il quale possono offrire alcuni servizi personalizzati ai propri clienti. Il portale è anche usato dal centro di assistenza per accedere al servizio offerto dall’azienda per ottenere le informazioni su un’automobile o per inviare i dati di un nuovo intervento effettuato sull’auto di un cliente.
Si progetti il sistema descritto, facendo tutte le ipotesi aggiuntive e prevedendo una architetture multi-tier, considerando tutti i punti seguenti.
Se utilizzato durante le lezioni di informatica, o durante gli anni precedenti, potrete illustrare la collaborazione fra i vari elementi del sistema distribuito utilizzando dei diagrammi UML[6].
Si realizzi un prototipo funzionante del sistema sopra progettato. Se possibile si utilizzino tanti computer quanti sono i sistemi informativi coinvolti.
La società GTT (Gruppo Trasporti Timbuktu) ha recentemente integrato una serie di altre società locali di trasporti dello stato di Mali. Come parte della nuova strategia gestionale, la GTT intende fornire ai propri utenti un servizio centralizzato di consultazione degli orari, di calcolo dei percorsi e di acquisto dei documenti di viaggio (biglietti).
La società GTT costruisce quindi un proprio sistema informativo, accessibile dai propri utenti mediante un portale web, che offre le funzioni citate. Ovviamente le informazioni riguardo i tragitti, gli orari, i prezzi sono di competenza delle singole società locali di trasporti, ciascuna delle quali ha nel tempo sviluppato il proprio sistema informativo.
Descrivere una possibile modalità di integrazione (quale tecnologia usare, quali dati scambiare, dove memorizzare i dati, ...) tra i sistemi informativi della GTT e quelli delle società locali di trasporti, in grado di supportare le funzionalità descritte (consultazione degli orari, calcolo automatico dei percorsi, acquisto dei documenti di viaggio). In particolare si consideri che per il calcolo dei percorsi è necessario integrare i dati delle diverse società locali, proponendo anche percorsi “misti”, ossia composti da tratte gestite da società locali distinte.
Gli studenti di Dottorato di Ricerca, nella loro formazione specialistica, devono spesso seguire corsi di dottorato che si tengono presso varie università, anche diverse dalla propria università di iscrizione.
Le Università intendono potenziare il proprio sistema informativo per permettere a tali studenti di trattare anche i corsi presso altre università alla stessa stregua dei corsi “interni”. Possiamo supporre che ciascuna università abbia un sistema equivalente al sistema proposto dal Politecnico di Torino, in cui esiste la gestione del carico didattico, prenotazione esami, visione del libretto elettronico, ecc. Tuttavia, possiamo essere certi che le varie università abbiamo implementato il sistema in modi anche molto diversi, sia dal punto di vista dell'interfaccia utente che dal punto di vista delle tecnologie coinvolte.
Descrivere una possibile soluzione al problema dell'integrazione tra i sistemi informativi, che permetta ad uno studente iscritto in una qualsiasi università partecipante di visionare i corsi disponibili, inserirli nel proprio carico didattico, comparire negli elenchi ufficiali visibili al docente, prenotarsi all'esame, ed avere registrato il proprio voto.
In vista delle prossime Olimpiadi, il comitato olimpico del paese X intende offrire a tutti i giornalisti (ed in generale ai media interessati) un servizio in tempo reale con i risultati di tutti gli eventi sportivi in corso.
In particolare, dovranno essere disponibili in modalità distribuita: i calendari delle gare, i risultati di ciascuna gara (in cui per ciascun atleta o squadra partecipante è indicato il punteggio o misura significativa), ed il “medagliere” (le medaglie assegnate ai vari atleti o squadre, divise per nazione e per tipo di medaglia).
Si tenga presente che anche l’Olimpiade stessa è un evento “distribuito”, poiché le gare si svolgono in luoghi geograficamente anche molto lontani; per questo motivo occorre identificare anche la modalità migliore di aggiornamento delle informazioni da parte degli organizzatori e dei giudici.
Si progetti, per conto del comitato olimpico del paese X, il sistema informativo che dovrà gestire la pubblicazione di tali dati e la relativa gestione ed aggiornamento.
Il Ministero delle Finanze intende investire in un sistema informativo in grado di combattere alla radice il fenomeno dell’evasione fiscale. Il principio guida di tale riforma –denominata Grande Fratello– è che esistono già molti sistemi informativi “parziali”, alcuni pubblici (catasto, anagrafe dei residenti, motorizzazione civile, ...) ed altri privati (banche, assicurazioni, finanziarie, ...). Il Ministero ritiene che “incrociando” tali dati sia possibile rilevare molte “anomalie” o situazioni sospette, grazie al fatto che tutti i sistemi informativi fanno capo comunque all’identificazione mediante codice fiscale (si tralasci il caso degli evasori totali o di coloro che si servono di prestanomi che non verrebbero comunque identificati da questo sistema).
In particolare, il Ministero dovrà poter consultare, in ogni momento, i dati relativi ad un determinato contribuente nelle varie base dati disponibili, ciascuna delle quali ovviamente conterrà dati di natura diversa. Deve inoltre essere possibile, da parte da qualsiasi ente, “segnalare” un determinato contribuente, qualora l’ente abbia il sospetto di comportamenti anomali.
Si progetti, per conto del Ministero delle Finanze, il sistema informativo che dovrà gestire la condivisione di tali dati ed il meccanismo di segnalazione. Le scelte progettuali dovranno mirare alla minimizzazione dei costi per i vari sistemi informativi che dovranno essere adattati ed integrati, mentre non vi sono praticamente vincoli di complessità sul sistema del Ministero.
Una grande catena di supermercati intende realizzare un sistema di videosorveglianza intelligente, in grado di osservare i comportamenti degli utenti nei propri punti vendita e dedurne informazioni di vario tipo (ad esempio, l’interesse relativo ai prodotti esposti, oppure sul rischio di taccheggio).
Il sistema è basato su una serie di Unità Video Intelligenti (UVI) dotate ciascuna di una telecamera e di un computer collegato alla rete. Ciascuna UVI (ve ne possono essere diverse in ciascun punto vendita) analizza il flusso di immagini provenienti dalla telecamera, e ne estrae alcuni dati sintetici significativi. Ad esempio, associa ad ogni persona diversa un diverso ID numerico autogenerato, rappresenta tale persona mediante delle caratteristiche biometriche in grado di riconoscere se la stessa persona compare nel campo visivo di più UVI, identifica una serie di “gesti” o “posture” (es: guarda, prende, confronta, ...) per ciascuna persona. In aggiunta, ciascuna UVI salva anche sul proprio hard disk i dati completi (tutti i fotogrammi e tutti i dati da essi estratti) relativi alle ultime 48 ore.
L’insieme di tutti gli UVI è collegato con il Centro di Elaborazione della grande catena, il quale è deputato a ricevere le informazioni sintetiche da tutti gli UVI supervisionati ed a compiere delle ulteriori elaborazioni sulle informazioni ricevute. Per alcune di tali elaborazioni (es. tracciamento del percorso di un cliente tra i campi visivi dei vari UVI) sono sufficienti i dati sintetici forniti dagli UVI. Per altre elaborazioni (es. invio alla polizia del filmato di un potenziale taccheggiatore) sono invece necessari i dati completi.
Ovviamente le capacità trasmissive di Internet non permettono in alcun modo la trasmissione continua, completa ed in tempo reale dei dati completi da parte di tutti gli UVI contemporaneamente, mentre invece per i dati sintetici non vi sono problemi di banda.
Si progetti, per conto della grande catena, il sistema informativo che dovrà gestire la raccolta dei dati dagli UVI ed il meccanismo di comunicazione tra questi ultimi ed il centro di elaborazione. Le scelte progettuali dovranno mirare alla compatibilità con la banda disponibile su Internet, permettendo però una rilevazione tempestiva di eventuali comportamenti anomali.
Meini G., Formichi F., Tecnologie di progettazione di sistemi informatici e di telecomunicazioni, vol. 3, Zanichelli, 2017
V. Auletta, Corso di Reti di calcolatori, http://www.di.unisa.it/professori/auletta/DIDATTICA/RETI_04/, autunno 2003
M. Maggini, Corso di Reti di calcolatori, http://www.dii.unisi.it/~maggini/Teaching/reti_di_calcolatori.html, a.a. 2015/2016
Università di Verona, Dipartimento di Informatica, Scrittura di applicazioni di rete, http://www.di.univr.it/documenti/OccorrenzaIns/matdid/matdid546139.pdf, ultimo accesso gennaio 2017
D. J. Dubois, Programmazione di rete in Java, http://home.deib.polimi.it/dubois/provafinale/rete.pdf, Politecnico di Milano, ultimo accesso gennaio 2017
G. Anastasi, N. Iardella, Corso di Reti Informatiche, http://www2.ing.unipi.it/~a008149/corsi/reti/materiale.html, Università di Pisa, ultimo accesso gennaio 2017
P. Camagni, R. Nikolassy, Tecnologie e progettazione di sistemi informatici e di telecomunicazioni, vol. 3, ed. Hoepli, 2014.
2veritasium, Transistors & The End of Moore's Law, https://youtu.be/rtI5wRyHpTg
A337 | S400, 3 Tier Client Server Architecture, https://youtu.be/jJYv-nfkMXk?list=PLmPpJG5-RKf0RUQ7VYjHn6pnoWT2__4CM
Barbero T., Clegg J., Programmare Percorsi CLIL, Carocci, Roma 2005.
Bubble.us, https://bubbl.us/
CLIL Media, http://clilmedia.com/
Cmap, http://www.cmaptools.com/
Christopher Kalodikis, Client-server model, https://youtu.be/ntp9XZ4hOrY?list=PLmPpJG5-RKf0RUQ7VYjHn6pnoWT2__4CM
V. Cardellini, Classificazione delle Architetture Parallele, Corso di Sistemi Distribuiti a.a. 2009/2010, Università degli Studi di Roma “Tor Vergata”, Facoltà di Ingegneria
English tutorial, http://www.englishpage.com/
G. Coulouris, J. Dollimore, T. Kindberg, G. Blair, Distributed system, Concept and Design, Fifth Edition, Addison-Wesley, May 2011, http://www.cdk5.net/wp/
Games to learn English, http://www.engames.eu/
Google, http://www.google.it
Grewal, N-Tier Architecture for kids, https://youtu.be/8gfTBQhh1kM?list=PLmPpJG5-RKf0RUQ7VYjHn6pnoWT2__4CM
C. Kalodikis, Client-Server Model, https://youtu.be/IOnWn5u-sXE
Kurzgesagt – In a Nutshell, Quantum Computers Explained – Limits of Human Technology, https://youtu.be/JhHMJCUmq28
Linking Words for IELTS Speaking: Word List & Tips, http://ieltsliz.com/linking-words-for-ielts-speaking/
ovp , How Cloud Computing Work, https://youtu.be/DGDtujmOBKc
PieterExplainsTech, UDP and TCP: Comparison of Transport Protocols, https://youtu.be/Vdc8TCESIg8
S. Haridi, Distributed Algorithms, https://www.youtube.com/playlist?list=PL700757A5D4B3F368
Regis University CPS SCIS, Middleware Concepts, https://youtu.be/S8sgGXUqw30?list=PLmPpJG5-RKf0RUQ7VYjHn6pnoWT2__4CM
Regis University CPS SCIS, Middleware Architecture, https://youtu.be/E2jFOTDK0tY?list=PLmPpJG5-RKf0RUQ7VYjHn6pnoWT2__4CM
The m-Power Platform, n-Tier Architecture Explained, https://youtu.be/KlHvRKSH4pk
Udacity, Georgia Tech, Advanced Operating Systems part 2 of 4, https://www.youtube.com/playlist?list=PLAwxTw4SYaPm4vV1XbFV93ZuT2saSq1hO
Udacity, Georgia Tech, HPCA: Part 5, https://youtu.be/WKXbvhkzBUo?list=PLmPpJG5-RKf0RUQ7VYjHn6pnoWT2__4CM
Veritasium, How Does a Quantum Computer Work?, https://youtu.be/g_IaVepNDT4
WhatIs.com, http://whatis.techtarget.com/
Wikipedia, The Free Encyclopedia, https://www.wikipedia.org/
P. Camagni, R. Nikolassy, Tecnologie e progettazione di sistemi informatici e di telecomunicazioni, vol. 3, ed. Hoepli, 2014
G. Meini, F. Formichi, Tecnologie e progettazione di sistemi informatici e di telecomunicazioni, vol. 3, Zanichelli, 2014
Wikipedia, The Free Encyclopedia, https://www.wikipedia.org/
R. Golia, Design pattern per esempi: i GoF creazionali, Microsoft® Development Network, https://msdn.microsoft.com/it-it/library/cc185067.aspx#ID0ECBAC, novembre 2006, ultima visita gennaio 2017
TutorialsPoint, Design Pattern - Factory Pattern, https://www.tutorialspoint.com/design_pattern/factory_pattern.htm, ultima visita gennaio 2017
B. McLaughlin & J. Edelson, Java & XML, 3rd edition, O’Reilly, 2006
Mkyong.com, How to read XML file in Java – (DOM Parser), http://www.mkyong.com/java/how-to-read-xml-file-in-java-dom-parser/, ultima visita gennaio 2017
P. Camagni, R. Nikolassy, Tecnologie e progettazione di sistemi informatici e di telecomunicazioni, vol. 3, ed. Hoepli, 2014
w3schools.com, HTTP Methods, http://www.w3schools.com/tags/ref_httpmethods.asp, ultimo accesso gennaio 2017
JavaTpoint, Web Terminology, http://www.javatpoint.com/web-terminology, ultimo accesso gennaio 2017
Wikipedia, L’enciclopedia libera, https://it.wikipedia.org
Lifewire, HTTP Status Codes, https://www.lifewire.com/http-status-codes-2625907, 28 novembre 2016
Mikalai Zaikin, IBM WebSphere Application Server Network Deployment V8.0 Core Administration Guide, http://java.boot.by/ibm-317/index.html, agosto 2013
Jayson Online, Configuring Apache in front of JBoss Application Server Using mod_jk, http://www.jaysonjc.com/programming/configuring-apache-in-front-of-jboss-application-server-using-mod_jk.html, 19 aprile 2011
Chiarelli A., RESTful Web Services – La Guida, http://www.html.it/guide/restful-web-services-la-guida/, HTML.it, 6 febbraio 2012
G. Meini, F. Formichi, Tecnologie e progettazione di sistemi informatici e di telecomunicazioni, vol. 3, Zanichelli, 2014
Java tutorial for beginners, http://www.java2blog.com/2013/03/web-service-tutorial.html, ultimo accesso gennaio 2017
ORACLE, Java™ Platform, Standard Edition 7 API Specification, https://docs.oracle.com/javase/7/docs/api/, ultimo accesso gennaio 2017
Stack Overflow, http://stackoverflow.com/
Javarevisited, Java Synchronization Tutorial : What, How and Why?,
http://javarevisited.blogspot.it/2011/04/synchronization-in-java-synchronized.html
aprile 2011, ultimo accesso febbraio 2017
Java Brains, Developing RESTful APIs with JAX-RS, https://www.youtube.com/playlist?list=PLqq-6Pq4lTTZh5U8RbdXq0WaYvZBz2rbn, ultimo accesso marzo 2017
L. Petrosino, Luca’s blog, http://luca-petrosino.blogspot.it/, ultimo accesso marzo 2017
Team SGTEAM, Progetto MONK - Gestione del protocollo della corrispondenza della biblioteca di Santa Giustina, http://www.dei.unipd.it/~fantozzi/MONK/descr_progetto.html, 30 settembre 1999, ultimo accesso marzo 2017
Maria Lăiu, Botnaru Elvis, Nicolae Abăcioaiei, Ionuț Stincescu and Dumitru Lesnic, pentastagiu, ultrashop - Wiki, https://github.com/pentastagiu/ultrashop/wiki, ultimo accesso marzo 2017
F. Corno, D. Bonino, 01KTF - Architetture distribuite per i sistemi infomativi aziendali, https://elite.polito.it/teaching/past-courses/19-01ktf?showall=, e-lite research group, 9 marzo 2008, ultimo accesso aprile 2017
Agile Modeling (AM) Home Page, Effective Practices for Modeling and Documentation, http://agilemodeling.com/, ultimo accesso aprile 2017
The Unified Modeling Language, http://www.uml-diagrams.org/, ultimo accesso aprile 2017
IBM developerWorks®, https://www.ibm.com/developerworks/, ultimo accesso aprile 2017
WebSequenceDiagrams, https://www.websequencediagrams.com/, ultimo accesso aprile 2017
StarUML, http://staruml.io/, ultimo accesso agosto 2017
Violet UML Editor, http://alexdp.free.fr/violetumleditor/page.php, ultimo accesso agosto 2017
[1] I sistemi legacy (legacy system) identificano i sistemi obsoleti presenti in una azienda ereditati dalle prime fasi di informatizzazione della stessa. Spesso rivestono ruoli critici e possono anche avere dimensioni notevoli, ma nonostante la possibile lunga storia di interventi di manutenzioni, non sono mai stati rimpiazzati perché sostanzialmente funzionano ancora e, probabilmente, l’attività svolta non è ancora stata sostituita da tecnologie più recenti.
[3] Document Object Model.
[4] Per questi esercizi si ringrazia il Prof. Giuliano Bellucci per la fantasia nell’inventarli e il Prof. Fulvio Corno del Politecnico di Torino che ha reso disponibile sul Web il materiale del corso di Architetture distribuite per i sistemi informativi aziendali dell’a.a. 2008/2009. Gli esercizi sono stati riadattati per un corso di scuola secondaria di secondo grado.