modulo 1 protocolli di trasporto
Transcript
modulo 1 protocolli di trasporto
MODULO 1 PROTOCOLLI DI TRASPORTO Servizi di rete TCP 7 UDP 21 HTTP 25 WebSocket Client/Server Modulo 2 TPSIT 5 2 46 50 79 um 1 di 434 SERVIZI DI RETE INTRODUZIONE La comunicazione tra PC avviene in base alle regole stabilite nei protocolli di trasmissione che possono essere suddivisi nei seguenti tipi. 1. Protocolli applicativi: dipendono dallo specifico servizio disponibile in rete, si servono congiuntamente delle funzioni svolte dai protocolli di trasporto TCP (Transmission Control Protocol) e UDP (User Datagram Protocol) e dal protocollo di rete per trasferirsi i loro messaggi e dati; per esempio HTTP (HyperText Transfer Protocol) definisce il formato delle richieste e risposte sul web. 2. Protocolli di trasporto: si preoccupano di stabilire le regole per la trasmissione dei singoli byte o bit; per esempio TCP prende in carico i messaggi HTTP e quelli degli altri protocolli applicativi e gestisce le singole conversazioni tra i client e i server. 3. Protocollo di rete: il più comune è IP (Internet Protocol) che instrada all’host destinatario i segmenti TCP, imbustati in pacchetti con il suo indirizzo logico. 4. Protocolli di accesso alla rete: è gestito dalle NIC (Network Interface Card) che svolgono due funzioni. 4.1. Gestione del collegamento dati: prende i pacchetti e li incapsula in trame adatte alla trasmissione sulla LAN (Local Area Network), per esempio Ethernet, assegnando loro se serve (le linee seriali punto-punto non lo necessitano) un indirizzo fisico dell’host destinatario. 4.2. Trasmissione fisica sulla rete: governa il modo in cui i bit sono rappresentati e spediti sui mezzi fisici e interpretati dall’host ricevente. PROTOCOLLI DI TRASPORTO Quando le applicazioni, come FTP (File Transfer Protocol) o HTTP, hanno bisogno di conferma della ricezione dei loro messaggi, usano TCP che funziona come le raccomandate con ricevuta di ritorno. TCP suddivide i dati in segmenti, li numera, li affida al protocollo IP e prende nota di quelli trasmessi. Se non riceve conferma entro un certo tempo, assume che si siano persi e rimanda solo quelli non confermati. Presso il ricevente, TCP riassembla i dati per la consegna. Se invece non occorre conferma di ricevuta e l’importante è andare veloci, è meglio usare UDP che è come una lettera normale. UDP è preferibile nelle applicazioni di “streaming” video e audio e nei sistemi VoIP (Voice over IP), dove le ritrasmissioni sono indesiderate in quanto peggiorerebbero le cose: invece di offrire all’utente una breve interruzione dell’audio o del video, fermerebbero la riproduzione in attesa dei segmenti ritrasmessi e l’interruzione sarebbe più lunga e fastidiosa. NUMERI DI PORTA Ogni server ha un unico indirizzo ma gestisce molti servizi, per esempio server web, FTP e posta SMTP (Simple Mail Transfer Protocol). Di conseguenza, dovrebbe avere altrettanti server su altrettanti indirizzi diversi. Per evitare un proliferare d’indirizzi inutili esistono le porte, in pratica numeri cui sono associati i vari servizi. Inoltre, per ricevere contemporaneamente dati eterogenei tra la stessa coppia di host, è stato sviluppato il concetto di multiplazione di porte, grazie al quale ogni protocollo applicativo utilizza una specifica porta, un numero di 16 bit (0-65535), per distinguere, a TPSIT 5 um 2 di 434 livello di trasporto, i propri pacchetti in transito. Sono concettualmente simili agli interni di un palazzo: una volta arrivato ad un certo numero civico, indirizzo IP, il pacchetto è consegnato all’interno, la porta che il mittente ha indicato nell’intestazione del pacchetto. Sono divisi in tre gruppi e gestiti da ICANN (Internet Corporation for Assigned Names and Numbers). 1. Well Known Ports: letteralmente porte ben note, da 0 a 1023 sono i servizi di rete più diffusi, per esempio HTTP e SMTP, alcune eccezioni sono rappresentate da servizi quali ICQ (I Seek You), Real Audio e alcuni servizi di proxying. 2. Registered Ports: da 1024 a 49152 (3/4 di 65536) possono essere usate sia come porte sorgente scelte dai client sia per applicazioni registrate, per esempio IM (Instant Messaging). 3. Private Ports: da 49152 a 65535, sono usate liberamente solo come porte sorgente. Ogni tipo di comunicazione prevede sempre almeno due interlocutori che comunichino sulla stessa porta contemporaneamente, altrimenti chi manda il messaggio troverebbe la “porta chiusa” dell’altro e viceversa. La comunicazione tra client/server deve quindi comprendere quattro valori numerici. 1. L’indirizzo IP del destinatario. 2. La porta di destinazione per consentire una risposta. 3. L’indirizzo IP del mittente. 4. La porta dell’applicazione che ha avviato la comunicazione. Il numero di porta è contenuto in ogni segmento, in modo che i segmenti di una conversazione tra due host siano sempre destinati agli stessi servizi che la stanno generando. In pratica, le porte sono due. 1. Porta destinazione: è assegnata dai client, per indicare quale servizio di un server è richiesto; per esempio un server potrebbe offrire servizi web (80) e FTP (21). 2. Porta sorgente: è generata casualmente dal client, sopra 1024 e identifica la conversazione lato client, tra le varie che esso può aprire verso diversi server. Entrambi i numeri di porta si trovano nel segmento che è poi incapsulato in un pacchetto, con gli indirizzi IP del mittente e del destinatario. L’insieme di un indirizzo IP, della corrispondente porta e del protocollo TCP o UDP usato prende il nome di socket. I milioni di conversazioni contemporanee sul web sono mantenuti distinti da coppie di socket. Esempio. Nella coppia indirizzo IP/porta TCP i due valori sono separati con i due punti. Indirizzo IP = 132.125.18.35. Porta TCP = 80. Socket = 132.125.18.35:80. TPSIT 5 um 3 di 434 Le porte TCP attive si controllano con il seguente comando. C:\>netstat -a Connessioni attive Proto Indirizzo locale TCP ubertini:smtp TCP ubertini:http TCP ubertini:epmap TCP ubertini:https TCP ubertini:microsoft-ds TCP ubertini:1025 TCP ubertini:1027 TCP ubertini:1029 Indirizzo esterno 0.0.0.0:0 0.0.0.0:0 0.0.0.0:0 0.0.0.0:0 0.0.0.0:0 0.0.0.0:0 0.0.0.0:0 0.0.0.0:0 Servizio FTP Data FTP Control SSH (Secure SHell) TELNET (TELetype on NETwork) SMTP DNS (Domain Name System) TFTP (Trivial File Transfer Protocol) finger HTTP POP3 RPC (Remote Procedure Call) NNTP (Network News Transport Protocol) NTP (Network Time Protocol) IMAP (Internet Message Access Protocol) News SNMP (Simple Management Network Protocol) SNMP (traps) BGP (Border Gateway Protocol) HTTPS (HTTP over Secure Socket Layer) rlogin rexec talk ntalk RIP (Routing Information Protocol) Socks Open VPN (Virtual Private Network) IPSec (IP Security) L2TP (Layer Two Tunneling Protocol) Open Windows NFS (Network File System) X11 TPSIT 5 Stato LISTENING LISTENING LISTENING LISTENING LISTENING LISTENING LISTENING LISTENING Numero di Porta/Protocollo 20/TCP 21/TCP 22/TCP AND 22/UDP 23/TCP 25/TCP 53/TCP AND 53/UDP 69/UDP 79/TCP 80/TCP 110/TCP 111/UDP 119/TCP 123/TCP AND 123/UDP 143/TCP 144/TCP 161/UDP 162/UDP 179/TCP 443/TCP 513/TCP 514/TCP 517/TCP AND 517/UDP 518/TCP AND 518/UDP 520/UDP 1080/TCP 1194/TCP AND 1194/UDP 1293/TCP AND 1293/UDP 1701/UDP 2000/TCP AND 2000/UDP 2049/TCP 6000/TCP AND 6000/UDP um 4 di 434 SOCKET Letteralmente “presa”, “attacco”, è il modello più utilizzato nella realizzazione di una comunicazione via rete tra dispositivi o tra più applicazioni all’interno dello stesso dispositivo. La prima volta questo meccanismo fu impiegato nella BSD (Berkeley Software Distribution) dell’università della California a Berkeley. Un socket è una porta di comunicazione, concettualmente simile ad una presa elettrica. Qualsiasi cosa progettata per comunicare tramite il protocollo standard TCP/IP, può collegarsi ad un socket e comunicare tramite questa porta di comunicazione, così come un qualsiasi apparecchio elettrico alimentato con la corrente 220 V può collegarsi ad una presa elettrica e sfruttare la tensione che la rete di distribuzione elettrica mette a disposizione. Nel mondo dei socket la rete di distribuzione è locale, geografica o Internet e invece dell’elettricità, nella rete viaggiano pacchetti TCP/IP. In questo modo i socket forniscono un’altra astrazione oltre a quella fornita da TCP/IP che permette di far comunicare dispositivi diversi che utilizzano lo stesso protocollo. In altre parole, i socket possono rappresentare il livello 6 del modello ISO/OSI (International Standard Organization/Open Systems Interconnection) che nel TCP/IP è assente. Il TCP ha bisogno di sapere a quale porta sia destinato il pacchetto e da quale porta arrivi, in modo tale che possa rispondere con un ACK (ACKnowledge) a pacchetto ricevuto. In maniera analoga, un pacchetto del livello IP porta con sé l’indirizzo IP d’origine e quello di destinazione. Un socket è costituito dal concatenamento di un indirizzo IP (identifica il PC sulla rete) con il numero di porta (identifica un’applicazione che risiede su quel PC). Un socket identifica una coppia univoca composta da PC e applicazione, i cui numeri sono separati da (:). Un socket è un oggetto attraverso il quale un’applicazione che ne fa uso in modo specifico invia o riceve dati attraverso la rete con altri socket che fanno uso dell’IPS (Internet Protocol Suite). I socket sono bidirezionali ossia consentono nello stesso momento un flusso di dati sia in entrata sia in uscita. Esistono due tipi di socket. 1. Stream sockets che consentono un flusso di dati continuo e illimitato con la garanzia di evitare duplicazione di dati inviati. TPSIT 5 um 5 di 434 2. Datagram sockets non garantiscono che l’ordine di ricezione dei dati sia lo stesso di quello d’invio con il rischio di non poter evitare duplicazione di dati, in altre parole gli stessi pacchetti di dati possono arrivare a destinazione più volte. Un server utilizzerà in prevalenza passive socket (passivi), impiegati sulle macchine che devono rimanere in attesa di richieste di connessione. Quando un client richiede una connessione ad un server crea invece un active socket. L’unica differenza fra i due è la maniera con cui sono gestiti dal S/W e dal SO (Sistema Operativo). Stabilita la connessione tra i due socket, lo scambio d’informazioni avviene in modo full duplex utilizzando i metodi Send e Receive. Winsock (Windows Sockets) È una DLL (Dynamic Link Library), progettata nel 1991, usata da Windows. Descrive le comunicazioni da e per lo stack TCP/IP, in pratica l’applicazione dice a Winsock che cosa fare, queste istruzioni passano poi allo stack TCP/IP che, a sua volta, le passa a Internet. Il controllo Winsock consente di utilizzare i protocolli di trasmissione TCP e UDP. INET (INtErnet Transfer) Permette d’inviare e di recuperare file usando FTP, HTTP e comandi specifici per eseguire operazioni per entrambi i protocolli. Per esempio, cancellare e rinominare i file con i comandi DELETE e RENAME. Supporta il trasferimento dei file sincrono e asincrono. È invisibile in un form, come il controllo Timer; tutte le sue funzionalità sono implementate attraverso i suoi metodi e non richiede UI (User Interface). Nella programmazione managed, il programmatore pone la propria attenzione sul livello più alto della comunicazione, trascurando le modalità di scambio dei pacchetti e concentrandosi sui dati e sulla businnes logic. Le operazioni di basso livello sono gestite dal SO e sono del tutto trasparenti sia per il programmatore sia per l’applicazione stessa. Localhost Ogni interfaccia di rete connessa a Internet ha un indirizzo IP. Lo standard prevede per ogni PC anche un’interfaccia interna, chiamata di loopback, il cui indirizzo è 127.0.0.1, maschera 255.0.0.0 e indirizzo di rete 127.0.0.0. Usata dalle applicazioni client/server per parlarsi direttamente, anche in assenza di connessioni esterne. TPSIT 5 um 6 di 434 TCP INTRODUZIONE Il namespace Sytem.Net è associato a operazioni di alto livello, come l’invio e la ricezione di file e richieste HTTP. Il namespace System.Net.Sockets è coinvolto in operazioni di basso livello giacché fornisce un’implementazione gestita dell’interfaccia Winsock che permette uno stretto controllo sull’accesso alla rete. Per esempio, socket permette un controllo capillare di tutte le impostazioni di connessione. Oltre a questo, c’è una serie di strumenti specializzati nella comunicazione con i protocolli applicativi, come HTTP o FTP che sollevano il programmatore dall’onere della gestione diretta dei byte. TPSIT 5 um 7 di 434 TCP è un protocollo che garantisce la corretta trasmissione dei dati inviati e ricevuti, conservandone la corretta sequenza di trasmissione e ricezione. Questo controllo è oneroso in termini di tempo di trasmissione, per contro, offre una notevole garanzia in materia di qualità della comunicazione. È un protocollo orientato alla connessione concepito per la trasmissione da punto a punto tra un client e un server, per questo in un’applicazione che lo utilizza, la cosa fondamentale da stabilire è se si tratta di un’applicazione client o server. In altre parole, si può dire che il funzionamento del TCP è analogo al funzionamento del telefono tradizionale, prima di dialogare bisogna stabilire una connessione. Il protocollo TCP è indicato per applicazioni client/server, in altre parole per le applicazioni che permettono lo scambio di dati tra una macchina denominata server e una o più macchine denominate client. Il protocollo TCP è ideale per sviluppare applicazioni come giochi che interagiscono con utenti in rete o in chat, oppure nel trasferimento file e dati perché l’attesa è giustificata da un dato completamente valido e utilizzabile dall’applicazione del richiedente, per esempio il protocollo HTTP si appoggia a TCP perché un file HTML (HyperText Markup Language) deve arrivare al browser integro e interpretabile. In pratica, il protocollo TCP instaura un canale di comunicazione bidirezionale tra un client e un server: il client può inviare dati al server e il server può inviare dati al client, in pratica supera la limitazione del protocollo HTTP. Differenze tra protocollo HTTP e TCP TCP non è basato su un pattern richiesta/risposta ma su un flusso bidirezionale. TCP non è basato su messaggi. Questo significa. Quando il cIient o il server inviano un messaggio, questi non rimangono in attesa di una risposta. I dati che sono scambiati non hanno il formato di un messaggio ma sono array di byte. Le classi per la gestione dei protocolli di trasporto si trovano già implementate nella BCL (Base Class Library) del .NET Framework e sono TcpClient e TcpListener che semplificano il lavoro, lasciando in disparte molti dei dettagli implementativi di basso livello TPSIT 5 um 8 di 434 che implica l’uso dei socket. La classe TcpClient consente uno scambio di dati in modalità socket, quindi di basso livello, in pratica incapsula una connessione TCP e fornisce un certo numero di proprietà per la gestione del buffering e dei timeout e metodi per la connessione, l’invio e la ricezione in modalità di blocco sincrono. La classe TcpListener fornisce i metodi che attendono e accettano richieste di connessione in ingresso in modalità di blocco sincrono; alla richiesta di connessione di un client è possibile utilizzare un TcpClient per stabilire una connessione con un TcpListener. Un TcpListener si crea con un IPEndPoint che è costituito da un indirizzo IP e da un numero di porta. Per consentire all’oggetto TcpClient di stabilire la connessione e scambiare informazioni, è necessario che un TcpListener, il server, sia in attesa delle richieste di connessione. È possibile connettersi al listener creando un oggetto TcpClient passando al costruttore l’IP del server e il numero di porta su cui è in ascolto. Il metodo Start permette di avviare la fase di attesa per le richieste di connessione in ingresso. Il metodo AcceptTcpClient accetta la connessione dei client, mentre la gestione della comunicazione è fatta da un thread separato. Esempio, inviare e ricevere immagini con un client e un server TCP. TcpClient File MAINWINDOW.XAML.CS Si recupera lo stream di comunicazione di tipo NetworkStream grazie al metodo GetStream, con il quale s’inviano i dati al server. Nell’esempio, si scrivono i byte dell’immagine selezionata. using System.Windows; using System.Windows.Media.Imaging; using System.IO; using Microsoft.Win32; using System.Net.Sockets; TPSIT 5 um 9 di 434 namespace TcpClient { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void btnInvia_Click(object sender, RoutedEventArgs e) { using (Stream fileStream = File.OpenRead(this.txbNome.Tag.ToString())) { System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient(); client.Connect("localhost", 8080); NetworkStream netStream = client.GetStream(); byte[] sendBuffer = new byte[1024]; int bytesRead = 0; do { bytesRead = fileStream.Read(sendBuffer, 0, 1024); if (bytesRead > 0) netStream.Write(sendBuffer, 0, bytesRead); } while (bytesRead > 0); netStream.Close(); } } private void btnSfoglia_Click(object sender, RoutedEventArgs e) { OpenFileDialog fileDialog = new OpenFileDialog { Filter = "immagini (*.jpg)|*.jpg" }; bool? show = fileDialog.ShowDialog(); if (show.HasValue & show.Value) { using (Stream fileStream = fileDialog.OpenFile()) { this.txbNome.Text = fileDialog.SafeFileName; this.txbNome.Tag = fileDialog.FileName; MemoryStream dataStream = new MemoryStream(); byte[] dataByte = new byte[1024]; int i = 0; do { i = fileStream.Read(dataByte, 0, 1024); if (i > 0) dataStream.Write(dataByte, 0, i); } while (i > 0); dataStream.Seek(0L, SeekOrigin.Begin); BitmapImage bmpImage = new BitmapImage(); bmpImage.BeginInit(); bmpImage.StreamSource = dataStream; bmpImage.EndInit(); this.imgImmagine.Source = bmpImage; } } } } } File MAINWINDOW.XAML <Window x:Class="TcpClient.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" TPSIT 5 um 10 di 434 Title="TCPClient" Height="350" Width="425" WindowStartupLocation="CenterScreen"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal" FlowDirection="LeftToRight" HorizontalAlignment="Center"> <TextBlock Height="23" x:Name="txbNome" Width="180" /> <Button Content="Sfoglia" Height="23" x:Name="btnSfoglia" Click="btnSfoglia_Click" Width="55" /> <Button Content="Invia" Height="23" x:Name="btnInvia" Click="btnInvia_Click" Width="55" /> </StackPanel> <Image Grid.Row="1" x:Name="imgImmagine" Stretch="Uniform" /> </Grid> </Window> TcpServer File MAINWINDOW.XAML.CS L’oggetto IPEndPoint specifica gli indirizzi e le porte degli host su cui l’oggetto TcpListener rimane in ascolto in attesa di possibili connessioni, mentre IPAddress.Any apre a tutti i potenziali client. All’interno del while, con il metodo Pending si verifica la presenza di una connessione in ingresso: in caso positivo, si attiva la comunicazione con AcceptTcpClientAsync che restituisce l’oggetto TcpClient, mediante il quale è possibile inviare e ricevere dati. Si procede alla lettura dei dati, salvandoli in un MemoryStream per utilizzarli come sorgente dell’oggetto BitmapImage e di mostrarli a video con un controllo di tipo Image. Per selezionare l’immagine si usa l’oggetto OpenFileDialog. Dopo aver aperto lo stream dell’immagine presente sul file system, si deve inizializzare l’oggetto TcpClient e aprire la connessione all’host locale, sulla porta 8080 con il metodo Connect; proprio per la natura del protocollo, è importante che, al momento della chiamata del metodo Connect, il server sia già attivo e in ascolto. using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; using System.IO; using System.Net.Sockets; using System.Net; using Microsoft.Win32; namespace TcpServer { public partial class MainWindow : Window { TPSIT 5 um 11 di 434 public MainWindow() { InitializeComponent(); this.Loaded += async (o, e) => await ReiceveData(); } private async void btnInvia_Click(object sender, RoutedEventArgs e) { using (Stream fileStream = File.OpenRead(this.txbNome.Tag.ToString())) { var client = new TcpClient(); client.Connect("localhost", 8080); var netStream = client.GetStream(); var sendBuffer = new byte[1024]; int bytesRead = 0; do { bytesRead = await fileStream.ReadAsync(sendBuffer, 0, 1024); if (bytesRead > 0) netStream.Write(sendBuffer, 0, bytesRead); } while (bytesRead > 0); netStream.Close(); } } private void btnSfoglia_Click(object sender, RoutedEventArgs e) { var fileDialog = new OpenFileDialog { Filter = "immagini (*.jpg)|*.jpg" }; bool? show = fileDialog.ShowDialog(); if (show.HasValue & show.Value) { using (fileDialog.OpenFile()) { this.txbNome.Tag = fileDialog.FileName; this.txbNome.Text = fileDialog.SafeFileName; } } } private async Task ReiceveData() { var server = new TcpListener(new IPEndPoint(IPAddress.Any, 8080)); server.Start(); while (true) { var localClient = await server.AcceptTcpClientAsync(); var netStream = localClient.GetStream(); if (netStream.CanRead) { var dataStream = new MemoryStream(); var dataByte = new byte[1024]; int i = 0; do { i = await netStream.ReadAsync(dataByte, 0, 1024); if (i > 0) dataStream.Write(dataByte, 0, i); } while (i > 0); dataStream.Seek(0, SeekOrigin.Begin); var bmpImage = new BitmapImage(); bmpImage.BeginInit(); bmpImage.StreamSource = dataStream; bmpImage.EndInit(); var img = new Image TPSIT 5 um 12 di 434 { Stretch = Stretch.Uniform, Source = bmpImage }; stpPannello.Children.Add(img); } localClient.Close(); netStream.Close(); } } } } File MAINWINDOW.XAML <Window x:Class="TcpServer.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="TCPServer" Height="350" Width="425" WindowStartupLocation="CenterScreen"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal" FlowDirection="LeftToRight" HorizontalAlignment="Center"> <TextBlock Height="23" x:Name="txbNome" Width="180" /> <Button Content="Sfoglia" Height="23" x:Name="btnSfoglia" Click="btnSfoglia_Click" Width="55" /> <Button Content="Invia" Height="23" Click="btnInvia_Click" x:Name="btnInvia" Width="55" /> </StackPanel> <StackPanel Grid.Row="1" Orientation="Vertical" x:Name="stpPannello"></StackPanel> </Grid> </Window> TPSIT 5 um 13 di 434 TCP E WINDOWS 8.X Le classi per comunicare con il protocollo TCP non appartengono al .NET Framework ma sono classi native di Windows 8.X, namespace Windows.Networking.Sockets. Il protocollo TCP è utile per app che richiedono un colloquio bidirezionale ma presenta un problema: la porta 80 è utilizzata per il traffico HTTP, i server socket comunicano usando altre porte che, però, spesso sono bloccate dal firewall e quindi rendono impossibile il collegamento tra client e server. Aprire la connessione con il server La classe StreamSocket permette di gestire il ciclo di vita della comunicazione TCP. Per aprire la connessione si usa il metodo ConnectAsync che accetta in input un oggetto HostName che rappresenta il nome del server verso cui aprire la connessione e una stringa contenente la porta. private StreamSocket _socket = new StreamSocket(); await _socket.ConnectAsync(new HostName("127.0.0.1"), "8888"); Inviare dati al server La classe StreamSocket espone la proprietà OutputStream di tipo IOutputStream che rappresenta lo stream tramite il quale è possibile inviare dati al server. Per inviare dati al server non si deve utilizzare la proprietà OutputStream ma si deve creare un’istanza della classe DataWriter contenuta nel namespace Windows.Storage.Streams alla quale si passa in input lo stream, espone diversi metodi tipizzati: WriteInt32, WriteString e WriteBoolean che scrivono in un buffer di memoria. Terminato di scrivere nel buffer, s’invia il contenuto con il metodo StoreAsync e si scollega il writer dallo stream con il metodo DetachStream. Esempio, creare un messaggio dove i primi 4 byte contengono la lunghezza del messaggio e i successivi contengono il corpo del messaggio. Mantenendo questo formato, il server è in grado d’interpretare il messaggio ricevuto. TPSIT 5 um 14 di 434 async private void SendMessage(StreamSocket socket, string message) { var writer = new DataWriter(socket.OutputStream); var len = writer.MeasureString(message); writer.WriteInt32((int)len); writer.WriteString(message); var ret = await writer.StoreAsync(); writer.DetachStream(); LogMessage(string.Format("Spedito (a {0}) {1}", socket.Information.RemoteHostName.DisplayName, message)); } Ricevere dati dal server La classe StreamSocket espone la proprietà InputStream che rappresenta lo stream sul quale sono ricevuti i dati dal server. Non si devono leggere i dati direttamente dallo stream ma si deve creare un’istanza della classe DataReader alla quale si passa in input lo stream per poi leggere i dati tramite questa classe. Per recuperare i dati dallo stream s’invoca il metodo LoadAsync della classe DataReader che accetta in input il numero di byte da leggere. Una volta recuperati i byte, si possono leggere con i metodi tipizzati che la classe DataReader mette a disposizione: ReadInt32, ReadString e ReadBoolean. Tramite il metodo LoadAsync i primi 4 byte del messaggio che rappresentano la lunghezza, sono recuperati dallo stream e allocati nel buffer del reader. Il metodo ReadInt32 trasforma i 4 byte nella lunghezza del messaggio. Ottenuta la lunghezza, si usa di nuovo il metodo LoadAsync per caricare nel buffer del reader il resto del messaggio e dopo si usa il metodo ReadString per trasformare l’array di byte in una stringa. Il metodo è ricorsivo perché deve rimanere in ascolto di nuovi messaggi dal server. Subito dopo aver stabilito la connessione, s’invoca il metodo WaitForData per fare in modo che il client cominci ad intercettare i messaggi inviati dal server. Esempio, leggere i dati. async private void WaitForData(StreamSocket socket) { var dr = new DataReader(socket.InputStream); var stringHeader = await dr.LoadAsync(4); if (stringHeader == 0) { LogMessage(string.Format("Disconnesso (da {0})", socket.Information.RemoteHostName.DisplayName)); return; } int strLength = dr.ReadInt32(); uint numStrBytes = await dr.LoadAsync((uint)strLength); string msg = dr.ReadString(numStrBytes); LogMessage(string.Format("Ricevuto (da {0}): {1}", socket.Information.RemoteHostName.DisplayName, msg)); WaitForData(socket); } TPSIT 5 um 15 di 434 JAVA Le classi Socket e ServerSocket implementano socket su TCP. 1. Il server aprirà una porta sul proprio indirizzo IP e resterà in ascolto su essa. 2. Il client punterà a quell’indirizzo IP e invierà dei messaggi sulla porta aperta dal server. 3. Il server non farà altro che rimandare il messaggio ricevuto al client, “EchoServer”. 4. Il server chiuderà il socket e quindi la porta, quando riceverà un messaggio vuoto. La classe che Java mette a disposizione per assolvere alle funzioni previste per il server è la classe ServerSocket, infatti, è possibile accettare connessioni dai client attraverso la rete su una ben determinata porta. Quindi quando si vuole realizzare un server si possono seguire i seguenti tre passi. 1. Creare un oggetto di classe ServerSocket impostando al costruttore il numero di porta locale, in altre parole la porta in cui il server rimarrà in ascolto di richieste di connessioni. 2. Invocare il metodo accept della classe ServerSocket, in modo tale da attendere le connessioni da parte dei client. 3. Usare il socket ottenuto ad ogni connessione, per comunicare con il client. package es; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; public class EchoServer implements Runnable { public static final int PORT = 4422; private boolean loop = true; private ServerSocket sSocket = null; private boolean running = false; Si è specificato su un campo costante e pubblico su quale porta il server rimanga in ascolto, in modo tale che i client possano saperlo facilmente. Un altro membro è proprio sSocket che implementa le funzionalità del server. Metodo run. public void run() TPSIT 5 um 16 di 434 In primo luogo è necessario istanziare un ServerSocket sulla porta scelta. Se questa operazione non andasse a buon fine sarebbe inutile proseguire e, infatti, sul catch di IOException, dopo aver stampato l’errore, si esce direttamente dal metodo. { try { sSocket = new ServerSocket(PORT); } catch (IOException e) { e.printStackTrace(); return; } Ci si deve preparare per ricevere un connessione da parte di un client. Socket clientSocket = null; PrintWriter out = null; BufferedReader in = null; running = true; Il metodo accept invocato immediatamente dopo della classe ServerSocket crea un oggetto Socket per ogni connessione. Il server non può accettare una nuova connessione finché non incontra nuovamente il metodo accept che è bloccante, in altre parole il server si blocca finché non c’è una connessione del client. Se mentre sta servendo un client, arriva una seconda richiesta da parte di un altro client, la connessione non è accettata ed è sospesa finché il server non si libera e fa un’altra accept. Il server potrà poi comunicare con il client attraverso lo stream d’input e quello di output grazie al BufferedReader e al PrintWriter. try { while (loop) { clientSocket = sSocket.accept(); out = new PrintWriter(clientSocket.getOutputStream(),true); in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); String line; while ((line = in.readLine()) != null) out.println("Server response: "+ line); } } catch (IOException e) { e.printStackTrace(); } finally { Una volta accettata la connessione, s’incapsula lo stream d’input nel reader e quello di output nel writer. Fatto ciò, si legge quanto il client ha inviato e lo si rispedisce indietro con il suffisso Server response:. Questa parte di codice è racchiusa all’interno di un ciclo while che ha come condizione di terminazione la variabile loop. Infatti, ogni server deve continuare a rimanere in ascolto finché non è “messo giù”. Il metodo accept rimane in attesa finché non riceve un messaggio. Una volta uscito da questo loop è necessario chiudere gli stream e i socket. TPSIT 5 um 17 di 434 if (out != null) out.close(); try { if (in != null) in.close(); if(clientSocket != null) clientSocket.close(); if(sSocket != null) sSocket.close(); } catch (IOException e) { e.printStackTrace(); } running = false; } System.err.println("Server is down"); } Bisogna evitare di chiudere i socket prima che siano chiusi i loro relativi stream. Questa classe deve avere un comportamento da server, il loop fa sì che il server rimanga in attesa della prossima richiesta da parte del client. Come prima caratteristica è necessario che, così come si può avviare il servizio tramite il metodo run, allo stesso modo sia possibile fermarlo, basta settare la variabile loop del ciclo while a false. In realtà non funzionerebbe, per due motivi. 1. La modifica del valore della variabile loop dev’essere effettuata da un thread esterno a quello che ha lanciato run perché quest’ultimo è “bloccato” sul ciclo while. 2. Anche mettendo a false la variabile loop questo non garantirebbe l’uscita dal ciclo, infatti, la riga di codice clientSocket = sSocket.accept(); rimane in attesa della prossima richiesta; risulta quindi necessario, inoltre, inviare anche un messaggio che serva esclusivamente a “sbloccare” il ciclo da quel punto. Si ricorre al metodo stop del server. public void stop() { loop = false; Socket dummySocket = null; PrintWriter writer = null; try { dummySocket = new Socket("127.0.0.1", PORT); writer = new PrintWriter(dummySocket.getOutputStream(),true); writer.println(""); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (writer != null) writer.close(); if (dummySocket != null) try { dummySocket.close(); } catch (IOException e) { e.printStackTrace(); } } TPSIT 5 um 18 di 434 } La prima cosa che si fa è settare la variabile globale loop a false, dopo di che si deve sbloccare il server dal suo ciclo, si deve mandare un messaggio vuoto al server. Naturalmente è scontato che il server cui si deve inviare tale messaggio risieda sullo stesso host, per cui l’indirizzo cui punterà il dummySocket sarà 127.0.0.1. Una volta stabilita la connessione non resterà da fare altro che scrivere sul proprio stream di output in modo tale che “dall’altra parte” il server legga tale messaggio. Vogliamo che il server possa servire un client e contemporaneamente forzare a fare un accept per ascoltare altri client. Un server multithreaded consente di servire più client contemporaneamente, nonché di accettare nuove connessioni, tipicamente è impiegato un thread per client. Per utilizzare il server in un ambiente multithread sarà quindi anche indispensabile sapere quando il server è effettivamente attivo, in pratica quando si è veramente collegato alla porta specificata e si è messo in ascolto. Questo è il significato della variabile running che è impostata a true appena prima dell’ingresso al ciclo while e a false appena usciti da quest’ultimo. Si è implementata alla classe EchoServer l’interfaccia Runnable; infatti sarà possibile lanciare tale classe come thread parallelo. Sapere quando il server è effettivamente attivo è di fondamentale importanza, infatti tutti i client dovranno attendere l’attivazione di quest’ultimo prima d’iniziare qualsiasi comunicazione con esso, a tale scopo è stato implementato il metodo waitForRunnig. public boolean isRunning() { return running; } public void waitForRunnig() { while(!isRunning()) Thread.yield(); } Questo metodo non fa altro che controllare ciclicamente lo stato della variabile running, terminando quando essa assume il valore true. È importante notare che non si tratta di un semplice ciclo while di “attesa” bensì è invocato il metodo statico Thread.yield che dice alla VM (Virtual Machine): “io per adesso posso attendere, esegui pure gli altri thread”. Instaurare la prima comunicazione client/server. public static void main(String[] args) throws IOException { EchoServer serverSocket = new EchoServer(); Thread server = new Thread(serverSocket); server.start(); serverSocket.waitForRunnig(); È necessario istanziare un server e incapsularlo all’interno di un oggetto thread, una volta lanciato quest’ultimo si attende che sia attivo grazie al metodo waitForRunnig. Socket echoSocket = null; PrintWriter out = null; BufferedReader in = null; try { echoSocket = new Socket("127.0.0.1", PORT); out = new PrintWriter(echoSocket.getOutputStream(), true); in = new BufferedReader(new TPSIT 5 um 19 di 434 InputStreamReader(echoSocket.getInputStream())); } catch (UnknownHostException e) { System.err.println("Don’t know about host: taranis."); System.exit(1); Una volta che il server risulta attivo si può collegare il socket client e comunicare attraverso i suoi stream. BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in)); String userInput; while ((userInput = stdIn.readLine()) != null) { if(userInput.equals("")) { serverSocket.stop(); break; } out.println(userInput); System.out.println(in.readLine()); } out.close(); in.close(); stdIn.close(); echoSocket.close(); } In particolare l’applicazione userà lo standard input per comunicare con il server, per esempio digitare Ciao e il server risponde. Inoltre, si è stabilito che un semplice INVIO senza alcun messaggio fermi il server. TPSIT 5 um 20 di 434 UDP INTRODUZIONE È un protocollo utilizzato soprattutto per lo scambio di messaggi. Non si occupa del controllo dei dati in transito perché sono inviati sotto forma di datagrammi che il ricevente può o non ricevere. È impiegato nelle comunicazioni, dove ha più importanza la velocità di trasmissione rispetto alla qualità come, per esempio, nelle trasmissioni broadcast (in pratica contemporaneamente su più nodi della rete) dove è trascurabile perdere alcuni byte, nel complesso della comunicazione. L’UDP a differenza del TCP non richiede una connessione esplicita tra i PC, in altre parole è di tipo connectionless. La mancanza di una connessione ne fa un protocollo poco affidabile ma veloce e semplice da utilizzare. Con il protocollo UDP i PC possono avere un comportamento equivalente, possono trasmettere e ricevere allo stesso modo, non è definita la figura del client e del server, per questo le applicazioni che utilizzano l’UDP si possono considerare di tipo P2P (Peer To Peer). La classe UdpClient consente uno scambio di dati in modalità socket, quindi di basso livello, con il protocollo UDP, senza stato e senza la necessità di attendere la connessione con un host specifico. Esempio, inviare e ricevere messaggi con un client e un server UDP. UdpClient File MAINWINDOW.XAML.CS Per iniziare la connessione, si utilizza il metodo Connect e si specifica come parametri sia l’host cui si desidera inviare i messaggi sia la porta di comunicazione che si utilizzerà, nell’esempio s’inviano i messaggi sullo stesso PC, "localhost", attraverso la porta 8080. TPSIT 5 um 21 di 434 Questo valore dev’essere superiore a 1024 per non entrare in conflitto con le Well Known Port Number e la porta non dev’essere utilizzata da altre applicazioni. L’effettivo invio dei messaggi avviene con il metodo SendAsync cui si passa l’array di byte corrispondente al messaggio che si vuole trasmettere, oltre alla lunghezza dell’array stesso. using System.Text; using System.Windows; using System.Net.Sockets; namespace Client { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void btnInvia_Click(object sender, RoutedEventArgs e) { var client = new UdpClient(); client.Connect("localhost", 8080); byte[] sendByte = Encoding.ASCII.GetBytes(txtInvia.Text); client.SendAsync(sendByte, sendByte.Length); } } } File MAINWINDOW.XAML L’interfaccia è una semplice finestra con una casella di testo in cui è possibile immettere il messaggio e un pulsante. <Window x:Class="Client.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Client" Height="80" Width="300"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal"> <TextBox Height="23" x:Name="txtInvia" Width="180" /> <Button Content="Invia" Height="23" x:Name="btnInvia" Width="55" Click="btnInvia_Click" /> </StackPanel> </Grid> </Window> UdpServer File MAINWINDOW.XAML.CS TPSIT 5 um 22 di 434 Per ricevere i messaggi si utilizza il metodo Receive, non prima di essersi messi in ascolto su una combinazione specifica d’indirizzo/porta. Il protocollo UDP non prevede che sia stabilita una connessione, quindi la ricezione è del tutto svincolata dall’invio e necessita, pertanto che sia eseguita in continuo. Nell’esempio, grazie al metodo ReceiveAsync, il ciclo while può continuare senza che l’interfaccia rimanga bloccata, a ricevere tutti i messaggi provenienti da tutti gli host in comunicazione sulla porta 8080. Infine, bisogna convertire in stringa i byte ricevuti e visualizzarli nella casella di testo. using System; using System.Text; using System.Windows; using System.Net.Sockets; namespace Server { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); ReiceveData(); } private void btnInvia_Click(object sender, RoutedEventArgs e) { var client = new UdpClient(); client.Connect("localhost", 8080); byte[] sendByte = Encoding.ASCII.GetBytes(this.txtInvia.Text); client.SendAsync(sendByte, sendByte.Length); } private async void ReiceveData() { var server = new UdpClient(8080); while (true) { var result = await server.ReceiveAsync(); var reiceveByte = result.Buffer; var reiceveString = Encoding.ASCII.GetString(reiceveByte); txtRicevi.Text += String.Format(" {0}", reiceveString); } } } } File MAINWINDOW.XAML <Window x:Class="Server.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Server" Height="350" Width="425" WindowStartupLocation="CenterScreen"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal"> <TextBox Height="23" x:Name="txtInvia" Width="180" /> <Button Content="Invia" Height="23" TPSIT 5 um 23 di 434 x:Name="btnInvia" Width="55" Click="btnInvia_Click" /> </StackPanel> <TextBox Margin="0,30,0,0" Grid.Row="1" HorizontalAlignment="Stretch" x:Name="txtRicevi" VerticalAlignment="Stretch" /> </Grid> </Window> JAVA La classe DatagramSocket implementa socket su UDP. Fornisce solo la possibilità d’inviare e ricevere un pacchetto: ritrasmissione e ordinamento sono a carico del programmatore. La classe MulticastSocket implementa socket multicast su UDP, estende DatagramSocket con la possibilità d’inviare lo stesso pacchetto ad un gruppo di riceventi anziché ad uno solo. TPSIT 5 um 24 di 434 HTTP INTRODUZIONE Per spedire o scaricare dati sulla rete, il client apre un collegamento TCP con un server. Il TCP collega gli indirizzi IP e le porte alle due estremità e si occupa di spedire i dati senza errori tra i due. Poiché le informazioni non devono soltanto essere trasportate ma anche comprese, sopra il livello del TCP vi è un protocollo applicativo attraverso il quale comunicano il client e il server: nel caso dei siti web si tratta dell’HTTP. Anche se il TCP, perché livello di trasporto, offre una spedizione di dati bidirezionale, l’HTTP non usa questa possibilità. L’HTTP funziona secondo un criterio semplice: il client chiede, il server risponde. La comunicazione è realizzata con sequenze richiesta/risposta, tipico delle architetture client/server, per esempio il client è il browser che invia un messaggio di “richiesta” al server web che restituisce i dati con un messaggio di “risposta”. A differenza degli altri protocolli dello stesso livello lavora in modalità disconnessa: questo vuol dire che non s’instaura un canale di comunicazione permanente tra client e server ma, a ogni richiesta, è aperto e poi richiuso il canale di comunicazione. In pratica, il protocollo HTTP è stateless, senza stato: ogni scambio richiesta/risposta è indipendente dagli altri e a livello di protocollo non esiste uno stato condiviso. Questo ha spinto i programmatori a trovare soluzioni alternative, tramite i linguaggi di programmazione, per la gestione dello stato. La stato dev’essere mantenuto e gestito a livello applicativo in tre modi. 1. URL (Uniform Resource Locator) rewriting passare le informazioni di stato sull’URL nella parte di query. 2. ViewState utilizzare dei campi nascosti per passare i dati con un POST. 3. Cookie: sono informazioni scambiate tra client e server a ogni richiesta/risposta, sono memorizzati sul client come file, quindi mantengono uno stato che rimane tra una richiesta e l’altra. Una comunicazione HTTP è composta da un messaggio di richiesta che viaggia dal client verso il server e da un messaggio di risposta che viaggia dal server verso il client. Il client richiede una risorsa al server e il server risponde o con la risorsa o con un messaggio di errore. Il protocollo HTTP permette di scambiare messaggi ma ha le limitazioni seguenti. È sempre il client che deve attivare la comunicazione. TPSIT 5 um 25 di 434 Il server non può inviare in autonomia messaggi al client. Ogni messaggio è composto da due parti. 1. Le intestazioni HTTP: contengono una serie di coppie chiave/valore che rappresentano i metadati, in pratica specificano il formato della richiesta e della risposta, per esempio le lingue supportate dal client e la lunghezza del messaggio. 2. Il corpo: è la parte del messaggio che contiene i dati. Con la creazione dei servizi REST (REpresentational State Transfer) sono diventate molto importanti le intestazioni HTTP che specificano il formato della richiesta e della risposta. È possibile monitorare le transazioni HTTP con uno strumento tracer HTTP. HTTP 1.1 Consente una richiesta soltanto per ciascun collegamento TCP, di conseguenza tutti gli elementi di una pagina web sono inviati singolarmente in sequenza. I browser per evitare questo, generano fino a 6 collegamenti TCP paralleli. Ciò non è efficiente perché un server può gestire ogni collegamento aggiuntivo con un intervallo di 500 ms soltanto e a ogni collegamento invia inutilmente un nuovo header HTTP. Inoltre, HTTP consente soltanto al client d’inviare richieste. HTTP 2 (originariamente HTTP 2.0) Google SPDY: consente l’invio in parallelo dei pacchetti HTTP, comprime i dati e li cifra SSL (Secure Sockets Layer), questa tecnologia è nata come miglioramento di HTTP 1.1 e poi divenuta la base su cui l’organizzazione ha implementato HTTP 2. TPSIT 5 um 26 di 434 Microsoft Speed+Mobility: usata per le app e i dispositivi mobili; i WebSocket generano un continuo collegamento bidirezionale tra client e server. HTTP 2 è stato sviluppato dal Working Group Hypertext Transfer Protocol dell’Internet Engineering Task Force, è un protocollo binario invece che testuale, supporta l’invio di dati in multiplexing e usa la compressione dell’header per ridurre lo spreco di banda secondo la specifica HPACK. Permette ai server di “spingere” proattivamente le proprie risposte alle richieste dei client. Tiene un canale aperto tra il client e il server. Richiesta HTTP Il formato è il seguente. <request-line> <headers> <blank line> [<request-body>] La prima riga indica il tipo di richiesta, la risorsa cui accedere e la versione di HTTP in uso. La sezione header indica informazioni che possono essere utili al server. Poi c’è una riga vuota, alla quale possono eventualmente seguire altri dati, corpo. L’ultima riga, il body è il corpo del messaggio. Richiesta GET GET / HTTP/1.1 Host: www.miosito.com Accept-Language: en-us User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6) Host: localhost:8080 Connection: Keep-Alive La prima riga specifica la risorsa richiesta, in questo caso si vuole accedere, GET, alla risorsa identificata dall’URL. HTTP ha diversi metodi che specificano quali azioni si possono eseguire su una risorsa. GET (formato stringa): richiede una risorsa al server; le chiamate effettuate dal browser sulla barra degli indirizzi sono di tipo GET. POST (formato binario): invia dati al server specificando che servono per aggiornare dati già esistenti sul server; quando si completa un form e si fa clic sul pulsante INVIO il browser invia una richiesta HTTP che inizia con POST; nel body della richiesta sono elencati i parametri. HEAD: come GET ma il server restituisce solo l’intestazione della risposta e non il file. PUT: invia dati al server specificando che servono per aggiornare una risorsa. DELETE: invia dati al server specificando che servono per cancellare dati già esistenti sul server; non è possibile usare il browser per inviare un comando DELETE in HTTP, anche se si può fare con JavaScript, creando un link con questo codice. TRACE. OPTIONS. La (/)indica che è richiesta la radice, root, del dominio. HTTP/1.1 indica la versione di protocollo HTTP utilizzata per formulare la richiesta. Host: indica il nome del dominio al quale la richiesta è inviata. Accept-Language: indica quali sono i linguaggi accettati dal client. Accept-text/html: indica quali sono i formati accettati dal client. User-Agent: comunica al server web il tipo di browser che ha fatto la richiesta. TPSIT 5 um 27 di 434 Connection: Keep-Alive: serve per le operazioni del browser. Il client può passare una serie di parametri, inserendoli in una richiesta HTTP. L’invio di parametri per una richiesta GET richiede che le informazioni extra siano aggiunte in coda all’URL dopo il (?), il formato è del tipo seguente. URL ? name1=value1&name2=value2&..&nameN=valueN Quest’informazione, chiamata querystring, è duplicata nella riga di richiesta della richiesta HTTP come segue. GET /books/?name=Professional%20Program HTTP/1.1 …. Il testo Professional Program dev’essere codificato, sostituendo lo spazio con %20, in modo da inviarlo all’URL come parametro: questa è chiamata codifica URL. Richiesta POST Fornisce al server informazioni aggiuntive nel corpo della richiesta. POST / HTTP/1.1 Host: www.miosito.com User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6) Content-Type: application/x-www-form-urlencoded Content-Length: 40 Connection: Keep-Alive name=Professional%20Program&publisher=Wiley La prima riga specifica la risorsa richiesta, in questo caso si vuole accedere, POST, alla risorsa identificata dall’URL. HTTP ha diversi metodi che specificano quali azioni si possono eseguire su una risorsa. POST (formato binario): invia dati al server specificando che servono per aggiornare dati già esistenti sul server; quando si completa un form e si fa clic sul pulsante INVIO il browser invia una richiesta HTTP che inizia con POST; nel body della richiesta sono elencati i parametri. Gli header Host e User-Agent sono sempre presenti. Content-Type mostra come è codificato il corpo della richiesta. Content-Length indica la lunghezza in byte del corpo della richiesta. Dopo l’header Connection e la riga vuota c’è il corpo della richiesta. I metodi GET e HEAD sono sicuri perché sono utilizzati solo per il recupero d’informazioni e quindi non cambiano lo stato del server. I metodi POST, PUT e DELETE non sono sicuri perché modificano lo stato del server. I metodi PUT, GET, HEAD e DELETE sono idempotenti perché la stessa richiesta produrrà sempre gli stessi risultati. Il metodo POST non è né sicuro né idempotente perché la stessa richiesta ripetuta più volte può produrre risultati differenti. Risposta HTTP Il formato è il seguente. <status-line> TPSIT 5 um 28 di 434 <headers> <blank line> [<response-body>] La prima riga contiene informazioni di stato, sono indicazioni sulla risorsa richiesta tramite un codice di stato. La sezione header indica informazioni che possono essere utili al server. Poi c’è una riga vuota, alla quale possono eventualmente seguire altri dati, corpo. L’ultima riga, il body è il contenuto della risposta. Risposta HTTP HTTP/1.1 200 OK Date: Server: Connection: Close Content-Length: 777 Content-Type: text/plain This is the content of file.txt. HTTP/1.1: indica la versione del protocollo usata dal server web per formattare la risposta. 200: è il codice di stato. 1xx: richiesta ricevuta, processata e completata correttamente. 2xx: la richiesta è stata recepita correttamente. 3xx: il browser deve effettuare una redirezione verso un altro indirizzo. 304 (NOT MODIFIED): la risorsa non è stata modificata dall’ultima richiesta, questo è utilizzato più di frequente per i meccanismi di cache dei browser. 4xx: errore del client; la richiesta non è formulata correttamente. 401 (UNAUTHORIZED): il client non è autorizzato ad accedere alla risorsa, in questi casi il browser richiederà un nome utente e una password per effettuare il login al server. 403 (FORBIDDEN): il client non ha ottenuto autorizzazione, questo accade solitamente se fallisce il login mediante nome utente e password dopo un codice 401. 404 (NOT FOUND): la risorsa non esiste all’indirizzo fornito. 5xx: errore del server; il server non è riuscito a rispondere ad una richiesta valida. OK: è la “reason” dello stato. Date: data e ora in cui la risposta è stata inviata. Server: nome del server che ha inviato la risposta. Content-Length: lunghezza del corpo della risposta in byte. Content-Type: indica il tipo MIME (Multipurpose Internet Mail Extension) dei dati restituiti nel body che è composto da due parti, sono questi i dati che il browser mostra all’utente. Non vi è indicazione del tipo di richiesta che ha provocato tale risposta ma questo non ha conseguenza alcuna per il server. È compito del client sapere quali tipi di dati debbano essere rinviati per ogni tipo di richiesta e decidere come utilizzarli. 1. Tipo: text. 2. Sottotipo: plain. text/plain = testo ASCII (American Standard Code for Information Interchange). text/html = documento HTML. text/xml = documento XML. image/gif = immagine in formato GIF (Graphics Interchange Format). TPSIT 5 um 29 di 434 L’elenco dei tipi MIME nel PC si trova nel registro di sistema alla chiave seguente. HKEY_CLASSES_ROOT\MIME\DATABASE\CONTENT TYPE. I programmatori hanno previsto dei metodi per la comunicazione live tramite HTTP. Il metodo più semplice è il polling, nel quale, a intervalli stabiliti, uno script nel browser chiede informazioni al server riguardo nuovi eventi. A ogni richiesta il polling crea una nuova connessione che il server termina dopo la risposta, anche se non deve riferire alcuna modifica. Tutto questo richiede molto tempo, aumenta il traffico a causa dell’overhead e l’utilizzo della rete a causa della frequente creazione di nuovi collegamenti TCP. Il long polling mantiene il collegamento fino al momento in cui il server deve annunciare gli eventi, questo accorcia la latenza tra evento e risposta ma provoca comunque traffico e utilizzo della rete. Il metodo migliore è lo streaming HTTP: il collegamento rimane aperto per lungo tempo, mentre il server può inviare i dati in sequenze irregolari. Lo svantaggio risiede nella dispendiosa implementazione dell’oggetto JavaScript XMLHttpRequest che non è lo stesso in ogni browser. Inoltre, per la comunicazione in tempo reale c’è sempre la necessità di due collegamenti HTTP. Esempio, selezionare un’immagine, visualizzarne l’anteprima e inviarla al server web. Con le classi astratte WebRequest e WebResponse, si entra nel modello richiesta/risposta tipica dei protocolli TCP, una particolarità di queste due classi è che non sono istanziate direttamente ma utilizzano un design pattern creazionale chiamato Factory. Per esempio la classe HttpWebRequest, per istanziare un oggetto di questo tipo, si deve passare attraverso la sua classe “Creator”, ovvero la WebRequest, la quale, tramite il metodo Create, può restituire un oggetto di tipo HttpWebRequest. Grazie alla classe HttpWebRequest si ha la possibilità d’interagire con le risorse esposte da un server web con il protocollo HTTP. Questi oggetti dispongono di molte proprietà. GetResponse Permette di recuperare il messaggio di risposta. TPSIT 5 um 30 di 434 ContentType Permette di conoscere le informazioni relative al tipo. Last Modified Data dell’ultima modifica, da usare insieme a ISFromCache per stabilire la validità della risorsa stessa. StatusCode Permette di conoscere il codice di stato HttpStatusCode. TPSIT 5 um 31 di 434 Headers Permette di controllare tutte le header del messaggio di risposta, collezione di tipo WebHeaderCollection. Cookies Permette di accedere alla collezione di oggetti Cookie relativi alla risposta, è possibile anche la gestione in scrittura con la proprietà CookieContainer dell’oggetto HttpWebRequest. GetResponseStream Permette di recuperare lo stream di risposta e di elaborarlo in funzione del tipo di risposta. GetRequestStreamAsync Permette d’inviare dati aprendo uno stream. È importante conoscere il modello esecutivo delle estensioni del server web relativamente al modo in cui queste gestiscono le richieste web. Nel caso dei CGI (Common Gateway Interface), ad ogni Request corrisponde un processo nuovo che termina con l’invio della Response. Nel caso di plugin, ogni Request è soddisfatta da un thread di un unico processo che termina anch’esso con la Response. File MAINWINDOW.XAML.CS Il metodo Create della classe WebRequest crea l’oggetto HttpWebRequest con l’URI (Uniform Resource Identifier) della pagina ASP.NET (Active Server Page), si apre, quindi, la richiesta dello stream in modo asincrono, con GetRequestStreamAsync. In quest’ultimo, si scrivono i byte del file selezionato e si trasmettono con il metodo GetResponseAsync che resta in ascolto del messaggio di risposta, sempre in modo asincrono, infine, si visualizza a video il risultato trasmesso dal server web. using System.Windows; using System.Windows.Media.Imaging; using System.IO; using Microsoft.Win32; using System.Net; using System; namespace HTTP { public partial class MainWindow : Window { Uri uploadUri; Stream fileStream; public delegate void ManageDataDelegate(string result); public MainWindow() { InitializeComponent(); } private void btnSfoglia_Click(object sender, RoutedEventArgs e) { OpenFileDialog fileDialog = new OpenFileDialog { Filter = "immagini (*.jpg)|*.jpg" }; bool? show = fileDialog.ShowDialog(); if (show.HasValue & show.Value) { using (Stream fileStream = fileDialog.OpenFile()) { this.txbNome.Text = fileDialog.SafeFileName; this.txbNome.Tag = fileDialog.FileName; MemoryStream dataStream = new MemoryStream(); byte[] dataByte = new byte[1024]; TPSIT 5 um 32 di 434 int i = 0; do { i = fileStream.Read(dataByte, 0, 1024); if (i > 0) dataStream.Write(dataByte, 0, i); } while (i > 0); dataStream.Seek(0, SeekOrigin.Begin); BitmapImage bmpImage = new BitmapImage(); bmpImage.BeginInit(); bmpImage.StreamSource = dataStream; bmpImage.EndInit(); this.imgImmagine.Source = bmpImage; this.uploadUri = new Uri(string.Format("http://localhost:1503/Upload.aspx?path={0}", fileDialog.SafeFileName)); } } } private async void btnInvia_Click(object sender, RoutedEventArgs e) { fileStream = File.OpenRead(txbNome.Tag.ToString()); var request = WebRequest.Create(uploadUri) as HttpWebRequest; request.Method = "POST"; var postStream = await request.GetRequestStreamAsync(); var sendBuffer = new byte[1024]; int bytesRead = 0; do { bytesRead = await this.fileStream.ReadAsync(sendBuffer, 0, 1024); if (bytesRead > 0) postStream.Write(sendBuffer, 0, bytesRead); } while (bytesRead > 0); var response = await request.GetResponseAsync(); var streamResponse = response.GetResponseStream(); var streamRead = new StreamReader(streamResponse); var responseString = streamRead.ReadToEnd(); txbNome.Text = responseString; streamResponse.Close(); streamRead.Close(); response.Close(); fileStream.Close(); } } } File MAINWINDOW.XAML <Window x:Class="HTTP.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="HTTP" Height="350" Width="425" WindowStartupLocation="CenterScreen"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> TPSIT 5 um 33 di 434 <StackPanel Orientation="Horizontal" FlowDirection="LeftToRight" HorizontalAlignment="Center"> <TextBlock Height="23" x:Name="txbNome" Width="180" /> <Button Content="Sfoglia" Height="23" x:Name="btnSfoglia" Click="btnSfoglia_Click" Width="55" /> <Button Content="Invia" Height="23" x:Name="btnInvia" Click="btnInvia_Click" Width="55" /> </StackPanel> <Image Grid.Row="1" x:Name="imgImmagine" Stretch="Uniform" /> </Grid> </Window> TPSIT 5 um 34 di 434 Progettare un downloader di file che prende una lista d’indirizzi web e, tramite richieste HTTP, ottiene in risposta degli stream che utilizza per copiare i file in una cartella locale. Uno stream è un flusso di dati in I/O come una sequenza di byte, la classe Stream del .NET Framework è una classe astratta alla base di tutti i tipi di stream. Questi possono essere ad esempio FileStream per la lettura e scrittura di file o NetworkStream per l’invio e la ricezione di dati mediante socket. Schema di funzionamento. File HTTPDOWLOADMANAGER.CS Contiene i metodi per salvare lo stream su file, il metodo statico, Shared, Download della classe HttpDownloadManager come prima cosa istanzia un oggetto di tipo HttpRequestManager che invia la richiesta HTTP e riceve una risposta. Una volta ricevuta la risposta, occorre effettuare il download del file, a tale scopo, si utilizza un FileStream Destination. È, quindi, letto lo Stream ottenuto tramite il metodo GetResponseStream della classe HttpWebResponse e salvato sul file di destinazione creato precedentemente. L’applicazione non fa altro che leggere una lista d’indirizzi HTTP ed utilizzare il metodo Download per ciascuno di essi. File HTTPREQUESTMANAGER.CS Contiene i metodi per effettuare le richieste HTTP al server e ricevere uno stream in risposta, un costruttore per generare la richiesta e i metodi per leggere lo stream di risposta. Il costruttore della classe invia una richiesta HTTP tramite il metodo Create della classe WebRequest che restituisce un oggetto “castabile” in HttpWebRequest. TPSIT 5 um 35 di 434 Una volta creata la richiesta, è utilizzato il metodo GetResponse della classe HttpWebRequest per ottenere un oggetto che è “castabile” in HttpWebResponse. Progettare un server web capace di gestire richieste a pagine HTML statiche, senza supporto per lo scripting. File SERVERSETTINGS.SETTINGS.CS namespace HTTPServer { /* la classe consente la gestione di eventi specifici sulla classe delle impostazioni: * l'evento SettingChanging è generato prima della modifica di un valore dell'impostazione; * l'evento PropertyChanged è generato dopo la modifica di un valore dell'impostazione; * l'evento SettingsLoaded è generato dopo il caricamento dei valori dell'impostazione; * l'evento SettingsSaving è generato prima del salvataggio dei valori dell'impostazione */ sealed partial class ServerSettings { File SERVERSETTINGS.SETTINGS Il file di configurazione contiene le impostazioni del server. DefaultFiles: è la lista dei possibili file di default. default.htm default.html Directories: le coppie cartella virtuale/cartella fisica separate da uno spazio. / C:\www/ /mysite/ C:\www\mysite\ MimeTypes: i tipi MIME conosciuti, coppie estensione /tipo MIME. TPSIT 5 um 36 di 434 .html text/html .htm text/html .bmp image/bmp .gif image/gif .jpg image/jpg Un tipo MIME identifica il tipo di dato che è inviato attraverso i protocolli HTTP o SMTP. È uno standard per il formato dei documenti scambiati su Internet. All’inizio era utilizzato per la posta elettronica e si limitava a messaggi in formato ASCII. Quando due applicazioni si scambiano informazioni attraverso la rete, il mittente deve specificare che tipo di messaggio sta inviando secondo lo standard MIME, così facendo, l’applicazione che lo riceve, sa come trattarlo e può decodificarlo in maniera opportuna. File HTTPCONFIGREADER.CS Contiene la classe per la lettura del file di configurazione, usa tre metodi statici che restituiscono il file di default, i MIME configurati e la cartella. Tutte e tre ciclano gli elementi della proprietà desiderata e trovano eventuali corrispondenze. Il metodo ReadConfiguredMimes esegue un ciclo sulla Collection _MIMETYPES e controlla se tra i tipi MIME supportati dal server c’è quello richiesto. File HTTPSERVERMANAGER.CS Contiene la classe che rappresenta il kernel del server web. Quando è creata una nuova istanza della classe, sono valorizzate le proprietà _IP di tipo IPAddress e _Port di tipo int. La classe IPAddress rappresenta un indirizzo IP che identifica univocamente un dispositivo all’interno di una rete TCP/IP. La ListBox Messages è utilizzata per visualizzare i messaggi del server. L’avvio del server avviene tramite il metodo ServerStart. È istanziato un oggetto di tipo TcpListener sulla porta specificata e quindi un thread che fa partire il metodo Listen. Come prima cosa, è creato un socket per consentire il flusso dei dati, tramite il metodo AcceptSocket dell’oggetto _Listener. Socket Sock = _Listener.AcceptSocket(); A questo punto, quando avviene una connessione, il server segue il flusso seguente. Riceve la richiesta: ReceiveRequest. Processa la richiesta: ParseRequest. Legge la cartella: GetDirectory. TPSIT 5 um 37 di 434 Legge il nome del file: GetFile. Legge il tipo MIME: HTTPConfigReader.Read ConfiguredMimes. Se tutto va a buon fine, invia il file al browser: SendFileToBrowser. Il metodo ReceiveRequest riceve in ingresso il socket e restituisce in output una stringa contenente la richiesta per la lettura della quale è utilizzato il metodo Receive della classe Socket. Il metodo SendFileToBrowser legge il file dal percorso fisico tramite un BinaryReader e lo invia al browser, per farlo, utilizza due metodi. 1. SendHeader che invia l’header HTTP. 2. SendToBrowser che invia il contenuto al browser. Il metodo SendToBrowser riceve in ingresso un array di byte che invierà tramite il metodo Send della classe Socket. File MAINFORM.CS Contiene le funzioni di avvio/interruzione del servizio e una ListBox per i messaggi. TPSIT 5 um 38 di 434 http E WINDOWS 8.X Il .NET Framework usa la classe HttpClient che supporta il paradigma async/await. La classe HttpClient contiene un metodo per ognuno dei metodi elencati e funzionano tutti allo stesso modo: accettano in input un URL e un oggetto HttpContent che rappresenta i dati da inviare al server e restituiscono un oggetto HttpResponseMessage contenente la risposta del server. Get*Async: sfrutta il metodo GET. PostAsync: sfrutta il metodo POST. PutAsync: sfrutta il metodo PUT. DeleteAsync: sfrutta il metodo DELETE. I browser supportano nativamente solo chiamate GET (usate solo per recuperare una risorsa) e POST (usate per inviare dati al server che decide l’operazione da effettuare in base ai dati inviati e non in base al metodo). PUT e DELETE non sono supportati dai browser. Recuperare dati dal server Si deve istanziare la classe HttpClient e dopo usare il metodo GetStringAsync per recuperare la stringa dal server remoto, il formato della stringa è trasparente al metodo, sarà compito dell’app interpretare la stringa per processarne il contenuto. Per recuperare altri tipi di dati come immagini, video e file si usano i metodi GetByteArrayAsync quando i file sono di piccole dimensioni e GetStreamAsync per scaricare grandi quantità di dati. È possibile usare il metodo GetAsync per cancellare una richiesta ancora in corso e di stabilire il momento in cui una richiesta è considerata conclusa, al ricevimento delle intestazioni HTTP o al ricevimento del messaggio. // recupera l’HTML di una pagina web da un server remoto tramite HTTP private async void GetHTMLAsync_Click(object sender, RoutedEventArgs e) { try { HttpClient client = new HttpClient(); string body = await client.GetStringAsync("http://127.0.0.1"); GetHTMLAsyncResult.Text = (body); } catch (HttpRequestException ex) { GetHTMLAsyncResult.Text = ex.Message; } } // scarica un’immagine private async void GetHTMLImageAsync_Click(object sender, RoutedEventArgs e) { try { HttpClient client = new HttpClient(); var imgAsByteArray=await client.GetByteArrayAsync("http://127.0.0.1/esempio.png"); } catch (HttpRequestException ex) { GetHTMLAsyncResult.Text = ex.Message; } } Inviare dati al server Affinché sia in grado di processarli e salvarli sul proprio DB (DataBase). Il secondo parametro del metodo PostAsync accetta in input un oggetto di tipo StringContent che è una classe che eredita da HttpContent. StringContent permette d’inviare stringhe al server ma ci sono altre classi che ereditano da HttpContent e che permettono d’inviare dati in altri formati. Per esempio, c’è la classe FormUrlEncodedContent che permette d’inviare coppie chiave TPSIT 5 um 39 di 434 valore al server, come fa il browser quando s’invia un form. La classe ByteArrayContent che permette d’inviare i dati come array di byte. La classe MultipartContent che serializza i dati nel formato multipart. Risposta del server: il metodo PostAsync restituisce un oggetto HttpResponseMessage che non contiene solo i dati ma anche le intestazioni HTTP e il codice dello stato della risposta. La classe HttpResponseMessage espone dei metodi per recuperare i dati restituiti dal server, per esempio il metodo ReadAsStringAsync, ReadAsStreamAsync e ReadAsByteArrayAsync. // inviare una stringa al server e recuperare la risposta sempre come stringa private async void Post(object sender, RoutedEventArgs e) { try { var content = new StringContent("Ciao"); HttpClient client = new HttpClient(); var response = await client.PostAsync("http://127.0.0.1:1892/home/post", content); var textResponse = await response.Content.ReadAsStringAsync(); StringBuilder sb = new StringBuilder(); foreach (var item in response.Headers) sb.AppendLine(item.Key + ": " + item.Value.First()); PostResult.Text = textResponse; } catch (HttpRequestException ex) { PostResult.Text = ex.Message; } } Manipolare le intestazioni HTTP Con MVC (Model View Controller) WebAPI, il client può inviare al server l’intestazione HTTP Accept impostando come valore application/xml per specificare che intende ricevere i dati in formato XML. Il metodo Add della proprietà DefaultRequestHeaders permette di aggiungere un’intestazione HTTP da inviare al server. La proprietà Headers, dell’oggetto contenente la risposta del server, recupera tutte le intestazioni contenute nella risposta del server. // aggiungere l’intestazione HTTP e leggere le intestazioni della risposta private async void Post(object sender, RoutedEventArgs e) { try { var content = new StringContent("Ciao"); HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Add("Accept", "application/xml"); var response = await client.PostAsync("http://127.0.0.1:1892/home/post", content); var textResponse = await response.Content.ReadAsStringAsync(); StringBuilder sb = new StringBuilder(); foreach (var item in response.Headers) sb.AppendLine(item.Key + ": " + item.Value.First()); PostResult.Text = textResponse; } catch (HttpRequestException ex) { PostResult.Text = ex.Message; } } WebAPI TPSIT 5 um 40 di 434 Permettono di accedere ad un determinato WS (Web Service). Progettare un’Applicazione universale per l’upload e il download di file. Usare in modo asincrono le richieste GET e POST. TPSIT 5 um 41 di 434 TPSIT 5 um 42 di 434 JAVA Esempio, leggere un URL da linea di comando, se non è valido o non ci sono parametri aprire per default l’URL http://www.miosito.it/index.asp, prelevare la pagina letta e salvarla su disco. package web; import java.io.BufferedReader; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; public class Web { public static void main(String[] args) { String un; try {un=args[0];} catch (ArrayIndexOutOfBoundsException e) { un="http://www.miosito.it/index.asp"; System.out.println("Nessun URL definito, prendo "+un); } System.out.println("URL:"+un); URL url; boolean pippo=false; try { url= new URL(un); } catch (MalformedURLException e) { System.out.println("URL errato! Uso: http://www.miosito.it/index.asp "); url = null; pippo=true; } if (pippo) try { url = new URL ("http://www.miosito.it/index.asp"); } catch (MalformedURLException e){}; BufferedReader stream; try { stream = new BufferedReader (new InputStreamReader (url.openStream())); } catch (IOException e) { System.out.println("Errore di apertura del file!"); stream=null; System.exit(0); } File out=new File(".\\"+url.getFile()); FileWriter Output; TPSIT 5 um 43 di 434 try { Output=new FileWriter(out); } catch (IOException e) { Output=null; } String l; try { while ((l=stream.readLine())!=null) Output.write(l); Output.flush(); Output.close(); } catch (IOException e) { System.out.println("Errore di lettura!"); } } } Esempio, visualizzare il codice HTML di una pagina di un server web. File BROWSER.JAVA package web; import java.io.*; import java.net.URL; public class Browser { public Browser (URL url) { System.out.println ("Inserisci URL: " +url); try { BufferedReader inStream = new BufferedReader (new InputStreamReader(url.openStream())); String line; while ((line = inStream.readLine()) != null) System.out.println (line); inStream.close(); } catch (IOException ex) { System.out.println ("Non esiste URL!"); } } } File WEB.JAVA TPSIT 5 um 44 di 434 package web; import java.net.URL; import java.net.MalformedURLException; public class Web { public static void main(String[] args) { try { URL url = new URL ("http://localhost/fauser/"); Browser app = new Browser(url); } catch (MalformedURLException ex ) { System.out.println ("Errore URL!"); } } } TPSIT 5 um 45 di 434 WEBSOCKET INTRODUZIONE Il protocollo WebSocket, offre il meglio dei protocolli HTTP e TCP. Permette di avere un canale bidirezionale tra client e server (vantaggio del protocollo TCP) sfruttando la porta 80 (vantaggio del protocollo HTTP): si riducono i tempi di latenza e di utilizzo della rete. Possibilità d’inviare dati sia come stream di byte il cui formato è deciso dal programmatore (tipico del protocollo TCP) sia come messaggi dal formato standard (tipico del protocollo HTTP). Frame WebSocket Il frame dati è semplice: contiene informazioni sullo stato di un particolare frame, la lunghezza del contenuto, la masking-key e i dati veri e propri. Partendo dall’alto e da sinistra verso destra si ha. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-----------------------------+ |F|R|R|R|opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - -+ | Payload Data continued ... | Bit descrizione Fin (bit 0) serve ad indicare l’ultimo frame del messaggio, sarà quindi impostato a 1 quando il frame è l’ultimo di una serie, oppure se il messaggio è composto da un singolo frame (in quanto primo e ultimo della serie). RSV1, RSV2,RSV3 (bit 1-3) Sono riservati alle estensioni dei WebSoket e dovrebbero essere 0 a meno che ne sia richiesto l’uso da parte di una specifica estensione. Opcode (bit 4-7) Determinano la tipologia di frame, i frame di controllo comunicano lo stato del WebSoket, mentre gli altri trasportano dati. Le diverse tipologie di codici sono le seguenti. TPSIT 5 um 46 di 434 0x0: continuation frame, contiene dati che devono essere messi in coda al frame precedente. 0x1: text frame, questo frame ed eventuali successivi, contiene del testo. 0x2: binary frame, questo frame ed eventuali successivi, contiene dati in formato binario. 0x3 - x7: riservati a frame che non siano di controllo, per essere sfruttati da estensioni di WebSoket. 0x8: close frame, questo frame dovrebbe chiudere la connessione. 0x9: ping frame. 0xA: pong frame. 0xB - 0xF: riservati a frame di controllo. Mask (bit 8) Determina se un certo frame usi una maschera o no. Lunghezza del Payload (bit 9-15 o 16-31 o 16-79) Questi sette bit indicano la lunghezza del contenuto: se il valore in questo set è 126, la lunghezza è rappresentata dai seguenti 2 byte (bit 16-31), se invece è127, la rappresentazione è estesa fino al bit 79 (4byte in più). Masking Key (i seguenti 4 byte) Rappresenta la maschera, quando il bit MASK è impostato a 1. Payload (tutti i dati successivi) È possibile trasferire il payload con più frame: si conosce la lunghezza effettiva del messaggio grazie all’informazione sulla lunghezza codificata nell’header del frame ed è possibile aggiungere dati finché non si riceve un frame con il flag FIN. Tutti i frame consecutivi saranno contrassegnati dall’opcode 0X0 (continuation frame). Il frame dei dati permette di capire quanto sia grande un messaggi, il tipo di codifica e l’eventuale maschera. È possibile inviare messaggi di qualunque lunghezza, purché questa sia rappresentabile con un numero base-64, in pratica 9.223.372.036.854.775.808 cifre. Il WebSocket, oltre all’indirizzo IP e alla porta, mantiene un canale duraturo con il server, in questo modo il client e il server possono inviare contemporaneamente dati tramite lo stesso collegamento senza necessità che vi sia prima una richiesta. Per la creazione del collegamento, sfrutta una funzione dell’handshake HTTP: il cambio di protocollo tramite l’upgrade. Quest’opzione serviva originariamente a criptare collegamenti tramite la porta 80 non codificati attraverso un upgrade TLS. Handshake HTTP: client. Upgrade WebSocket Richiesta HTTP di upgrade al protocollo WebSocket. Richiesta di sicurezza Sicurezza del client tramite richiesta dell’origine e chiave. Handshake HTTP: server. Conferma upgrade Conferma della richiesta di upgrade tramite risposta HTTP. TPSIT 5 Chiave di sicurezza Conferma del protocollo del server tramite una chiave di sicurezza. um 47 di 434 Affinché comunichino fra loro solo punti finali WebSocket ammessi, gli sviluppatori hanno inserito nell’header HTTP alcuni meccanismi di sicurezza: il client genera nella sua richiesta una chiave di sicurezza in Base64 che il server integra con una stringa standard, producendo un valore di hash SHA-1 (Secure Hash Algorithm) che è a sua volta codificato e rispedito al client. In questo modo client e server si assicurano di utilizzare il protocollo WebSocket. L’indicazione di origine nella richiesta protegge, inoltre, un server WebSocket da accessi indesiderati da fonti esterne, facendo sì che solo client a lui noti e da lui supportati possano costituire una connessione. L’ultimo meccanismo di difesa ha luogo dopo l’handshake HTTP: il client WebSocket deve codificare ogni pacchetto di dati con una maschera EX-OR, affinché server proxy intermedi non considerino erroneamente il traffico WebSocket come richieste HTTP. PROGRAMMAZIONE Le classi per comunicare con il protocollo WebSocket sono incluse nel namespace Windows.Networking.Sockets e sono: MessageWebSocket e StreamWebSocket. Aprire la connessione con il server La classe MessageWebSocket permette di gestire il ciclo di vita della comunicazione TCP tramite messaggi; per aprire la connessione si usa il metodo ConnectAsync che accetta in input un oggetto Uri che rappresenta l’URI del server verso cui aprire la connessione. webSocket = new MessageWebSocket(); await webSocket.ConnectAsync(server); Inviare messaggi al server La classe MessageWebSocket espone la proprietà OutputStream di tipo IOutputStream che rappresenta lo stream tramite il quale è possibile inviare dati al server. Per inviare dati al server non si deve utilizzare la proprietà OutputStream ma si deve creare un’istanza della classe DataWriter contenuta nel namespace Windows.Storage.Streams alla quale si passa in input lo stream, espone diversi metodi tipizzati: WriteInt32, WriteString e WriteBoolean che scrivono in un buffer di memoria. Terminato di scrivere nel buffer, s’invia il contenuto con il metodo StoreAsync e si scollega il writer dallo stream con il metodo DetachStream. La sola differenza tra il protocollo WebSocket e il protocollo TCP è che con il protocollo WebSocket non ci si deve preoccupare del formato del messaggio (con il protocollo TCP si metteva prima la lunghezza del messaggio e poi il messaggio stesso) perché l’impacchettamento del messaggio è gestito dalla classe MessageWebSocket. Ricevere messaggi dal server La classe MessageWebSocket espone l’evento MessageReceived che si scatena ogni volta che si riceve un messaggio dal server. In questo modo non si deve fare altro che sottoscriversi all’evento e processare il messaggio nel delegate. Il delegate MessageReceived riceve l’istanza della classe MessageWebSocket che ha generato l’evento e un parametro che rappresenta il messaggio ricevuto. Di quest’ultimo oggetto s’invoca il metodo GetDataReader che restituisce un oggetto DataReader contenente il messaggio. Infine, s’invoca il metodo ReadString per ottenere il messaggio come stringa. webSocket.MessageReceived += MessageReceived; private void MessageReceived(MessageWebSocket sender, MessageWebSocketMessageReceivedEventArgs args) TPSIT 5 um 48 di 434 { try { Log("Messaggio Ricevuto; Tipo: " + args.MessageType); using (DataReader reader = args.GetDataReader()) { reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8; Log(reader.ReadString(reader.UnconsumedBufferLength)); } } catch (Exception ex) { WebErrorStatus status=WebSocketError.GetStatus(ex.GetBaseException().HResult); Log("Errore: " + status + " - " + ex.Message); } } WINDOWS 8.X Progettare un’applicazione Windows Store che usa WebSocket. TPSIT 5 um 49 di 434 CLIENT/SERVER INTRODUZIONE Un’applicazione client/server è divisa in due parti. 1. Applicazione server: fornitore di servizi. 2. Applicazione client: utilizzatore di servizi del server. Client e server risiedono su PC diversi connessi da una rete di comunicazione. Aderisce ad un comune protocollo. Indipendente dal tipo di piattaforma, di SO e meccanismi base di comunicazione. Modello non simmetrico: richiesta/risposta. Ogni macchina può gestire molteplici client, oppure molteplici server o entrambi. Esempio, comunicazione asimmetrica, molti client e un server. Un server può essere implementato in due modi. 1. Server iterativo È in grado di servire un client alla volta, questa limitazione può essere non influente nel caso di client che restano connessi solo per il tempo necessario a ottenere una risposta dal server. while (server_attivo) { // affinché il server è in attività accetta nuova richiesta dal client if (richiesta_accettabile) While (client_connesso) // affinché il client non decide di sconnettersi <Leggi richiesta client> <Rispondi al client> } // sconnettiti dal client } 2. Server concorrente Nel momento in cui ricevono una richiesta di connessione da parte di un client, generano un nuovo processo che gestisce lo specifico client in modo da poter accettare e servire altre richieste di connessione contemporanee. Deve rispondere simultaneamente a quanti più client possibile. La sua implementazione può essere eseguita utilizzando tre possibili meccanismi. 2.1 Server multithreading Il server crea un thread per ogni client, quindi ad un certo istante ci sono tanti thread in esecuzione quanti sono i client connessi. Il server dealloca il thread quando il client si sconnette. Vantaggi: molto semplice da implementare. TPSIT 5 um 50 di 434 Svantaggi: richiede risorse in termini di CPU e memoria. 2.2 Single threading Il server crea un solo thread che ha il compito di gestire la comunicazione con tutti i client. La tecnica di comunicazione è l’overlapped I/O basata sul seguente meccanismo. Ogni operazione d’I/O è eseguita in background. Il thread è libero di eseguire nel frattempo altre operazioni, quando il SO segnala il completamento di un’operazione d’I/O, il thread procede alla R/W dei dati. Vantaggi: richiede meno risorse di memoria e CPU. Svantaggi: è di più complessa implementazione. 2.3 Thread pool Un numero prefissato di thread è creato e attivato, alla chiusura della connessione, il thread relativo non è terminato ma è sospeso e “riciclato” per una nuova connessione. Il client/server è il modello di riferimento per il networking a qualunque livello, si contrappone come filosofia e come struttura al mainframe. Il client manda un messaggio al server chiedendo un servizio. Il server esegue il servizio richiesto e fornisce i risultati al client. LIVELLI Come evitare l’overhead dei protocolli a stack con molti livelli, per esempio ISO/OSI. 1. Modello connection oriented: tutto il traffico usa la stessa connessione, usato nelle WAN (Wide Area Network), protocollo standard TCP. 2. Modello connectionless: usato nelle LAN, protocollo IP o UDP, lo stack ha meno livelli, ne bastano tre. Livello fisico: si occupa della trasmissione. Livello collegamento dati: si occupa di gestire il traffico. Livello rete: non è necessario, se è una LAN. Livello trasporto: non è necessario. Livello sessione: definisce l’insieme di richieste/risposte consentite. Esempio, il client vuole fare la copia di un file utilizzando i servizi forniti da un server. Client e server devono condividere qualche definizione, quindi si può includere lo stesso file di definizioni nel codice delle due applicazioni. #include <stdio.h> #include <string.h> #include <stdlib.h> #define TRUE 1 #define MAX_PATH 255 #define BUF_SIZE 1024 TPSIT 5 /* definizioni necessarie per client e server */ /* lunghezza massima del nome del file */ /* quantità di dati da trasferire in una volta */ um 51 di 434 #define FILE_SERVER 243 /* indirizzo di rete del file del server */ /* definizioni delle operazioni consentite*/ #define CREATE 1 /* crea un nuovo file */ #define READ 2 /* legge i dati da un file e lo restituisce */ #define WRITE 3 /* scrive i dati sul file */ #define DELETE 4 /* cancella un file esistente */ /* codici di errore */ #define OK 0 /* operazione eseguita correttamente */ #define E_BAD_OPER -1 /* operazione richiesta sconosciuta */ #define E_BAD_PARAM -2 /* errore in un parametro */ #define E_IO -3 /* errore del disco o altro errore d'I/O */ /* definizione del formato del messaggio*/ struct message { long source; /* identità del mittente */ long dest; /* identità del ricevente */ long opcode; /* operazione richiesta */ long count; /* numero di byte da trasferire */ long offset; /* posizione nel file per avviare I/O */ long result; /* risultato dell'operazione */ char name[MAX_PATH]; /* nome del file in corso di gestione */ char data[BUF_SIZE]; /* dati che devono essere letti o scritti */ }; int copy(char *src, char *dst); void initialize (void); void receive (int ,message m); void send (long m, message n); int do_create (message m,message n); int do_read (message m,message n); int do_write (message m,message n); int do_delete (message m,message n); int main(void) { message ml, m2; /* messaggi in entrata/uscita */ int r; /* codice di risultato */ system("cls"); /* Il server gira senza fine */ while(TRUE) { /* protocollo richiesta/risposta*/ receive(FILE_SERVER, ml); /* blocco in attesa di un messaggio */ /* messaggio concernente la tipologia di richiesta */ switch(ml.opcode) { case CREATE: r = do_create(ml, m2); break; case READ: r = do_read(ml, m2); break; case WRITE: r = do_write(ml, m2); break; case DELETE: r = do_delete(ml, m2); break; default: r = E_BAD_OPER; } m2.result = r; /* ritorna il risultato al client */ /* protocollo richiesta/risposta */ send(ml.source, m2); /* invio risposta */ TPSIT 5 um 52 di 434 } system("pause");return(0); } void initialize (void) { } void receive (int ,message m) { } void send (long m, message n) { } int do_create (message m,message n) { return (0); } int do_read (message m,message n) { return (0);} int do_write (message m,message n) { return (0); } int do_delete (message m,message n) { return (0);} /* copia i file usando il server */ int copy(char *src, char *dst) { struct message ml; /* buffer messaggio*/ long position; /* posizione corrente del file */ long client = 110; /* indirizzo client */ initialize(); /* preparare esecuzione */ position = 0; do { ml.opcode = READ; /* operazione è in lettura */ ml.offset = position; /* posizione corrente nel file */ ml.count = BUF_SIZE; /* numero di byte da leggere */ strcpy(ml.name, src); /* copia del nome del file da leggere */ /* protocollo richiesta/risposta */ send(FILE_SERVER, ml);/* invia un messaggio al file server */ /* attesa per la risposta */ receive (client, ml); /* scrivere i dati appena ricevuti per il file di destinazione */ ml.opcode = WRITE; /* operazione è una scrittura */ ml.offset = position; /* posizione corrente nel file */ ml.count = ml.result; /* numero di byte da scrivere */ strcpy(ml.name, dst); /* copia del nome del file da scrivere */ /* protocollo richiesta/risposta */ send(FILE_SERVER, ml);/* invia un messaggio al file server */ /* blocco di attesa per la risposta */ receive(client, ml); position += ml.result; } while( ml.result > 0 ); return(ml.result >= 0 ? OK : ml.result); } INDIRIZZAMENTO macchina.processo TPSIT 5 um 53 di 434 Bisogna individuare il server attraverso un identificatore univoco: un indirizzo. Per sapere l’indirizzo del server, esistono diverse modalità. Se il server gestisce più processi, come sapere qual è il processo giusto? Il kernel del server non può saperlo. Occorre che il client identifichi sia la macchina sia il processo. Il messaggio è indirizzato alla macchina o ad un singolo processo? Come numerare i processi? Ogni macchina può numerarli partendo da 0. Non è necessario uno schema globale perché la coppia macchina.processo identifica univocamente un processo, senza ambiguità. Problema: il sistema macchina.processo non è trasparente, per esempio, se il file server inizialmente è attivo sulla macchina 23 e poi è spostato sulla macchina 17, allora qualsiasi riferimento alla 23 non va più bene. Altra soluzione. Assegnare a ogni processo un indirizzo unico che non contiene il numero della macchina. È necessario un processo centralizzato che fornisca tali numeri, può avere problemi di scalabilità. Altra soluzione. Ogni processo sceglie a caso un numero da un insieme molto grande, per esempio 64 bit. La probabilità che due processi scelgano lo stesso numero è bassa. Name server Inserire una macchina che realizzi il mapping: nome macchina in indirizzo. Problema: è centralizzato ma si possono realizzare dei name server distribuiti, come per esempio DNS. Broadcast TPSIT 5 um 54 di 434 Per individuare la macchina, può essere utilizzato anche dal server nel momento in cui sceglie il suo numero identificativo per verificare che non sia già assegnato ad altri. Il sender manda uno speciale pacchetto locate packet contenente il numero del processo destinazione. Tutte le macchine ricevono tale messaggio. Tutti i kernel controllano se il processo destinazione è loro. La macchina giusta rispedisce nella rete il proprio indirizzo. Il sender prende questo indirizzo e lo usa per eventuali comunicazioni future. Questa tecnica può essere applicata solo se esiste la possibilità di fare broadcast, per esempio in Ethernet è semplice fare un broadcast. Problema: la rete rischia di essere “inondata” di locate packet. Si può mitigare il problema grazie al caching. PRIMITIVE DI COMUNICAZIONE Bloccante/non bloccante? Bufferizzata/non bufferizzata? Sender: se c’è il buffer può bloccarsi solo in S1 (se il buffer è pieno), altrimenti può bloccarsi in S2 (quando il messaggio è stato inviato, in altre parole ha lasciato il nodo di partenza), oppure in S3 (quando il messaggio è arrivato al nodo destinazione) oppure in S4 (quando il messaggio è stato prelevato dal destinatario). Receiver: si blocca sempre in S3. La send bloccante con scambio di messaggi è quella che blocca finché il messaggio non è stato prelevato dal destinatario, quella non bloccante è quella che prosegue non appena il messaggio è stato preso in carico dal sottosistema di comunicazione, scaricata nel buffer locale, se c’è o immessa nella rete. La gestione del buffer presso il ricevitore può essere difficile per il SO. Cosa fare se arriva un messaggio e nessuno ha dichiarato di voler ricevere qualcosa? La cosa si semplifica un po’ se a livello utente (chi programma) si rende esplicita la possibilità di bufferizzazione attraverso la definizione di mailbox che sono porte di comunicazione con possibilità di bufferizzazione entro un limite definito. TPSIT 5 um 55 di 434 Come essere sicuri che un messaggio spedito arrivi a destinazione? Esempio, considerare primitive bloccanti. Il client esegue una send e si blocca. Quando il messaggio è stato spedito, allora il client si sblocca. Se il sottosistema di comunicazione non è affidabile, il client non sa se il messaggio è davvero arrivato a destinazione. Possibili approcci. Ridefinire la semantica della send, in modo che il sistema non garantisca la consegna dei messaggi. È l’utente che deve preoccuparsi d’implementare meccanismi di affidabilità, per esempio il sistema postale. Si richiede al kernel ricevente di mandare un messaggio di ACK al mittente. Il client si sblocca solo dopo aver ricevuto l’ACK. Se si sfrutta il fatto che nel paradigma client/server si hanno i seguenti punti. 1. Richiesta dal client al server. 2. Risposta dal server al client. Il server non manda l’ACK ma è la risposta che funge da ACK. Quando è opportuno inviare l’ACK? A ogni singolo pacchetto? Problematiche di comunicazione. I messaggi sono divisi in pacchetti che sono trasmessi. Vantaggio: se un pacchetto va perso, basta ritrasmetterlo. Svantaggio: molto traffico nella rete. A ogni messaggio? Vantaggio: meno traffico. Svantaggio: se un pacchetto va perso, il client non ricevendo l’ACK invia nuovamente l’intero messaggio. REQ REP ACK AYA IAA TA AU TPSIT 5 Client – Server Server – Client Client – Server Server – Client Server – Client Server – Client Si usa per fare la richiesta. È la risposta del server. Conferma arriva di un pacchetto. Are You Alive? I Am Alive. Try Again (mailbox piena). Address Unknown. um 56 di 434 ALTRI METODI PER LA PROGETTAZIONE DI APPLICAZIONI CLIENT/SERVER IPC (Inter Process Communication) Se client e server si trovano sulla stessa macchina, si hanno a disposizione gli strumenti IPC del SO. Linguaggi concorrenti, in altre parole dotati d’istruzioni apposite per attivare e far sincronizzare/comunicare più processi/thread. System calls messe a disposizione dal SO. Librerie per lo sviluppo di applicazioni concorrenti. RPC Fu progettata da Sun Microsystems nei primi anni ’80. La gestione dell’interazione tra client e server avveniva tramite primitive d’I/O del SO: il livello di astrazione era troppo basso. Allora si cercò di fornire al programmatore un meccanismo per accedere a risorse remote attraverso la chiamata a sottoprogramma, nascondendo tutti i dettagli relativi al setup della connessione, alla serializzazione dei parametri e all’eterogeneità delle piattaforme. Un’applicazione sulla macchina A chiama un sottoprogramma sulla macchina B. A si sospende, inizia l’esecuzione del sottoprogramma sulla macchina B. Il sottoprogramma chiamante, lato client, richiama una versione diversa del sottoprogramma, chiamata client stub. Il client stub costruisce il messaggio e tramite send lo invia. Il server passa il messaggio al server stub tramite una receive. Il server stub legge il messaggio, chiama il sottoprogramma che ritorna il risultato allo stub e costruisce il messaggio da inviare al client. Dal punto di vista concettuale è come se l’invocazione avvenisse in locale. L’azione di mettere i parametri in un messaggio è chiamata marshaling. L’azione di estrarre il risultato dal messaggio è chiamata unmarshaling. TPSIT 5 um 57 di 434 TPSIT 5 um 58 di 434 DCOM (Distributed Component Object Model) Metodo OO (Object Oriented), permette agli oggetti COM in esecuzione su una macchina di creare oggetti COM su altre macchine, è un prodotto Microsoft ma è uno standard aperto. CORBA (Common Object Request Broker Architecture) È la specifica di un’architettura standard per ORB (Object Request Brokers), in altre parole intermediari di richieste a oggetti. Permette agli oggetti su una macchina di richiamare i metodi di oggetti su altre macchine, è uno standard aperto. Java RMI (Remote Method Invocation) È una trasposizione in ambito OO di RPC, è fornita la possibilità d’invocare un metodo su un oggetto remoto come se fosse locale. PROGRAMMAZIONE È sempre un client a contattare in modalità sincrona il server e mai viceversa. Il server è sempre in ascolto per una richiesta del client. Al ricevimento di una richiesta del client, il server risponde prima alla richiesta iniziale. Quando sono connessi, esiste una relazione paritetica il cui il client o il server possono richiedere informazioni uno all’altro. Quest’approccio è sempre corretto, però in qualche caso può non essere sufficiente, per esempio è il server a segnalare o a richiedere qualcosa ai client e non viceversa. Queste situazioni possono essere gestite con il modello unidirezionale: i client eseguono un polling al server per verificare che questi non abbia da comunicargli qualcosa. L’architettura del .NET Framework Remoting offre la possibilità di una comunicazione vera tra server e client, per far questo è necessario che il client sia disposto ad accettare richieste provenienti dal server. Come questo è implementato dipende dal canale utilizzato. HTTP: non consente questo modello di bidirezionalità. TCP: lo consente, a patto, però, di aprire una nuova porta TCP sul client per ricevere le richieste dal server, in pratica due canali aperti su due porte differenti. Sulla prima il client contatta il server e sulla seconda il server contatta il client. Thick client (spesso, grosso) È un’applicazione che prevede una logica elaborata per la manipolazione dei dati che gli sono trasmessi dal server. Thin client (sottile, leggero) È un’applicazione che non fornisce alcun supporto elaborativo e si limita a visualizzare i dati che gli sono trasmessi dal server. Un client deve impacchettare la sua richiesta in modo tale che questa possa essere compresa dal server. Il server riceve la richiesta, esegue il servizio e reinvia la risposta in modo comprensibile dal client. I passi sono gli stessi di un’applicazione che effettui una chiamata ad un sottoprogramma locale definito in una libreria. L’interazione client/server è qualcosa di simile ad una chiamata a sottoprogramma locale. 1. Impone al server un’interfaccia standard per il servizio. 2. Fa sì che il programmatore possa invocare un servizio remoto in modo semplice. Come nei sottoprogrammi locali, un servizio può essere reso più efficiente in modo trasparente al client, a patto che l’interfaccia rimanga inalterata. TPSIT 5 um 59 di 434 L’architettura a livelli è un modo per designare sistemi composti da più sotto sistemi distinti, ciascuno dei quali svolge un insieme coerente di funzionalità e concorre con gli altri al raggiungimento dell’obiettivo complessivo del sistema. In ogni livello gli elementi devono svolgere una determinata funzione e scambiare, se necessario, dati e risultati con gli altri elementi dello stesso livello e con quelli del livello inferiore o superiore. Ogni livello può essere costituito da uno o più componenti residenti sullo stesso PC o distribuiti su PC diversi. Le architetture possono differire a seconda di cosa si mette sul client e cosa sul server. Si può scomporre le funzionalità in tre insiemi. 1. UI. 2. Elaborazione. 3. Gestione persistente dei dati. A seconda di come sono allocate queste funzionalità si distinguono. 1. Architetture a due livelli (2-tier): two-tiered architectures Sviluppata negli anni ‘80, partendo dall’esperienza dei file server. È formata da tre componenti distribuiti su due livelli. 1. GUI (Graphics User Interface): interfaccia di comunicazione con l’utente, è il livello di presentazione Client Tier; si hanno tutte le tecnologie dedicate all’interfaccia utente come Silverlight ma anche le tecnologie per la gestione di dati locali e di connessione a servizi di rete RIA (Rich Internet Application) Services. 2. PM (Processing Management): contiene gli elementi che implementano il cuore dell’applicazione, in altre parole le regole con le quali devono essere elaborate le informazioni presenti sul DB prima di essere mostrate attraverso lo strato di presentazione, Middle o Business Tier; si hanno le tecnologie condivise con il livello client, come Data Workspace e RIA Services ma anche la parte logica dell’applicazione. 3. DBM (DB Management): è il livello di accesso ai dati, responsabile dell’accesso e del mantenimento dei dati persistenti dell’applicazione, esso si estende fino al DBMS. L’architettura assegna la GUI al client, il DBM al server e divide il PM tra client e server. Il client comunica direttamente con il server, senza intermediari, scarsa scalabilità, in altre parole la capacità di adattarsi alla crescita del sistema, infatti, se il server ricevesse un carico di lavoro troppo elevato da parte dei client, l’unica soluzione consisterebbe nell’aggiornare il server. In pratica, l’applicazione gira sul desktop, componenti server e DB sono su un PC di rete. Presentazione distribuita Tutta la logica è nel server e il client si occupa solo d’interagire con il server che gli affida compiti specifici di presentazione. Presentazione remota La logica applicativa e i dati sono nel server, il client fa da pannello di controllo remoto. Logica distribuita Il client contiene anche una parte della logica distribuita, si cerca di alleggerire il server dal punto di vista computazionale, il client si appesantisce ma si rende più scalabile il server. Accesso remoto ai dati Il server fornisce solo l’accesso ai dati grezzi, il client è un front end per la loro gestione. Base di dati distribuita Il client contiene anche una parte dei dati. TPSIT 5 um 60 di 434 Questa suddivisione dà buoni risultati fino a quando non si verifica la situazione in cui un server restituisce più dati di quelli che il client è in grado di elaborare. All’aumentare del numero degli utenti attivi, il carico sul DB aumenta. All’aumentare del numero dei DB con cui un’applicazione deve interagire, aumenta il numero di connessioni che l’applicazione deve mantenere aperte. 2. Architetture a tre livelli (3-tier): three-tiered architectures Sviluppata negli anni ’90, è adottatata in Internet, un livello client (browser), un livello di presentazione (server web), un livello di gestione dati (DB server) e un livello applicazione. È formata da tre componenti distribuiti su tre livelli. 1. GUI: client. 2. DBM: server. 3. MiddleWare: si trova tra la GUI e il DBM e fornisce la business logic dell’applicazione. Lo spostamento della business logic e l’accesso ai dati nel livello intermedio significa una quantità minore di codice per i client, semplifica, inoltre, la gestione dei componenti di processo aziendali e la logica di accesso ai dati. MiddleWare Letteralmente S/W di mezzo posto a metà fra il SO e le applicazioni, consiste in un insieme di servizi e/o di ambienti di sviluppo di applicazioni distribuite che permettono a più entità (processi e oggetti), residenti su uno o più PC, d’interagire attraverso una rete d’interconnessione a dispetto di differenze nei protocolli di comunicazione, architetture dei sistemi locali e SO. Alcuni tipi di middleware sono TM (Transaction Monitor) e ORB, per esempio IBM (International Business Machines) WebSphere e Microsoft Transaction Server. L’utilizzo del middleware consente di ottenere un elevato livello di servizio per gli utenti e un elevato livello di astrazione per i programmatori. La flessibilità e la natura distribuita delle applicazioni multilivello rendono quest’architettura ideale sia per progetti Internet sia per progetti di reti aziendali. In pratica, l’applicazione gira sul desktop, componenti server e DB girano sul server web. Server diversi ma un solo DB. Vantaggi. Scalabilità: l’architettura può essere estesa. Gestibilità: isolamento dei problemi, distribuzioni e configurazioni. TPSIT 5 um 61 di 434 Trasparenza: commutazione da un PC ad un altro. Modalità connessa Nelle applicazioni client/server, si stabilisce una connessione ad un DB all’avvio dell’applicazione e si mantiene attiva durante tutto il ciclo di vita dell'applicazione. Quest’approccio diminuisce il tempo di attesa delle operazioni sul DB poiché elimina il tempo necessario alla connessione ma in alcuni casi non è possibile utilizzare per risorse di sistema limitate. Modalità disconnessa Si apre una connessione solo quando serve, si estrae un blocco di dati, si memorizza sul client e quindi si chiude la connessione per rilasciare le risorse lato server ad essa associate; dopo aver caricato i dati sul client, è possibile utilizzarli per eseguire qualsiasi tipo di elaborazione, come per esempio aggiungere nuovi record oppure modificare o eliminare quelli esistenti. Una volta che sono terminate le opportune elaborazioni sui dati, si riapre la connessione, si scaricano i dati sul server e si richiude di nuovo. Le applicazioni sono connesse al DB solo il tempo necessario per recuperare o aggiornare i dati, di contro, aprire e chiudere la connessione significa maggiore tempo di attesa. Architetture browser a tre livelli Il browser è un thick client perché elabora codice lato client che gli è trasmesso ogni volta che accede all’applicazione, il server web è il server. La differenza sostanziale è che, a differenza di una connessione su una LAN, non si hanno garanzie che la connessione ad un server di dati su Internet rimanga attiva. Le tecnologie di accesso ai dati utilizzate in Internet spesso partono dal presupposto che una connessione sia continua fino al completamento di una transazione. Le implicazioni sono minime per applicazioni che eseguono solo richieste ma sono rilevanti per quelle che richiedono funzionalità come la modifica e l’eliminazione dei dati. Il risultato di una query dev’essere conservato anche se la connessione è interrotta. In fase di aggiornamento, è necessario che sia eseguita un’operazione automatica di riconnessione e sincronizzazione del DB, questa situazione è definita accesso ai dati senza connessione, modalità di lavoro offline: le tecnologie di accesso ai dati includono sia funzionalità con connessione sia senza connessione. La creazione di script server con tecnologie di accesso ai dati incorporate consente di accedere in modalità browser a dati aziendali su Internet e di fornire le informazioni ai client come dati in formato HTML. In questo modo un utente può visualizzare i dati aziendali nello stesso modo in cui visualizza altre informazioni su Internet. ARCHITETTURA DI UN’APPLICAZIONE CLIENT/SERVER TPSIT 5 um 62 di 434 Server Contiene la logica dell’applicazione, monitora una porta TCP e attende le richieste di connessione da parte dei client. Client È un’applicazione desktop che permette all’utente di collegarsi al server, non deve far altro che adeguarsi al protocollo e lavorare come un tramite tra l’utente e il server: è un intermediario. Il server deve svolgere i seguenti compiti. 1. Autenticare: l’identità e l’autenticità del client; per esempio l’utente deve digitare username e password, il server provvede all’autorizzazione, validando la richiesta di accesso, associando il client ad un’utenza del sistema. 2. Autorizzare: il client a compiere determinate operazioni. Il collegamento è temporaneo, dura finché il client è connesso e si chiama sessione. Networking Il server deve monitorare una porta del PC, in attesa delle connessioni in ingresso, per esempio il server web ascolta la porta 63301. I client, per connettersi al server, devono conoscere il suo indirizzo e la porta da esso ascoltata. Stabilita la connessione, il protocollo TCP permette di scambiare dati in ambo le direzioni. I dati scambiati, a livello del protocollo di trasporto, sono sequenze di byte. Client e server sono collegati in una LAN o via Internet: una rete di questo tipo non può essere definita “ad alta affidabilità”, se qualcuno intercetta la comunicazione, infatti, può leggervi in chiaro i dati dell’utente connesso. Il server deve offrire un meccanismo di connessione criptato, per esempio basato su SSL. La GUI gira al server qualsiasi richiesta svolta dall’utente e attende dal server una risposta o una conseguenza. Per evitare che un rallentamento della rete influenzi negativamente il grado di reattività del client, tutte le comunicazioni con il server devono essere svolte in thread secondari, diversi da quello usato dalla GUI per dialogare con l’utente. La caduta della connessione, sia lato server sia lato client, manda in crash l’applicazione. Bisogna prevedere un meccanismo di recovery, da implementare sia lato server sia lato client che consenta all’utente di riprendere l’applicazione dal punto che si era interrotta dopo essersi riconnesso. Si potrebbe, per esempio stabilire un timeout: se la connessione cade, il server aspetta per un minuto che il client si riconnetta e solo allo scadere di questo intervallo lo esclude automaticamente e fa procedere gli altri client. TPSIT 5 um 63 di 434 Protocollo applicativo Il protocollo usato per le applicazioni è un protocollo over TCP/IP, perché si basa sui socket TCP per lo scambio bidirezionale di dati. Il server, dialoga attraverso delle richieste HTTP, all’interno delle quali è possibile inserire comandi, sotto forma di parametri. Il vantaggio delle applicazioni over HTTP è che possono essere utilizzate anche da dietro un firewall o in una LAN, attraverso un proxy HTTP. Se puoi navigare sul web, allora puoi anche usare l’applicazione, perché il canale di comunicazione è lo stesso. Il modo più semplice per progettare un protocollo di livello applicativo per un server di tipo testuale è quello di prevedere parole chiave corrispondenti alle operazioni che il server deve rendere disponibili e che costituiscono i veri e propri comandi. I dati scambiati, a livello del protocollo applicativo, danno significato alle sequenze di byte inviate e ricevute. Tipo di dato scambiato I byte scambiati attraverso la connessione sono interpretati come caratteri ASCII. Non appena la connessione è stabilita, il server resta in attesa di una richiesta da parte del client. Tutto il protocollo applicativo si articola sul paradigma richiesta/risposta: il client fa una richiesta e il server gli risponde. Richieste Un messaggio è costituito da parti, ciascuna delle quali è una stringa. La prima parte è il comando, mentre le successive sono i parametri. Sono comprese in una sola riga che ha il seguente formato. COMANDO PARAMETRO1 PARAMETRO2 … Il comando esprime la natura della richiesta. La quantità e la qualità dei parametri dipendono dallo specifico comando espresso. Comandi e parametri sono separati da un blank. Per esempio, il comando per chattare è il seguente. MESSAGE MESSAGGIO MESSAGE Ciao I comandi e parametri sono codificati con la codifica URL encoding, la stessa che si utilizza per la composizione degli indirizzi URL. Per esempio, il messaggio di chat “In che città vivi?” è inviato con la richiesta seguente. MESSAGE In+che+citt%C3%A0+vivi%3F Risposte Una volta inviata la richiesta, il client attende risposta dal server. Le risposte possono essere articolate e contenere molti dati. Per questo saranno organizzate nella seguente maniera. BEGIN MESSAGGIO1 MESSAGGIO2 TPSIT 5 um 64 di 434 MESSAGGIO3 … END Ciascuno dei messaggi contenuti nella risposta ha la stessa forma delle richieste. COMANDO PARAMETRO1 PARAMETRO2 … Comando e parametri devono essere sottoposti alla codifica URL. Esecuzione Per provare il server, occorre un client. Se il server usa un protocollo applicativo puramente testuale, la verifica del suo corretto funzionamento può essere eseguita utilizzando un client generico TELNET. TELNET esegue una connessione al server individuato dall’indirizzo IP e dal numero di porta TCP indicati, dopo di che ciò che è digitato dal client è trasmesso mediante il socket e ciò che è ricevuto è visualizzato. Eseguendo contemporaneamente più client TELNET, è possibile verificare la modalità di funzionamento del server. Avviare il server, quindi aprire la CLI (Command Line Interface) e digitare il comando seguente. Premere INVIO. Stabilita la connessione, è possibile digitare i comandi. Al limite, il server può fare solo l’eco di tutti i dati che riceve ritrasmettendoli al client da cui li ha ricevuti. TPSIT 5 um 65 di 434 TPSIT 5 um 66 di 434 Microsoft Visual C# Progettare una chat su TCP/IP. TPSIT 5 um 67 di 434 Progettare una chat sicura su TCP/IP. Client Di tipo WPF (Windows Presentation Foundation), implementa le classi che permetteranno la comunicazione con il server in modalità criptata con il flusso NetCryptoStream, l’infrastruttura di comunicazione sicura è del tipo end-to-end. Server Di tipo console, implementa le classi necessarie alla coordinazione dei messaggi criptati in ricezione e spedizione tra i vari client. Libreria SECURENET.DLL Entrambe le applicazioni fanno uso della libreria di classi, serve a mantenere le classi comuni tra client e server che implementa il flusso criptografato NetCryptoStream. Flusso criptografico È in grado di trasformare le informazioni inviate in chiaro dai client in informazioni criptate e di ritrasformarle in chiaro in ricezione. Il server decifra le informazioni ricevute e le rispedisce criptate a tutti i client connessi che, a loro volta, le decifreranno e le visualizzeranno a video. Questo poiché ogni client utilizza una propria chiave segreta condivisa solo con il server. La lettura e la scrittura sul canale creato è gestita tramite un oggetto NetworkStream, passato al costruttore della classe che implementa il flusso criptografato, inizializzato dal metodo GetStream dell’oggetto TcpClient. Il thread di ascolto implementato nel metodo ClientConnected, una volta avviato e stabilita la comunicazione criptata, rimane in attesa dei messaggi dei client da trasmettere a tutti gli altri utenti connessi. TPSIT 5 um 68 di 434 TPSIT 5 um 69 di 434 Progettare una chat. TPSIT 5 um 70 di 434 Microsoft Visual C++ Progettare un’applicazione per un gioco di carte. Il server è in ascolto e quando il client stabilisce una connessione, il server avvia la partita. Il client, dopo aver impostato l’IP del PC su cui è in esecuzione il server, si connette e resta in attesa che il server gli comunichi tutte le carte del mazzo. Dopo aver “distribuito” le carte, si gioca a turno. L’applicazione invierà la carta giocata all’avversario e resterà in attesa di una risposta. Se si è collegati a Internet, è possibile inviare dal server un’email, indirizzata al client, contenente l’indirizzo IP. Il protocollo di comunicazione lavora nelle due direzioni e contiene come primo carattere un carattere di controllo che corrisponde al tipo di messaggio che un PC invia all’altro. Il server si occupa della gestione della partita: inizia, mescola il mazzo, invia al client tutte le informazioni (4 per ogni carta, messaggi B, C, D, E) relative al mazzo, invia il turno (messaggio F) e il messaggio A che indica al client che la partita può iniziare. I messaggi G e H indicano che l’avversario ha giocato la sua carta e la comunica all’altro giocatore. Carattere di controllo A B C D E F G H N M Tipo di Messaggio Direzione Fine trasmissione carte del mazzo, più il S->C turno. Carta mazzo numero. S->C Carta mazzo seme. S->C Carta mazzo punto. S->C Carta mazzo immagine. S->C Turno iniziale. S->C Posizione carta giocata dal server (IG1). S->C Posizione carta giocata dal client (IG2). C->S Il server si è disconnesso oppure ha chiuso S->C l’applicazione. Il Client si è disconnesso oppure ha chiuso il C->S programma. Passare allo scanner le 40 carte più una carta rovesciata che rappresenta il mazzo e salvarle in un file BMP (BitMaP) con risoluzione 256 colori. Creare un file di risorse e inserire tante nuove bitmap quante sono le carte e ricopiare in ognuna le immagini salvate precedentemente. Creare una DLL di risorse. Supporre di voler distribuire l’applicazione in un Paese in cui per giocare a carte si utilizzi un mazzo con immagini diverse. Basta inserire le immagini delle nuove carte in un’altra DLL di risorse, mantenendo invariati i nomi delle immagini nella risorsa e chiedere al giocatore, all’avvio dell’applicazione, di scegliere il mazzo di carte che vuole utilizzare. Si può fare la stessa cosa per le didascalie dei componenti visuali dell’applicazione, inserendo in una risorsa le stringhe delle didascalie tradotte nella lingua locale. TPSIT 5 um 71 di 434 TPSIT 5 um 72 di 434 Microsoft Visual Basic TPSIT 5 um 73 di 434 SIGNALR Permette la comunicazione bidirezionale in real-time tra client e server, in altre parole i dati visualizzati devono essere aggiornati costantemente, per esempio le quotazioni di borsa. In questi casi è il server che deve inviare gli aggiornamenti al client in modalità push, quindi evitando che sia quest’ultimo a controllare periodicamente la presenza di nuovi dati in modalità polling. Il protocollo HTTP non permette chiamate push, per questo è stato progettato SignalR. L’architettura è basata su due API (Application Programming Interface). 1. PersistentConnection: basso livello. 2. Hub: alto livello. Per eseguire le comunicazioni, SignalR usa le modalità di trasporto seguenti. WebSocket Dev’essere supportata sia dal server sia dal client. Server Sent Events Un browser riceve aggiornamenti automatici dal server. Forever Frame È creato un iFrame (inline Frame) nascosto che esegue una richiesta verso un endpoint del server che non è mai completata. Il server invia script al client che sono eseguiti immediatamente, realizzando, quindi, una comunicazione monodirezionale tra server e client. La comunicazione da client a server usa una connessione separata e, come nelle richieste HTTP, una nuova connessione è creata ogni volta che devono essere inviati dati. AJAX Long Polling Non crea una connessione persistente ma fa polling verso il server con una richiesta che rimane aperta fino a che quest’ultimo non invia una risposta. A questo punto, la connessione è chiusa e subito ne è fatta una nuova. All’avvio della connessione, è eseguita una negoziazione per stabilire quale modalità di trasporto utilizzare: questa procedura è chiamata TransportNegotiation. SignalR è costruito sopra OWIN (Open Web Interface for .NET), un’astrazione che TPSIT 5 um 74 di 434 disaccoppia le applicazioni web dal server e, quindi, può essere usato per il self-hosting di un’applicazione web in un proprio processo, al di fuori di IIS (Internet Information Services). Il client non dev’essere necessariamente una pagina HTML, in pratica le piattaforme supportate sono molteplici, per esempio in Xamarin il client è disponibile come libreria di classi portabile, quindi si può usare nelle app per iOS e Android. Esempio, progettare un canale di comunicazione per monitorare in real-time la posizione dei dispositivi mobili e inviare a essi comandi per eseguire determinate operazioni. Server Fare clic su File/Nuovo/Progetto… (CTRL+MAIUSC+N). Selezionare Modelli/Visual C#/Web/Applicazione Web ASP.NET vuota. Fare clic con il pulsante destro sul nome del progetto in Esplora soluzioni, dal menu contestuale selezionare Aggiungi/Nuovo elemento… e nella finestra che si apre selezionare Visual C#/Web/SignalR/Classe SignalR Hub (v2). Il template crea l’Hub seguente. public class MyHub1 : Hub { public void Hello() { Clients.All.hello(); } } Il metodo Hello potrà essere richiamato da ogni client connesso, non è definito nel codice perché è un oggetto di tipo dinamico e quindi è risolto a compile-time. L’istruzione Clients.All.hello fa sì che tutti i client ricevano un evento, generato dal server, con questo nome tramite una comunicazione push, in pratica è inviato un payload in formato JSON (JavaScript Object Notation) che indica che dev’essere invocato il gestore corrispondente. L’applicazione è composta da due attori. 1. Admins: sono gli amministratori che tracciano la posizione dei dispositivi e mandano loro messaggi. TPSIT 5 um 75 di 434 2. Clients: sono gli utenti che comunicano agli amministratori i loro spostamenti e possono inviare richieste di aiuto. Terminata la progettazione dell’Hub SignalR, bisogna registrare il servizio all’avvio dell’applicazione. Fare clic con il pulsante destro sul nome del progetto in Esplora soluzioni, dal menu contestuale selezionare Aggiungi/Nuovo elemento… e nella finestra che si apre selezionare Visual C#/Web/Generale/Classe di avvio di OWIN. Il template crea il metodo seguente, bisogna aggiungere l’istruzione per configurare l’Hub in modo che sia accessibile attraverso il web. public void Configuration(IAppBuilder app) { app.MapSignalR(); } Client Windows Phone 8.1 File PACKAGES.CONFIG Aggiungere al progetto i seguenti pacchetti. <?xml version="1.0" encoding="utf-8"?> TPSIT 5 um 76 di 434 <packages> <package id="Coding4Fun.Toolkit.Controls" version="2.0.7" targetFramework="wp80" /> <package id="Microsoft.AspNet.SignalR.Client"version="2.0.1"targetFramework="wp80"/> <package id="Microsoft.Bcl" version="1.0.19" targetFramework="wp80" /> <package id="Microsoft.Bcl.Build" version="1.0.13" targetFramework="wp80" /> <package id="Microsoft.Net.Http" version="2.1.10" targetFramework="wp80" /> <package id="Newtonsoft.Json" version="5.0.8" targetFramework="wp80" /> <package id="WPtoolkit" version="4.2013.08.16" targetFramework="wp80" /> </packages> File MAINPAGE.XAML L’interfaccia è composta da una sola pagina che visualizza una mappa con la posizione corrente dell’utente. Affinché l’app sia in grado di utilizzare il servizio di localizzazione dello smartphone, bisogna aggiungere le seguenti funzionalità. <Capabilities> <Capability Name="ID_CAP_NETWORKING" /> <Capability Name="ID_CAP_MEDIALIB_AUDIO" /> <Capability Name="ID_CAP_MEDIALIB_PLAYBACK" /> <Capability Name="ID_CAP_SENSORS" /> <Capability Name="ID_CAP_WEBBROWSERCOMPONENT" /> <Capability Name="ID_CAP_LOCATION" /> TPSIT 5 um 77 di 434 <Capability Name="ID_CAP_MAP" /> </Capabilities> Client Windows 8.1 Fornisce le funzionalità di amministrazione del sistema, in pratica visualizza su una mappa la posizione di tutti i client connessi. Aggiungere al progetto gli stessi pacchetti e, inoltre, il Bing Maps SDK (Software Development Kit). TPSIT 5 um 78 di 434 MODULO 2 APPLICAZIONI DISTRIBUITE Introduzione 80 Comunicazione cross process 83 Messaggi su code 88 WCF 106 WS 124 Grid service 192 REST/POX 194 Applicazioni orientate ai servizi TPSIT 5 206 um 79 di 434 INTRODUZIONE GENERALITà URI È una stringa che identifica univocamente una risorsa generica che può essere un indirizzo web, un documento, un’immagine, un servizio o un indirizzo di posta elettronica. Serve a “identificare”, mentre un URL serve a “localizzare”. Il fraintendimento nasce dal fatto che localizzare, equivale anche a identificare, dunque ogni URL è anche un URI, al contrario, esistono degli URI che non sono URL. Esempio, Andrea Sperelli può essere un URI perché identifica una persona, dal suo nome, però, non è possibile ricavare alcuna informazione su dove risieda, dunque non è un URL. Al contrario, Via Roma 148, Novara, è un URL, perché identifica un luogo, se a ogni numero civico corrisponde una sola residenza, ecco in questo caso allora definire un luogo equivale a definire anche un’identità, dunque un URI. La struttura generale è la seguente. potocollo:// host.dominio.toplevel:port/path/filename[?query][#fragment] Dove. protocollo: può essere HTTP, HTTPS, FTP, SMTP, … host: il nome del PC, in genere WWW (World Wide Web). dominio: è una stringa che identifica il dominio di secondo livello. toplevel: è una stringa che identifica il dominio di primo livello, .com, .net, .it. port: indica la porta TCP su cui il protocollo HTTP è in ascolto, per default 80. path: opzionale, indica il percorso della cartella per raggiungere la risorsa. filename: è il nome della risorsa. query: indica un elenco di argomenti di cui la pagina può disporre. www.miosito.it/search.jsp?nome=Andrea&cognome=Sperelli fragment: il browser identifica una sottoparte della risorsa, invece è ignorato dal server web. La sintassi URL permette d’indicare anche risorse non HTTP, per esempio FTP. ftp://[user[:password]@]host[:port]/path Dove. user: se omesso, qualifica una connessione anonima, anonymous. port: se omesso, è di default 21. La sintassi URL permette d’indicare anche risorse non HTTP, per esempio SMTP, naturalmente comporta l’apertura del client di posta di default. mailto:user@host[;user@host[...]][?Subject=...&Body=...] Dove. TPSIT 5 um 80 di 434 Subject: è l’oggetto dell’email. Body : è il testo dell’email. Un URL può anche indicare una risorsa locale. file://C:/Documenti/Immagini/2.GIF WWW È un’applicazione client/server. 1. Un server in grado di restituire, a richiesta, secondo un determinato protocollo, documenti ipertestuali memorizzati in locale, sul disco del server. 2. Un visualizzatore client in grado di richiedere documenti al server e visualizzarli in forma grafica. URL È un URI che, oltre a identificare una risorsa, fornisce mezzi per agire su o per ottenere una rappresentazione della risorsa descrivendo il suo meccanismo di accesso primario o la sua ubicazione in una rete. Esempio, l’URL http://www.miosito.it è un URI che identifica una risorsa l’home page e lascia intendere che una rappresentazione di tale risorsa, il codice HTML della versione corrente di tale home page, è ottenibile via HTTP da un host di rete chiamato www.miosito.it. Non tutti i caratteri sono ammessi in un URL che classifica i caratteri in tre categorie. 1. I caratteri alfanumerici, maiuscoli o minuscoli, numerici e alcuni di punteggiatura, _!~*, non sono riservati. 2. Alcuni caratteri sono riservati e sono invece utilizzati con significato particolare :;,/?&@$=+ 3. Caratteri che non hanno significato particolare ma che non possono essere utilizzati in un URL: lo spazio, in generale i caratteri non stampabili, #%<>. URN (Uniform Resource Name) È un URI che identifica una risorsa mediante un “nome” in un particolare dominio di nomi, namespace, può essere usato per parlare di una risorsa senza lasciar intendere la sua ubicazione o come ottenerne una rappresentazione. Esempio, l’URN: ISBN:0-395-36341-1 è un URI che consente d’individuare univocamente un libro mediante il suo nome 0-395-36341-1 nel namespace dei codici ISBN (International Standard Book Number) ma non suggerisce dove e come si può ottenere una copia di tale libro. Un’applicazione distribuita è un progetto costituito da singole parti tali che ciascuna parte potrebbe girare su PC diversi da quello locale, in altre parole comunica con diversi protocolli. Esempio, sul PC A potrebbe girare l’applicazione che recupera informazioni da un DB sul PC B potrebbe girare l’applicazione che utilizza questi dati, sul PC C potrebbe girare il DB: in pratica le singole applicazioni scambiano i dati fra loro. TPSIT 5 um 81 di 434 SOA (SERVICE ORIENTED ARCHITECTURE) C’è una ragione architetturale che ha spinto alla creazione di varie tecniche per far comunicare sistemi eterogenei. Per esempio, è sempre più rara la presenza del DB in locale sulla macchina client, si preferisce farlo risiedere su un server ed esporlo tramite servizi. Quest’architettura si chiama SOA e ha i seguenti vantaggi. Si basa sui servizi. Disaccoppiamento. Versioning. Centralizzazione della business logic. Scalabilità. Con quest’architettura si può cambiare la tecnica di accesso al DB in modo trasparente, eseguire del caching al posto dell’accesso diretto ai dati, oppure bilanciare i carichi di lavoro su diverse macchine. Le tecnologie più comuni sono quattro. 1. Comunicazione cross process. 2. Messaggi su code. 3. WS. 4. REST/POX (Plain Old Xml). SOA definisce quattro concetti. 1. Definizione di confini espliciti: i limiti entro i quali un servizio deve muoversi e funzionare devono essere ben definiti; è importante distinguere l’accesso a oggetti locali dall’accesso ad un servizio. 2. I servizi sono autonomi: ogni servizio dev’essere capace di funzionare senza dipendere da altri servizi. 3. Un servizio condivide schemi e contratti e non classi e interfacce: ogni servizio accetta uno schema di dati, costituendo così architetture tra loro disaccoppiate che rimangono stabili nel tempo. 4. Compatibilità dei servizi basata su policy: ogni servizio deve stabilire esplicite regole per il suo utilizzo in forma leggibile, in modo da consentire la separazione tra il servizio e le modalità di accesso al servizio. La comunicazione fra elementi che utilizzano protocolli e modalità non omogenee rappresenta, in un mondo dove il networking assume una rilevanza importante e dove la programmazione diventa sempre maggiormente distribuita, un problema di dimensioni colossali. TPSIT 5 um 82 di 434 COMUNICAZIONE CROSS PROCESS INTRODUZIONE Permette la comunicazione di due unità che si possono trovare all’interno dello stesso processo e condividono quindi la stessa area di memoria o vivono all’interno della stessa macchina. Il .NET Framework Remoting è la soluzione Microsoft per la comunicazione cross process, consente sia di richiamare funzioni esposte da “processi” che girano su macchine remote ma in modo del tutto trasparente, sia di gestire chiamate a processi differenti che girano sulla stessa macchina. È una soluzione proprietaria e non aderisce ad alcuno degli standard d’interoperabilità di protocolli remoti. È una tecnologia legacy mantenuta per una questione di compatibilità con le applicazioni esistenti di versioni precedenti e non è consigliato per il nuovo sviluppo. Le applicazioni distribuite devono essere sviluppate utilizzando WCF. Rappresenta una validissima soluzione per chi voglia realizzare un’architettura multi-tier, anche in senso fisico e non solo con diversi strati di classi che girano nello stesso eseguibile ma non voglia investire risorse per attrezzare server con IIS per far girare i WS. I client eseguono chiamate ai metodi sugli oggetti remoti inviando messaggi al dominio dell’applicazione remota. Ciò è portato a termine da un set di oggetti canale. Il dominio dell’applicazione client contiene un canale client e il dominio dell’applicazione remota contiene un canale remoto. Ogni canale è composto di una serie di sink di canale collegati insieme in una catena. Prima dell’invio o dopo la ricezione di un messaggio, i canali inviano ciascun messaggio lungo una catena di oggetti sink di canale. La catena di sink contiene sink necessari per le funzionalità di base del canale, quali sink del formattatore, di trasporto o del generatore di stack ma è possibile personalizzare la TPSIT 5 um 83 di 434 catena di sink di canale per l’esecuzione di attività speciali con un messaggio o un flusso. La catena di sink di canale elabora qualsiasi messaggio inviato a o da un dominio di applicazione. Un sink di canale ha accesso al messaggio elaborato e l’elaborazione successiva utilizza il messaggio restituito al sistema dopo averlo elaborato. Si tratta del momento più adatto per implementare un servizio di registrazione o un qualsiasi tipo di filtro. Ogni sink di canale elabora il flusso e quindi passa il flusso al prossimo sink di canale. Ciò vuol dire che i sink precedenti o successivi ad uno specifico sink devono sapere come gestire il flusso ricevuto. Prima che i messaggi tra le applicazioni remote siano inviati sui canali sono codificati nei seguenti modi. In binario per le applicazioni critiche. In XML dove è essenziale interoperabilità, i messaggi XML utilizzano sempre SOAP (Simple Object Access Protocol). I servizi di remoting offerti dal .NET Framework nascondono al programmatore la complessità dei metodi di chiamata remoti. Quando un client attiva un oggetto remoto ne riceve un proxy con il quale interagisce. Prima si registra un canale di comunicazione HTTP o TCP, poi diventa possibile registrare gli oggetti remoti. Per la registrazione di un oggetto remoto servono i seguenti dati. 1. Il nome del tipo di oggetto remoto. 2. L’URI che i client utilizzeranno per individuare l’oggetto. 3. La modalità oggetto richiesta per l’attivazione da server sono due. 3.1. SigleCall: crea un’istanza di classe per ogni chiamata del client, anche se le chiamate provengono dallo stesso client; l’invocazione successiva, sarà sempre servita da una differente istanza del server, anche se la precedente non è stata ancora riciclata dal sistema. 3.2. Singleton: non presenta mai più di un’istanza contemporaneamente, se un’istanza è già esistente la richiesta è soddisfatta da quell’istanza, se l’istanza non esiste, alla prima richiesta è creata dal server e tutte le successive richieste sono soddisfatte da quell’istanza. La differenza è sostanziale e dev’essere tenuta in conto quando si decide d’implementare un’architettura distribuita nelle proprie applicazioni usando .NET Remoting. Per esempio, si supponga di voler istanziare l’oggetto Server seguente. Server remoteObject = new Server(); Dopo questa istruzione l’oggetto Server non è ancora stato contattato per cui nessuna istanza sul server è creata né tanto meno è controllato che il server sia in grado di rispondere, soltanto all’invocazione del metodo la richiesta giunge effettivamente al server. DataSet ds = remoteObject.GetData(); Se l’oggetto Server ha una proprietà Name di tipo R/W, si osservi il codice in tabella. Istruzione Versione Locale Versione remota SingleCall Versione remota Singleton restituisce "Andrea" restituisce null restituisce "Andrea" Server firstObject = new Server(); firstObject.Name = "Andrea"; MessageBox.Show(firstObject.Name); TPSIT 5 um 84 di 434 Server secondObject = new Server(); MessageBox.Show(secondObject.Name); restituisce null restituisce null restituisce "Andrea" secondObject.Name = "Sperelli"; MessageBox.Show(secondObject.Name); MessageBox.Show(firstObject.Name); restituisce "Sperelli" restituisce "Andrea" restituisce "Sperelli" restituisce restituisce null "Sperelli" restituisce null Il comportamento di tale codice, con l’oggetto Server che gira in remoting, è completamente diverso rispetto al suo omologo in locale e anche nella versione SingleCall e singleton ci sono differenze consistenti. I vantaggi del Singleton sono evidenti in termini di semplificazione della programmazione. Esso, infatti, consente di mantenere gli stati interni da una chiamata all’altra (stateful) e, inoltre, fa da memoria centrale per tutte le richieste. Con un singleton, infatti, si possono conservare tutte le informazioni di tutti i client e avere sempre un’idea precisa di quanto sta accadendo nel S/W perché ogni richiesta passa dal singleton e questo è in grado di tenerne traccia e di ricordarsene. Lo svantaggio è evidente: se il singleton va giù tutti i client vanno giù. Il SingleCall, d’altro canto, impone una programmazione senza stati (stateless) in cui tutto ciò che serve a rispondere ad una richiesta di un client dev’essere passato come parametri del client stesso o dev’essere reperito dal server in altre forme, per esempio da un DB e non si può programmare sperando che sia sempre la stessa istanza a rispondere e a conservare l’impostazione della chiamata precedente. Questo modello in compenso fornisce una scalabilità e una robustezza ben maggiori. PROGRAMMAZIONE File CLIENT.CS using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; TPSIT 5 um 85 di 434 using Remote; namespace Client { public class ClientProcess { [MTAThread] public static void Main(string[] args) { HttpChannel channel = new HttpChannel(); ChannelServices.RegisterChannel(channel, false); // registra la classe remota RemotingConfiguration.RegisterWellKnownClientType( Type.GetType("Remote.ServiceClass, remote"),"http://localhost:8080/object1uri"); ServiceClass object1 = new ServiceClass(); try { Console.WriteLine("ServerTime: " + object1.GetServerTime()); } catch (Exception ex) { Console.WriteLine("Exception of type: " + ex.ToString() + " occurred."); Console.WriteLine("Details: " + ex.Message); } } } } File REMOTE.CS using System; namespace Remote { public class ServiceClass : MarshalByRefObject { private DateTime m_startTime; public ServiceClass() { Console.WriteLine("ServiceClass creata senza il costruttore. Istanza hash: " + GetHashCode().ToString()); m_startTime = DateTime.Now; } ~ServiceClass() { Console.WriteLine("Collezione dopo: " + (new TimeSpan(DateTime.Now.Ticks m_startTime.Ticks)).ToString() + " secondi."); } public DateTime GetServerTime() { Console.WriteLine("Tempo richiesto dal client."); return DateTime.Now; } public int InstanceHash { get { return GetHashCode(); } } } } File SERVER.CS using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; using Remote; namespace Server { class Server { static void Main(string[] args) TPSIT 5 um 86 di 434 { HttpChannel channel = new HttpChannel(8080); ChannelServices.RegisterChannel(channel, false); ServiceClass object1 = new ServiceClass(); // crea una singola istanza di ServiceClass // tutti i client la usano ObjRef ref1 = RemotingServices.Marshal(object1, "object1uri"); Console.WriteLine("ObjRef.URI: " + ref1.URI); Console.WriteLine("Running. Premi <INVIO> per terminare la pubblicazione."); Console.ReadLine(); RemotingServices.Disconnect(object1); Console.WriteLine(); Console.WriteLine("Disconnessione dell'oggetto. Client adesso riceve una RemotingException."); Console.WriteLine("Premi <INVIO> per non registrare il canale."); Console.ReadLine(); // in questo punto l'oggetto ServerClass esiste ChannelServices.UnregisterChannel(channel); Console.WriteLine("Canale non registrato. Client adesso riceve una WebException."); Console.WriteLine("L'applicazione host e' ancora in running. Premi <INVIO> per terminare il processo."); Console.ReadLine(); GC.Collect(); GC.WaitForPendingFinalizers(); } } } TPSIT 5 um 87 di 434 MESSAGGI SU CODE INTRODUZIONE L’idea che ne è alla base consiste di due attori. 1. Il mittente mette in una coda i messaggi da processare. 2. Il destinatario recupera i messaggi, smaltendo la coda. Aumentando il numero di attori che processano la coda, si può aumentare la velocità di smaltimento dei messaggi e mantenere costanti le prestazioni anche all’aumentare del carico di lavoro: scalabilità. A differenza delle comunicazioni basate su socket o su altri protocolli di rete, il mittente e il destinatario possono essere attivi in momenti differenti. Infatti, nel momento in cui i messaggi sono spediti, il destinatario può essere offline. In un secondo tempo, quando il destinatario ritorna online, riceverà i messaggi senza ulteriori interventi da parte del mittente. I messaggi su code sono usati sia in ambienti “alwayson” sia in ambienti scollegati. 1° Caso Un agente si reca presso i clienti e carica i dati sul client che invia un messaggio per ogni lettura alla coda di messaggi del sistema locale; quando l’agente ritorna in azienda e si collega col dispositivo alla rete, tutti i messaggi in coda sono automaticamente trasferiti dal sistema della coda client alla coda di destinazione per la successiva elaborazione. 2° Caso In un’azienda si vuole ridurre il sovraccarico di richieste per un server, allora ogni richiesta è accodata e le può essere associata una priorità, in modo da essere elaborata di notte. 3° Caso Sito web per l’acquisto online, se la comunicazione con l’istituto di credito è interrotta, non è possibile dire all’utente di riprovare più tardi, allora si accoda la richiesta e s’informa l’utente che il suo acquisto è in fase di “processing” e che riceverà una comunicazione sull’esito della transazione. La soluzione è progettare una tabella in un DB locale dove memorizzare le informazioni che saranno spedite ad un’applicazione remota al momento in cui c’è connettività. Il programmatore deve gestire la frequenza d’invio, l’eventuale priorità con cui spedire i record, le politiche transazionali con cui inviare il messaggio, la cancellazione del record dalla tabella locale quando è arrivato a destinazione. TPSIT 5 um 88 di 434 I vantaggi derivanti dall’utilizzo dei messaggi su code sono i seguenti. Applicazioni a basso accoppiamento L’applicazione client può eseguire gli invii senza necessariamente sapere se l’applicazione destinataria è disponibile a processare i messaggi; questo riduce notevolmente la dipendenza tra le due applicazioni. Failure isolation Le applicazioni che inviano o ricevono i messaggi verso una coda possono generare eccezioni senza influenzare l’altra applicazione; se, ad esempio, il destinatario fallisce, il mittente può continuare a inviare i messaggi senza bloccarsi; una volta riattivato, il destinatario può tranquillamente processare i restanti messaggi. Bilanciamento del livello di carico L’applicazione mittente può essere più veloce e performante dell’applicazione destinatario. Le code consentono di livellare questa differenza. Operazioni disconnesse L’invio, la ricezione e l’elaborazione dei messaggi sono operazioni che possono funzionare correttamente anche nelle fasi di lavoro disconnesso, consentendo ad esempio di continuare a inviare messaggi anche se l’endpoint di riferimento non è al momento disponibile. MSMQ (MICROSOFT MESSAGE QUEUE) È stato progettato come servizio per lo scambio di messaggi tra applicazioni. Gestire il recapito. Gestire il routing (instradamento). Gestire la sicurezza durante il trasporto. Gestire la comunicazione tra processi: IPC sia per applicazioni installate sullo stesso PC sia per applicazioni sparse per i nodi della rete. Fornire le interfacce di programmazione per accedere in R/W alle code. Far comunicare macchine remote, anche su Internet, con il protocollo HTTP/HTTPS. Installazione e configurazione Fare clic su Pannello di controllo/Programmi/Attivazione o disattivazione delle funzionalità Windows/Microsoft Message Queue (MSMQ) Server. Una volta installati i componenti necessari, dev’essere avviato il relativo servizio TPSIT 5 um 89 di 434 Accodamento messaggi. Code Secondo l’utilizzo, è possibile specificare un tipo diverso di coda. Pubbliche: sono accessibili da tutti i siti connessi in rete senza conoscere il PC su cui si trovano perché sfruttano Active Directory, MSMQ dev’essere configurato con questa modalità. Private: sono presenti solo sul PC su cui sono configurate, è possibile accedere conoscendo il percorso completo che punta alla coda o l’etichetta. Journal: sono utilizzate per tenere traccia dei messaggi inviati e ricevuti. Di amministrazione: hanno la funzione di notificare l’avvenuta ricezione del messaggio al mittente. Di sistema: hanno diversi scopi tra cui la gestione dei messaggi transazionali. Deadletter: mantengono i messaggi scaduti che non sono stati consegnati nel tempo prestabilito o che non possono essere trattati correttamente dal ricevente. Di report: possono essere usate per il tracciamento dei percorsi effettuati dai messaggi o per eventuali messaggi di prova. Active directory È un servizio di directory che fornisce un’infrastruttura centralizzata e gerarchica per la memorizzazione d’informazioni sugli utenti, sulle risorse di rete e sui servizi. Per il raggruppamento d’informazioni elementari, si basa sui concetti di dominio, alberi e foreste, ogni informazione elementare è vista come un oggetto. Per l’accesso in lettura e scrittura di oggetti direttamente nella base dati del servizio .NET mette a disposizione il namespace System.DirecoryServices. La prima cosa da fare è la creazione della coda, due possibilità. 1. Codice. MessageQueue queue = null; string nomecoda = @".\private$\codaprivata"; if (MessageQueue.Exists(nomecoda)) queue = new MessageQueue(nomecoda); else queue = MessageQueue.Create(nomecoda, false); 2. SO. Per creare una coda servono i privilegi di amministratore. Fare clic su Start/Tutti i programmi/Strumenti di amministrazione/Gestione computer. Selezionare Servizi e Applicazioni/Accodamento messaggi/Code private, nella finestra che si apre sarà possibile creare nuove code, eliminare quelle esistenti e controllare l’elenco dei messaggi. Con il pulsante destro del mouse, scegliere Nuova e digitare il nome per creare la coda. TPSIT 5 um 90 di 434 Le code possono essere aperte in due modi. 1. Nome di percorso: è dato dal nome del server e dal nome della coda. 2. Nome di formato: è associato automaticamente dal sistema alla creazione della coda ed è rappresentato da un GUID (Global Unique Identifier). Esempi. Coda pubblica: nome_PC\nomecoda. Coda privata: nome_PC\private$\ nomecoda. Coda diario: nome_PC\Journals$. Coda deadletter: nome_PC\DeadLetters$. Programmazione MSMQ si colloca in una realtà distribuita in cui le comunicazioni fra i vari componenti S/W che costituiscono l’applicazione girano su macchine diverse che possono essere connesse da linee non sempre attive, oppure quando è necessario far comunicare applicazioni che girano su SO differenti e/o non si vuole che problemi di rete possano fermare o rallentare l’esecuzione. Le comunicazioni asincrone risolvono il problema delle performance, infatti, il tempo necessario per scrivere un messaggio in una coda può essere considerato quasi uguale a zero, mentre attendere una comunicazione sincrona con un’applicazione più lenta, su una connessione di rete lenta, significa rallentare anche l’applicazione chiamante. In una comunicazione sincrona le richieste sono processate nell’ordine in cui sono inviate dal chiamante, mentre con un sistema di messaggistica è possibile assegnare una priorità a ogni messaggio. Un crash nella comunicazione sincrona causa un rollback, un annullamento di tutte le operazioni effettuate, mentre MSMQ può eseguire un numero di retry impostabile dall’applicazione. È possibile definire il tempo massimo in cui il messaggio può raggiungere la coda di destinazione e un tempo massimo in cui il messaggio dev’essere letto. In caso di timeout è il servizio MSMQ che s’incarica d’inserire un messaggio amministrativo di mancato recapito o mancata lettura che è possibile recuperare e interpretare. Nel caso in cui un certo numero di client eseguono richieste sincrone di aggiornamento TPSIT 5 um 91 di 434 dati occorre definire una politica di lock sulle risorse rallentando l’operazione di aggiornamento nel caso in cui la risorsa sia bloccata. Una coda non ha alcun problema di concorrenza perché i messaggi sono inseriti e accodati dalle varie applicazioni del mittente. L’applicazione del ricevente processerà i messaggi uno ad uno e quindi eseguirà degli aggiornamenti “sequenziali” sulle risorse. Se l’applicazione del ricevente gira in multithread facendo riemergere il problema dei lock sulla risorsa finale, però il problema è spostato dall’applicazione che l’utente usa all’applicazione del ricevente, liberando così il front-end dalle problematiche descritte. Un’applicazione che invia un messaggio in coda deve referenziare la libreria seguente. using System.Messaging; Deve creare un oggetto MessageQueue che rappresenta la coda, è il wrapper che gestisce l’interazione con MSMQ e permette l’invio, la ricezione e l’anteprima dei messaggi. Deve creare un oggetto Message che rappresenta il messaggio, contenuto e formato, da spedire nella coda. Un messaggio è composto da un body contenente i dati da trasmettere e da un header che lo descrive. Il messaggio può includere altre informazioni riguardanti il mittente, il valore di priorità, l’ID (IDdentificatore) della transazione cui è associato e l’eventuale configurazione per la scadenza di validità. Il corpo può contenere qualsiasi tipo d’informazione e prevede appositi formattatori che serializzano il contenuto del messaggio, implementano l’interfaccia ImessageFormatter e sono previste tre classi. 1. XMLMessageFormatter: formatta il messaggio in binario in XML, default. 2. BinaryMessageFormatter: formatta il messaggio in binario. 3. ActiveMessageFormatter: formatta il messaggio in binario e dà la possibilità ai messaggi di essere letti da oggetti COM. Esistono due versioni di consegna dei messaggi. 1. Recoverable: garantisce la consegna del messaggio anche nel momento in cui ci sono problemi di rete o nel PC, in questo caso i messaggi sono memorizzati come file nella cartella <WINDIR>\SYSTEM32\MSMQ\STORAGE. 2. Express: variante più veloce ma insicura poiché usa solo la memoria per immagazzinare i messaggi prima del recapito. Per accedere ad una coda privata occorre definire un oggetto di tipo MessageQueue e alla sua istanza bisogna passare il nome del server, oppure anteporre un punto indicante il PC locale e il nome della coda privata. MessageQueue queue = new MessageQueue(@".\private$\codaprivata "); Il nome di formato è obbligatorio in un ambiente disconnesso ed è utilizzato per ottenere il nome del server MSMQ tramite Active Directory. Se il nome della coda è conosciuto, non è necessario compiere ricerche e si può accedere direttamente. Viceversa, la ricerca può essere fatta cercando l’etichetta, la categoria o il nome di formato tramite i seguenti metodi. GetPublicQueuesByLabel. GetPublicQueuesByCategory. GetPublicQueuesByMachine. GetPublicQueues: restituisce tutte le code pubbliche del dominio in una struttura ad array. TPSIT 5 um 92 di 434 Per spedire un messaggio, si usa il metodo seguente. queue.Send("Corpo del messaggio","Ttitolo del messaggio"); Per leggere da una coda esistono varie tecniche. Se si desidera leggere e rimuovere solo il primo messaggio dalla coda, si usa il metodo seguente. Message message = queue.Receive(); Se non si desidera rimuovere il messaggio dalla coda, si usa il metodo seguente. Message message = queue.Peek(); Esistono delle varianti che permettono di leggere/rimuovere uno specifico messaggio in base al suo ID. Message message = queue.PeekById(msgID); Message message = queue.ReceiveById(msgID); Il metodo seguente legge tutti i messaggi della coda in una struttura array. Message[] messages = queue.GetAllMessages(); foreach (Message message in messages) { //elaborazione del messaggio } Un’alternativa è tramite l’uso dell’enumeratore seguente. MessageEnumerator enumerator = queue.GetMessageEnumerator2(); while (enumerator.MoveNext()) enumerator.RemoveCurrent(); Si può avviare la lettura o la ricezione in modalità asincrona tramite i metodi seguenti. queue.BeginPeek(); queue.BeginReceive(); E completare l’operazione specificata con i rispettivi. queue.EndPeek(); queue.EndReceive(); In modalità asincrona è necessario definire un gestore per l’evento scatenato al termine dell’operazione associato alla proprietà PeekCompleted. queue.PeekCompleted += new PeekCompleted EventHandler(MyPeekCompletedEventHandler); … void MyPeekCompletedEventHandler (Object source,PeekCompletedEventArgs asyncResult) { // connessione alla coda MessageQueue queue = (MessageQueue)source; TPSIT 5 um 93 di 434 // fine operazione asincrona Message message = queue.EndPeek(asyncResult.AsyncResult); // eventuale elaborazione del messaggio Console.WriteLine("Message: " + (string) message.Body); // riavvio operazione asincrona queue.BeginPeek(); return; } In molti scenari ha senso suddividere le tipologie di messaggio da spedire/ricevere secondo diversi criteri. Per esempio, per i messaggi relativi a Ordini da processare e per i messaggi relativi a Spedizioni da effettuare bisogna creare due code diverse in modo da rendere indipendenti i messaggi. L’applicazione che deve analizzare il contenuto del messaggio aprirà la coda Ordini se deve processare Ordini o la coda Spedizioni per analizzare le spedizioni da effettuare. Per esempio, differenziare fra ordini urgenti e meno urgenti. La posizione che il messaggio assume nella coda è di tipo FIFO (First In First Out). La priorità è una delle proprietà impostabili sul singolo messaggio da spedire: è l’applicazione del mittente che imposta questa proprietà. È possibile dare una priorità ai messaggi con l’enumeratore seguente. enum System.Messaging.MessagePriority Per impostare la priorità al messaggio; le opzioni disponibili sono le seguenti. Highest, VeryHigh, High, AboveNormal, Normal (default), Low,VeryLow, Lowest. I messaggi nella coda di arrivo saranno tolti in base a tale priorità. Un messaggio non Recoverable, il default, è tenuto in RAM dalla macchina che lo deve inviare verso la coda di destinazione, se la macchina va in crash, oppure il servizio MSMQ è riavviato, i messaggi “normali” sono persi. Utile per esempio in campo industriale: le macchine di produzione inviano il loro stato ad applicazioni di monitoraggio che tramite indicatori di livello, grafici, mostrano queste informazioni ad un operatore a video. Se la connettività fra le macchine è interrotta o è riavviato il PC della macchina industriale è inutile tenere i messaggi informativi perché non avrebbero più significato. In tutti gli altri casi in cui è importante che tutti i messaggi spediti siano recapitati a destinazione, bisogna impostare la proprietà Recoverable a true per fare in modo che le macchine coinvolte nella spedizione del messaggio verso la coda di destinazione appoggino i messaggi stessi su disco. Un’applicazione che monitorizza le quotazioni di borsa invia al DB che poi alimenta il sito pubblico consultabile dai clienti aggiornando le quotazioni ogni 10 minuti. La proprietà TimeToReachQueue indica il tempo, in secondi,’ che il messaggio può impiegare per raggiungere la coda di destinazione: in pratica il tempo valido entro cui il messaggio ha senso che sia spedito nella coda di destinazione. Utile per non appesantire il sistema con la trasmissione di messaggi che non hanno senso. Per ottenere una risposta da parte del destinatario sull’esito dell’operazione in base al contenuto del messaggio, occorre un controllo sui messaggi spediti, i messaggi arrivati e/o letti e i messaggi non spediti per timeout o non letti entro il timeout. Tutte queste opzioni sono a carico di MSMQ, però non forniscono informazioni sull’esito dell’operazione fatta dal destinatario sul messaggio stesso. TPSIT 5 um 94 di 434 La prima opzione per tracciare tutti i messaggi spediti è abilitare il journal dov’è tenuta una copia di tutti i messaggi recapitati tramite la proprietà UseJournalQueue, oppure definire il journaling a livello di coda dalla maschera delle proprietà, inoltre è possibile limitare la dimensione in KB. Inoltre, MSMQ ha una coda di sistema denominata MESSAGGI JOURNAL dove tenere una copia di tutti i messaggi transitati dalla macchina. La seconda opzione per tracciare tutti i messaggi spediti è abilitare, con la proprietà UseDeadLetterQueue, l’utilizzo della coda di sistema deadletter dove recapitare tutti i messaggi impossibili da spedire. Dall’interfaccia di amministrazione o da codice è possibile aprire tale coda per ispezionarne il contenuto e quindi sapere quali e/o quanti messaggi non sono stati recapitati. Volendo avere più controllo sull’esito dell’invio dei messaggi, si può impostare sul singolo messaggio le proprietà seguenti. AcknoledgeType. AdministrationQueue: indica una coda, definita dall’applicazione, dove recapitare l’esito d’invio di ogni messaggio, la coda deve esistere. L’enum System.Messaging.AcknowledgeTypes consente d’indicare. 1. FullReachQueue. 2. FullReceive indica se il messaggio è arrivato a destinazione ed è stato letto dall’applicazione del destinatario. 3. NegativeReceive: indica se il messaggio non è stato ricevuto. 4. None. 5. NotAcknowledgeReachQueue. 6. NotAcknowledgeReceive. 7. PositiveArrival. 8. PositveReceive. Il messaggio recuperato con Receive espone la proprietà Acknowledgement con la quale è possibile capire cosa è successo al messaggio. ReachQueue: indica che il messaggio ha raggiunto la coda di destinazione. ReachQueueTimeout: indica che è scaduto il timeout indicato per raggiungere la coda e quindi il messaggio non è stato recapitato. Receive: indica che il messaggio ha avuto una ricezione positiva da parte dell’applicazione del destinatario. L’applicazione del mittente indica con la proprietà ResponseQueue, dove vorrebbe che fossero recapitate le risposte che ha compiuto il destinatario in base al contenuto del messaggio, sull’esito dell’operazione. Di norma, la coda di risposta si trova sulla stessa macchina dell’applicazione del mittente. L’applicazione del destinatario invierà un messaggio di risposta sulla coda indicata dal client. TPSIT 5 um 95 di 434 File DESTINATARIO.CS using System; using System.Messaging; namespace Destinatario { class Destinatario { static void Main(string[] args) { try { using (MessageQueue mq = new MessageQueue(@".\private$\codaprivata")) // il messaggio, per default, è serializzato in XML all’interno della coda { mq.Formatter = new XmlMessageFormatter(new Type[] { typeof(String) }); // ricezione del messaggio ed eliminazione dalla coda Message messageReceived = mq.Receive(); // tipo di contenuto del messaggio String contenuto = (String) messageReceived.Body; // processo il contenuto del messaggio try { using (MessageQueue risposta = new MessageQueue(@".\private$\rispostedestinatario")) { Message messaggio = new Message(); messaggio.Body = "Risposta negativa al messaggio " + messageReceived.Id; risposta.Send(messaggio); } Console.WriteLine("Inviato!"); } catch (MessageQueueException ex) { Console.WriteLine("Errore invio risposta!" + ex.ToString()); } Console.WriteLine("Messaggio ricevuto: " +contenuto); } } catch (MessageQueueException ex) { Console.WriteLine("Errore ricezione messaggio!" + ex.ToString()); } Console.ReadKey(); } } } TPSIT 5 um 96 di 434 File MITTENTE.CS using System; using System.Messaging; namespace esempio { class Mittente { static void Main(string[] args) { try { // Dispose dell'oggetto che rappresenta la coda using (MessageQueue mq = new MessageQueue(@".\private$\codaprivata")) { Message messaggio = new Message(); // contenuto del messaggio messaggio.Body = "Ciao dal mittente."; // imposta la priorità messaggio.Priority = MessagePriority.High; // messaggi salvati su disco messaggio.Recoverable = true; // tempo in secondi entro cui spedire il messaggio messaggio.TimeToReachQueue = TimeSpan.FromMinutes(10); // recapita un messaggio di acknoledgement messaggio.AdministrationQueue = new MessageQueue(@".\private$\esitomessaggi"); // si vuole sapere tutto messaggio.AcknowledgeType = AcknowledgeTypes.FullReceive; // risposte del destinatario messaggio.ResponseQueue = new MessageQueue(@".\private$\rispostedestinatario"); mq.Send(messaggio); } Console.WriteLine("Messaggio inviato!"); } // intercetta gli errori durante l’invio del messaggio catch (MessageQueueException ex) { Console.WriteLine("Errore invio messaggio!" + ex.ToString()); } Console.ReadKey(); } } } TPSIT 5 um 97 di 434 TPSIT 5 um 98 di 434 Progettare un’applicazione per lo scambio di messaggi. Esplora server, la console di gestione del server per Visual Studio, consente di esplorare le code esistenti sui PC della rete cui si ha accesso. TPSIT 5 um 99 di 434 MQTT (MESSAGE QUEUE TELEMETRY TRANSPORT) Il protocollo ha le seguenti caratteristiche. È molto snello, in altre parole i pacchetti possono avere una dimensione di pochi byte. È orientato agli eventi e ai messaggi. Permette ai dispositivi di comunicare in maniera asincrona fra loro, anche all’interno di una rete non performante e poco affidabile. È payload agnostic, in altre parole non entra nel merito del contenuto dei pacchetti, quindi può essere usato come trasporto per qualsiasi tipo di dato, sarà compito del livello applicazione interpretarne il contenuto. Il suo principale utilizzo è nell’ambito della comunicazione M2M (Machine-To-Machine) e dell’Internet of Things, in pratica in quei sistemi in cui ci sono esclusivamente macchine che si scambiano dei dati senza l’intervento umano, per poi giungere ad un server per un’elaborazione massiva degli stessi. Il protocollo è di tipo mittente/destinatario perché ciascun client esprime l’interesse a ricevere determinate tipologie di messaggi, così com’è in grado di pubblicarne altri con determinati tipi di contenuti. Ogni messaggio è formato da un topic (argomento) che è alla base del meccanismo di sottoscrizione così come a quello di pubblicazione; ha una struttura simile ad un URI e, quindi, è caratterizzato da una gerarchia i cui livelli sono separati da (/). Un client può essere interessato ad uno specifico argomento, per cui sottoscrive la ricezione di tutti i messaggi per uno specifico topic, così come può pubblicare messaggi appartenenti ad uno specifico topic, non necessariamente lo stesso. Le sottoscrizioni possono essere anche gestite in modo tale che, se il client si disconnette, il broker continua ad immagazzinare i messaggi per quel client, retain message, per poi trasferirli nel momento in cui esso ritornerà online. Al momento della connessione, il client può fornire al broker un messaggio di testamento, associato ad un certo topic che trasmetterà agli interessati nel caso in cui la connessione con il client dovesse essere interrotta. La persistenza della connessione tra client e broker è garantita da un messaggio di keepalive che il client invia periodicamente, l’intervallo di tempo è indicato al broker al momento della connessione. La gestione della coda dei messaggi e dello smistamento da parte del broker può essere specificato per ogni singolo messaggio un livello di QoS (Quality of Service) tra i seguenti. At most once: letteralmente al massimo uno, il recapito di un messaggio è lasciato al protocollo sottostante TCP/IP, per cui ci possono essere messaggi persi o duplicati. At least once: letteralmente almeno uno, è garantita la sicurezza di recapito del messaggio ma possono comunque verificarsi casi di duplicazione dello stesso. Exactly once: letteralmente esattamente uno, è garantito che un messaggio arrivi una e una sola volta ai client che sono sottoscritti al topic di appartenenza. Architettura TPSIT 5 434 um 100 di Broker I client e il server non comunicano mai in maniera diretta ma passano attraverso una figura centrale, il broker, il cui compito è di accettare le connessioni dei client e garantire lo smistamento dei messaggi. Il broker può essere scelto sul sito http://mqtt.org, per esempio IBM WebSphere MQ Telemetry, Mosquito e RSMB (Really Small Message Broker). Avviare il broker, da CLI, con il comando seguente. Client Gli utenti possono registrarsi, loggarsi, chiedere le amicizie e scambiarsi messaggi. Comunicano con un broker, per esempio usando la libreria M2MQTT che funziona con il .NET Framework, il .NET Compact Framework e .il NET Micro Framework. Server È un client per il broker ma è un vero e proprio server per i client. Gestisce la registrazione e l’autenticazione degli utenti. Il server è implementato nella classe MqttMessengerServer che fa uso di un’istanza della classe MqttClient per comunicare con i client attraverso il broker. Il metodo Connect stabilisce la connessione ed esegue la sottoscrizione ai topic definiti nella classe MqttMessengerTopic. Avviare il server, da CLI, con il comando seguente. TPSIT 5 434 um 101 di L’operazione di richiesta di una nuova amicizia prevede i seguenti passi. 1. Il client invia al server la richiesta con il metodo SendFriendshipRequest che pubblica il messaggio sul topic MqttMessengerTopic.FRIENDSHIP_REQ. 2. Il server, ricevuta la richiesta, verifica l’esistenza dell’utente e gira la richiesta pubblicandola sul topic MqttMessengerTopic.FRIENDSHIP_REQ_USER. 3. Il client destinazione, ricevuto il messaggio di richiesta di una nuova amicizia, solleva l’evento FriendshipRequested al livello superiore; è compito dell’utente, tramite l’UI, dare conferma o meno; in caso di amicizia accettata, il client invia la risposta con il metodo SendFriendshipResponse che pubblica il messaggio sul topic MqttMessengerTopic.FRIENDSHIP_RESP. 4. Il server riceve l’esito sul topic suddetto e la rigira al client mittente sul topic MqttMessengerTopic.FRIENDSHIP_RESP_USER, inoltre salva la nuova amicizia con il datasource e rinvia la lista degli amici aggiornata ai due client coinvolti. 5. Il client mittente, una volta ricevuto l’esito, solleva l’evento FriendshipResponse in modo che il livello di UI possa essere avvisato della nuova amicizia. Lo scambio dei messaggi non coinvolge il server ma è gestito da un canale diretto tra i due client. Ciascun client è registrato al topic MqttMessengerTopic.MESSAGE_CHANNE che riceve i messaggi trasmessi da un qualsiasi altro client. L’invio è effettuato con il metodo SendMessage che esegue la pubblicazione su tale topic, mentre la ricezione è garantita dalla sottoscrizione al medesimo topic. L’informazione del client mittente è trasportata all’interno del messaggio rappresentato dalla classe MessageMsg. La ricezione di un messaggio scatena l’evento MessageReceived per l’UI che visualizza il contenuto. Avviare due client. TPSIT 5 434 um 102 di WEB MESSAGE API - HTML 5.0 Con la progettazione di applicazioni web moderne che richiedono sempre più la possibilità di utilizzare servizi esterni forniti da altri provider, per esempio le mappe, è nata la necessità di superare la limitazione imposta dal rigido sistema same origin policy. Le Web Message implementano un sistema di messaggistica che consente lo scambio di dati, tra documenti appartenenti a domini differenti, in modo sicuro e affidabile. Il procedimento per inviare e ricevere i messaggi consente di far comunicare in modo bidirezionale due documenti caricati in pagine web provenienti da differenti origini, dove per origine s’intende l’indirizzo da cui proviene il messaggio. Tale origine è composta da uno schema, il nome di un host e una porta e non tiene in considerazione gli eventuali percorsi. Per esempio, una pagina servita dall’indirizzo https://127.0.0.1:99 ha un’origine differente da una pagina servita dall’indirizzo http://127.0.0.1:99 perché differisce nella componente schema HTTPS e HTTP, mentre una pagina servita dall’indirizzo TPSIT 5 434 um 103 di http://127.0.0.1:99/check.html non ha un’origine differente da una servita dall’indirizzo http://127.0.0.1:99/widget/chat.html perché i percorsi non sono considerati nella valutazione dell’equivalenza delle origini. La funzione seguente, invia un messaggio alla window da cui è invocata e l’evento message consente di riceverlo e processarlo. window.postMessage(message, targetOrigin [, ports ]) Dove. message rappresenta la stringa di messaggio da inviare. targetOrigin rappresenta una stringa contenente l’URL del documento cui inviare il messaggio. ports opzionale, rappresenta un array di porte. L’evento message, di tipo MessageEvent, è assegnabile ad un oggetto e ha le seguenti proprietà. data Di tipo any, ritorna il dato del messaggio. origin Di tipo DOMString, ritorna l’URL originatore del messaggio. lastEventId Di tipo DOMString, ritorna il valore dell’ultimo ID, assegnato per un oggetto di tipo EventSource che è utilizzato per inoltrare messaggi, in modalità push, da un server remoto ad una pagina web; questa modalità è definita Server Sent Events. source Di tipo WindowProxy, ritorna la window originatrice del messaggio. ports Di tipo MessagePortArray, ritorna un array di oggetti di tipo MessagePort. La sicurezza contro attacchi di tipo cross-site scripting è garantita in primo luogo dal browser, poiché quando si utilizza la funzione postMessage esso fa un primo controllo che la window da cui s’invia il messaggio abbia la stessa origine di quella specificata dal parametro dove s’inserisce il target destinatario del messaggio e, infatti, in caso di discordanza il messaggio non è inviato. Inoltre, all’atto della ricezione del messaggio, l’identificazione dell’originatore è inclusa come parte del messaggio dal browser stesso, in modo che non possa essere falsificata: spoofing. In secondo luogo la sicurezza può essere ulteriormente garantita dal programmatore che può verificare se l’URL originatore è tra quelle inserite in un’eventuale white-list, controllando la proprietà origin dell’oggetto evento passato al gestore dell’evento message e facendo attenzione a come valutare la stringa del messaggio. Infatti, in quest’ultimo caso, bisogna fare attenzione a non usare proprietà o funzioni che parserizzano in modo non sicuro il dato del messaggio come, ad esempio, fanno la proprietà innerHTML o la funzione eval. Channel messaging La Web Message API supporta, oltre al meccanismo di comunicazione appena descritto, TPSIT 5 434 um 104 di anche channel messaging, dove i messaggi sono inoltrati e ricevuti avvalendosi di canali, creati come oggetti di tipo MessageChannel e di porte, di tipo MessagePort. Esempio, inviare un messaggio, da un documento, INDEX.HTML, caricato dall’URL http://127.0.0.1:99, al documento caricato nella window dell’iframe all’URL http://www.altrosito.it/chatwidget.html e scrivere anche un gestore per l’evento message che consente di ricevere la risposta dell’elaborazione della pagina CHATWIDGET.HTML. File INDEX.HTML <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>CDM</title> <script> window.addEventListener("load", function () { window.setTimeout(function () { var frame_w = document.getElementsByTagName("iframe")[0].contentWindow; frame_w.postMessage(JSON.stringify({ left: 10, top: 10 }), "http://altrosito.com:81"); }, 1000); }, false); window.addEventListener("message", function (ev) { if (ev.origin == "http://altrosito.com:81") { // processa il messaggio ricevuto... var d = ev.data; } }, false); </script> </head> <body> <iframe src="http://user1.com:81/chatwidget.html" width="800" height="200"></iframe> </body> </html> File CHATWIDGET.HTML Gestore per l’evento message, è in grado di processare i dati inviati e d’inviarli nuovamente alla window originatrice. <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> <script> window.addEventListener("message", function (ev) { if (ev.origin == "http://127.0.0.1:99") { // processa in qualche modo il messaggio... var d = JSON.parse(ev.data); var res = d.left * 2 + d.top * 5; // ...e lo invia all’originatore ev.source.postMessage("Ok processing terminato ... ecco il risultato: " + res, "http://127.0.0.1:99"); TPSIT 5 434 um 105 di } }, false); </script> </head> <body> </body> </html> JMS (JAVA MESSAGING SERVICE) È un insieme d’interfacce tale che un client basato su Java possa fare uso di un sistema di messaggistica, definisce due tipi di messaggi. 1. P2P: si basa sul concetto di coda. 2. Pub/Sub (Publish and Subscribe): un client s’iscrive (subscribe) ad un topic per ricevere messaggi riguardanti uno specifico argomento, gli altri client pubblicano (publish) i messaggi verso questo topic e i messaggi sono spediti a tutti i client che sono iscritti al topic in questione. MantaRay È un sistema di messaggistica open source, usato in sistemi server-less, in altre parole privi di un server centrale, perciò basati su un’architettura puramente distribuita. WCF (WINDOWS COMMUNICATION FOUNDATION) INTRODUZIONE È il framework per lo sviluppo di applicazioni SOA di Microsoft, permette di uniformare lo scambio d’informazioni e di creare e consumare servizi. Il suo punto forte è l’indipendenza dal mezzo di trasporto. Si presenta come l’evoluzione delle tecnologie seguenti. ASP.NET WS. WSE (WS Enhancements). .NET Remoting. .NET Enterprise Services. Coniuga le potenzialità delle diverse tecnologie in un unico modello di programmazione, rendendo semplice il passaggio da una modalità di utilizzo all’altra data e la versatilità con TPSIT 5 434 um 106 di cui è possibile utilizzare un protocollo piuttosto che un altro senza, in alcun modo, modificare il codice del servizio sviluppato. È un’infrastruttura di comunicazione utilizzata per creare applicazioni distribuite, permette d’inviare dati come messaggi asincroni da un endpoint del servizio ad un altro. ARCHITETTURA Il messaggio è la componente fondamentale su cui è basato il complesso processo di scambio d’informazioni tra due entità: mittente e destinatario. L’architettura prevede una suddivisione in livelli. Contratti Definiscono i vari aspetti del sistema del messaggio, il contratto dati descrive ogni parametro che costituisce ogni messaggio che un servizio può creare o utilizzare. I parametri del messaggio sono definiti da documenti XSD (XML [eXtensible Markup Language] Schema Definition language) e consentono a qualsiasi sistema compatibile con XML di elaborare i documenti. Il contratto del messaggio definisce parti specifiche del messaggio utilizzando protocolli SOAP e consente un controllo più preciso delle parti del messaggio laddove tale precisione è richiesta dall’interoperabilità. Il contratto di servizio specifica le firme effettive del metodo del servizio ed è distribuito come interfaccia in uno dei linguaggi di programmazione supportati. I criteri e le associazioni stipulano le condizioni necessarie per la comunicazione con un servizio, l’associazione, ad esempio, deve specificare almeno il trasporto utilizzato HTTP o TCP e una codifica. I criteri comprendono requisiti di sicurezza e altre condizioni che devono essere soddisfatte per la comunicazione con un servizio. L’architettura prevede una suddivisione in livelli che consentono d’isolare e raggruppare le varie componenti che la formano. TPSIT 5 434 um 107 di Fase di esecuzione del servizio Contiene i comportamenti che si verificano solo durante l’operazione effettiva del servizio. La limitazione delle richieste controlla la quantità dei messaggi elaborati che può essere variata se la richiesta per il servizio raggiunge un limite preimpostato. Un comportamento dell’errore specifica la situazione che ha luogo quando si verifica un errore interno nel servizio, ad esempio controllando le informazioni comunicate al client. Il comportamento dei metadati stabilisce il modo e l’opportunità di renderli disponibili al mondo esterno. Il comportamento dell’istanza specifica il numero delle istanze del servizio che possono essere eseguite, ad esempio, un singleton specifica una sola istanza per l’elaborazione di tutti i messaggi. Il comportamento della transazione consente il rollback delle operazioni transazionali in caso di errore. Il comportamento della distribuzione è il controllo del modo in cui un messaggio è elaborato nell’infrastruttura WCF. L’estendibilità consente la personalizzazione di processi della fase di run-time. Il controllo messaggi, ad esempio, è la funzionalità che consente di controllare parti di un messaggio e il filtro parametri consente il verificarsi di azioni preimpostate in base a filtri che agiscono sulle intestazioni del messaggio. Messaggistica Questo livello è composto da canali: sono componenti che elaborano in vario modo un messaggio, ad esempio autenticandolo. I canali operano su messaggi e intestazioni del messaggio, inoltre illustrano i possibili formati e modelli di scambio dei dati. Un set di canali, inoltre, è noto come stack di canali. È un livello diverso dalla fase di run-time del servizio che verte essenzialmente TPSIT 5 434 um 108 di sull’elaborazione del contenuto del corpo del messaggio. Esistono tre tipi di canali. 1. Canali del trasporto: leggono e scrivono messaggi dalla rete o da altri punti di comunicazione con il mondo esterno; esempi di trasporti sono HTTP, NamedPipes, TCP e MSMQ, esempi di codifiche sono XML e sistema binario ottimizzato. 2. Canali di codifica: convertono i messaggi nella/dalla rappresentazione del flusso di byte utilizzata dalla rete. 3. Canali del protocollo: implementano protocolli di elaborazione dei messaggi, spesso leggendo o scrivendo intestazioni aggiuntive nel messaggio; esempi di tali protocolli comprendono WS-Security (consente la protezione a livello di messaggio) e WSReliability Messaging (consente la garanzia di recapito dei messaggi). La figura evidenzia i passaggi che il messaggio percorre prima di giungere a destinazione. Attivazione e hosting È l’ambiente di esecuzione dei servizi sviluppati in WCF, infatti, nella forma finale, un servizio è un’applicazione. Gli hosting più usati sono i seguenti. 1. IIS. 2. WAS (Windows Activation Services): una tecnologia studiata per WCF e che consente di utilizzare su IIS anche protocolli differenti dall’HTTP. Per abilitare WAS su IIS si usa il comando seguente. C:\Programmi\iiS express>appcmd.exe Strumento di amministrazione generico di IIS per la riga dei comandi. APPCMD (comando) (tipo-oggetto) <identificatore> </parametro1:valore1 ...> Tipi di oggetto supportati: SITE Amministrazione di siti virtuali APP Amministrazione di applicazioni VDIR Amministrazione di directory virtuali APPPOOL Amministrazione di pool di applicazioni CONFIG Amministrazione di sezioni di configurazione generale MODULE Amministrazione di moduli del server TRACE Utilizzo di registri di traccia di richieste non riuscite (Per elencare i comandi supportati da ogni oggetto utilizzare /?, ad esempio 'ap pcmd.exe site /?') Parametri generali: /? Visualizza un messaggio della Guida sensibile al contesto. /text<:valore> Genera l'output in formato testo (predefinito). /text:* mostra in dettaglio tutte le proprietà degli oggetti. /text:<attributo> mostra il valore dell'attributo specificato per ogni oggetto. /xml Genera l'output in formato XML. Usare per produrre output che può essere inviato a un altro comando in esecuzione nella modalità /in. /in o Legge e utilizza l'input XML da un input standard. Usare per un input prodotto da un altro TPSIT 5 434 um 109 di comando in esecuzione nella modalità /xml. /config<:*> Mostra la configurazione per gli oggetti visualizzati. /config:* include anche la configurazione ereditata. /metadata Mostra i metadati quando si visualizza la configurazione. /commit Imposta il percorso per salvare le modifiche di configurazione. Può indicare un percorso di configurazione specifico, "site", "app", "parent" o "url" per salvare la porzione corretta del percorso modificato dal comando, "apphost", "webroot" o "machine" per il livello di configurazione corrispondente. /apphostconfig Specificare un altro file applicationHost.config da modificare. /debug Mostra le informazioni di debug per l'esecuzione del comando. quali ad esempio "/!debug:value" utilizzato per l'impostazione della proprietà d i configurazione "debug". I servizi possono, inoltre, essere eseguiti manualmente come file eseguibili. Un servizio può essere eseguito automaticamente come servizio Windows. Scenari di esempio. Applicazione dashboard che esegue il polling di uno o più servizi per i dati e che li visualizza in una presentazione logica. Esposizione di un flusso di lavoro implementato utilizzando WWF (Windows Workflow Foundation) come servizio WCF. Applicazione Silverlight per eseguire il polling di un servizio per i feed di dati più recenti. ENDPOINT Per inviare un messaggio, un mittente ha la necessità di conoscere alcune informazioni, specifiche del servizio, per costruire correttamente la chiamata. Il messaggio può essere semplice come una parola o come un singolo carattere inviato in formato XML o complesso come un flusso di dati binari. 1. Deve sapere l’indirizzo verso cui indirizzare il suo messaggio, infatti, un destinatario in attesa di ricevere richieste, è in attesa su di un determinato indirizzo. 2. Il mittente deve conoscere il modo in cui può comunicare con il destinatario del messaggio; il destinatario di un messaggio, ad esempio, può richiedere l’invio di particolari intestazioni oppure può richiedere che i messaggi scambiati devono rispettare determinate regole di sicurezza, come confidenzialità e integrità. 3. Il mittente deve conoscere le operazioni che il servizio mette a disposizione e la strutturazione che le richieste e le risposte devono avere. In pratica, l’endpoint è uno specifico punto di accesso di un servizio che utilizza un tipo di dati e un determinato protocollo, può appartenere ad un servizio disponibile in modo continuo ospitato da IIS oppure essere un servizio ospitato in un’applicazione. Per esempio, un endpoint può essere un client di un servizio che richiede dati da un endpoint del servizio. Questi tre elementi insieme compongono l’endpoint del servizio. 1. A (Address): è l’URL sul quale il servizio è in ascolto in attesa di ricevere una richiesta, sintassi <protocollo>://<servername>:<porta>. 2. B (Binding): stabilisce la modalità d’interfacciamento con il servizio destinatario e definisce i protocolli da utilizzare per la comunicazione. 3. C (Contract): è la rappresentazione dei servizi e delle operazioni richiamabili e utilizzabili da un fruitore, esistono diverse tipologie di contratto. 3.1. Service Contract: contiene i metodi, chiamati operation e i parametri che un client deve rispettare per comunicare con il servizio. 3.2. Data Contract: definisce gli schemi che rappresentano i tipi di dati scambiati tra le TPSIT 5 434 um 110 di entità. 3.3. Message Contract: consente di lavorare direttamente sul messaggio SOAP, impostando le proprietà che appartengono all’header e al body. 3.4. Fault Contract: la gestione delle eccezioni è soggetta, anche se non in maniera obbligatoria, all’osservazione di un particolare contratto. 3.5. Callback Contract. TRACKBACK SOAP è nato da un protocollo preesistente XML-RPC e vi ha aggiunto una serie di funzionalità che ha sporcato il protocollo originario, rendendolo più complesso e meno efficace del necessario. XML-RPC non ha la complessità di SOAP pur assolvendo le stesse funzioni, inoltre è usato da una grande varietà di blog per implementare il meccanismo di TrackBack per la comunicazione e la notifica tra due risorse. La risorsa A manda un Trackback ping, chiamato Pingback, alla risorsa B, la quale risponde con un messaggio di avvenuta notifica o con un eventuale errore. La risorsa A dovrebbe mandare un ping alla risorsa B, nel caso in cui sia presente, nella risorsa A, un approfondimento o una citazione della risorsa B. Il Trackback si è molto diffuso nei blog. In questo caso, un blog riceve una serie di ping da altri blog e, solitamente, mostra, sotto a ogni post, l’elenco dei ping ricevuti e riferiti a quello specifico post. Il Trackback ping può contenere informazioni riguardanti il titolo della risorsa notificata, un estratto della stessa e il titolo del sito web che ha inviato il ping. L’unica informazione obbligatoria che un ping deve contenere è il permalink della risorsa notificata. Il termine Trackback è nato nel 2002, così battezzato da Six Apart che introdusse questo sistema nella sua piattaforma di blogging Movable Type, sebbene il Trackback utilizzi funzionalità presenti nelle specifiche HTTP 1.0 e quindi già dal 1992. Un fenomeno diffuso è l’utilizzo dei Trackback a scopo di SPAM, in pratica l’invio indiscriminato di Trackback ping allo scopo di far apparire i propri link nell’elenco dei ping di qualche blog. Esistono, però, filtri per individuare i Trackback di SPAM, implementati in molte delle piattaforme di blogging che supportano il Trackback, riducendo, almeno parzialmente, il fastidio causato dallo SPAM. Chiamata Il seguente codice rappresenta una chiamata alla procedura remota getStateName alla quale è passato come parametro il valore 41. POST /RPC2 HTTP/1.0 User-Agent: Frontier/5.1.2 (WinNT) Host: betty.userland.com Content-Type: text/xml Content-length: 181 <?xml version="1.0"?> <methodCall> <methodName>getStateName</methodName> <params> <param> <value> <i4>41</i4> </value> TPSIT 5 434 um 111 di </param> </params> </methodCall> La chiamata si compone di due parti. 1. Header: si trova l’User-Agent e l’host che stanno effettuando la chiamata, il content type text/xml e il content-lenght che segna la lunghezza del messaggio. 2. Payload: è formato da una struttura il cui nodo radice è <methodCall> che indica che si sta effettuando una richiesta; all’interno di questo nodo si trova <methodName> che contiene il metodo da richiamare; ancora più internamente nella gerarchia si trova <params> al cui interno possono essere contenuti uno o più valori <param> ciascuno valorizzato con un <value>. È possibile indicare un tipo che specifica il senso del valore del parametro. <i4> o <int> <boolean> <string> <double> <dateTime.iso8601> <base64> Un intero di 4 byte 0 (false) or 1 (true) string numeri float date/time base64-encoded -12 1 hello world -12.214 19980717T14:08:55 binaryeW91IGNhbid0IHJlYW QgdGhpcyE= Il tipo <struct>, è un record al cui interno sono contenute delle celle che hanno un nome e un valore che a sua volta può anche essere di tipo <struct>. <struct> <member> <name>lowerBound</name> <value> <i4>18</i4> </value> </member> <member> <name>upperBound</name> <value> <i4>139</i4> </value> </member> </struct> L’ultimo tipo strutturato che può essere passato come parametro ad una procedura di remote call è il tipo <array>. <array> <data> <value> <i4>12</i4> </value> <value> <string>Egypt</string> </value> <value> <boolean>0</boolean> TPSIT 5 434 um 112 di </value> <value> <i4>-31</i4> </value> </data> </array> Risposta Il server remoto restituisce l’esito al chiamante, utilizzando il formato XML. La struttura di una risposta si compone di un header e di un payload. HTTP/1.1 200 OK Connection: close Content-Length: 426 Content-Type: text/xml Date: Fri, 17 Jul 1998 19:55:02 GMT Server: UserLand Frontier/5.1.2-WinNT <?xml version="1.0"?> <methodResponse> <fault> <value> <struct> <member> <name>faultCode</name> <value> <int>4</int> </value> </member> <member> <name>faultString</name> <value> <string> Too many parameters. </string> </value> </member> </struct> </value> </fault> </methodResponse> La root <methodResponse> contiene un <params> che a sua volta contiene un <param>> che a sua volta contiene ancora un singolo <value> che definisce il valore della risposta. Viceversa il <methodResponse> potrebbe anche contenere un <fault> che a sua volta contiene dei dati che identificano un eventuale problema. In XML non possono convivere segmenti <fault> e segmenti <params>. Se una richiesta non ha dato esito corretto, ovviamente non può esserci un params che identifica il risultato. APPLICATION DOMAIN Ogni servizio WCF non può esistere da solo ma dev’essere obbligatoriamente ospitato all’interno di un processo Windows chiamato processo host. TPSIT 5 434 um 113 di In ogni processo, possono esistere più di un Application Domain e all’interno di ognuno di essi può essere ospitato anche più di un servizio WCF. Di conseguenza, un servizio WCF può essere ospitato in una qualsiasi tipologia di applicazione. Console Application. Windows Forms Application. ASP.NET/IIS/WAS. Enterprise Services. Windows Services. Self-hosting: in pratica, un servizio WCF è ospitato all’interno di un’applicazione .NET. class ServiceHost Ogni servizio WCF è eseguito all’interno del suo host. Il servizio è gestito da una classe chiamata ServiceHost. WCF fornisce sia una classe base astratta, il ServiceHostBase sia un’implementazione di default direttamente utilizzabile, il ServiceHost. Esiste un rapporto uno a uno tra il servizo WCF e il ServiceHost presso il quale è ospitato. Un ServiceHost può ospitare un solo tipo di servizio. Quando si lancia un’applicazione segue la creazione di un processo che altro non è che l’immagine dell’applicazione in memoria. Lo spazio di memoria riservato ai processi e ai dati che gestiscono è isolato, quindi il processo A non può accedere all’area di memoria riservata al processo B. Per scambiare dati fra processi è necessario stabilire un meccanismo di comunicazione fra processi, il SO usa l’RPC che gestisce la comunicazione fra processi. Il .NET Framework supera questo concetto, introducendo il concetto di application domain. I processi di questo tipo sono svincolati dall’uso di RPC, la memoria è interamente gestita dal CLR (Common Language Runtime). In .NET è possibile creare più application domain e persino application domain annidati all’interno di un’unica applicazione. Ciascun application domain ha una gestione della memoria separata. Ovviamente .NET Remoting deve poi individuare dei metodi affinché gli oggetti appartenenti ai vari application domain possano comunicare. Quando s’invoca un oggetto presente nell’application domain corrente, è sufficiente una chiamata locale. Se invece l’oggetto è presente in un diverso application domain si rende necessaria una chiamata remota. In tal caso sul client è creato un proxy, in pratica un’istanza della classe TransparentProxy, mentre sul server è creato uno stub. La potenza e la caratteristica di .NET Remoting, rispetto ad altre architetture di remotizzazione concorrenti, è che tutto questo meccanismo è completamente automatico e non è quindi richiesto al programmatore l’onere di codificare tali strati proxy/stub. MARSHALING Risolve due problemi. 1. Conversione dei parametri in stream di byte I parametri dei metodi devono essere impacchettati secondo un formato che sia compreso da client e server e che sia gestibile direttamente dallo strato di trasporto. Le strutture dati complesse, per esempio struct devono essere ridotte a sequenze di byte, è la serializzazione. TPSIT 5 434 um 114 di 2. Conversione fra dati appartenenti a piattaforme eterogenee Macchine diverse hanno rappresentazioni diverse dei dati, per esempio rappresentazione little endian e big endian. Il codice che esegue la serializzazione e la deserializzazione è generato automaticamente dalla specifica delle interfacce dei metodi, descritta in un IDL (Interface Definition Language) che fa parte degli stub. Tramite questo meccanismo la locazione dell’oggetto è completamente trasparente all’applicazione che l’utilizza, nel senso che è la tecnologia che risolve la chiamata al metodo remoto sia che l’oggetto si trovi sullo stesso PC, all’interno di una LAN oppure su di un server remoto cui si accede tramite Internet. Ci sono due diversi metodi per rendere disponibile ad un client un oggetto remoto. 1. Un oggetto MBV (Marshal By Value) è creato sul server, serializzato in uno stream, trasmesso al client su cui è ricostruita un’esatta replica dell’oggetto stesso che non ha più alcuna relazione con l’istanza originaria. 2. Un oggetto MBR (Marshal By Reference) passa il riferimento ad un oggetto creato ed eseguito sul server al client che ne trattiene, dunque, solo un riferimento, proxy. Di fatto gli oggetti MBR si possono considerare i veri oggetti di remotizzazione proprio perché essi effettivamente sono eseguiti su un server e non trasferiti. Inoltre, gli MBV possono andare incontro ad incongruenze quali il pericolo di portare al loro interno riferimenti a risorse che hanno senso e visibilità solo sul server e che invece li perdono a seguito dei trasferimenti. Si pensi, ad esempio, alla connessione ad un server DB o l’accesso ad un file fisicamente raggiungibile solo dal server o ad una periferica hardware del server. Ad un oggetto MBV è richiesta, come unico requisito, la presenza dell’attributo Serializable a livello di classe oppure l’implementazione dell’interfaccia Iserializable. [Serializable] public class MyMBVObject { int Code; string Name; double Execute(int quantity); } Oppure. public class MyMBVObject : ISerializable { int Code; string Name; double Execute(int quantity); // unico metodo da implementare dell’interfaccia ISerializable void GetObjectData(SerializationInfo info, StreamingContext context); } Un oggetto MBR, invece, deve necessariamente discendere direttamente o indirettamente dalla classe System.MarshalByRefObject. public class MyMBRObject :MarshalByRefObject { int Code; string Name; double Execute(int quantity); } TPSIT 5 434 um 115 di Alla base del principio di remotizzazione delle chiamate c’è il meccanismo di serializzazione delle richieste al server e delle risposte e di come sono salvate le informazioni in un pacchetto informativo dalla struttura nota ai due capi della comunicazione: tale compito spetta ai Formatter. Il .NET Framework è flessibile: prevede due modalità di formattazione delle richieste. 1. SoapFormatter: usa il formato XML SOAP per il formato delle chiamate e delle risposte remote. 2. BinaryFormatter: usa un formato completamente binario e quindi più ottimizzato. Esse non sono che l’implementazione di una specifica più generica e astratta, basata sul polimorfismo d’implementazione dell’interface IFormatter che prevede la libertà di estendere e aggiungere nuovi formati di formattazione semplicemente fornendo nuove implementazioni di tale interfaccia. WS-RELIABLE-MESSAGING Un fruitore, client di un servizio, invia una richiesta che può potenzialmente attraversare diversi sistemi, S/W e H/W, come ad esempio una LAN. Cosa succede se la rete non è in grado di servire le richieste? Cosa succede se il server verso il quale si spediscono i messaggi non è disponibile? WS-Reliability-Messaging è un’evoluzione della specifica WS-Reliability ed è stata studiata appositamente per garantire la consegna affidabile dei messaggi tra sistemi eterogenei. Il sender si predispone per l’invio di una sequenza di tre messaggi (1). Dopo aver inizializzato la comunicazione (2), il sender incomincia l’invio della sequenza di messaggi (3, 4, 5) allegando nell’intestazione un identificatore univoco (UID) che il receiver utilizzerà per collegare tra loro i diversi messaggi ricevuti. L’ultimo messaggio conterrà anche un lastMessage token il quale ha lo scopo d’indicare al receiver che l’invio dei messaggi è terminato (5). A questo punto il receiver invia un report per elencare quali sono i messaggi che sono stati correttamente ricevuti (6). Il sender ritenterà l’invio dei messaggi che mancano (7) e resta in attesa di un nuovo resoconto da parte del receiver. TPSIT 5 434 um 116 di Questo ciclo non termina fino a quando il receiver non avrà ricevuto tutti i messaggi (8). In alcuni casi, dipendentemente dalle impostazioni del servizio, il receiver può riordinare i messaggi nella giusta sequenza e solo a questo punto inoltrarli al servizio per il suo utilizzo (9). Utilizzando questo meccanismo, ogni messaggio è inviato e ricevuto solo una volta. Questo significa che è possibile evitare l’invio e la conseguente elaborazione di messaggi duplicati. L’ordine dei messaggi è, invece, una caratteristica opzionale. Se l’applicazione richiede che i messaggi devono essere elaborati rispettando la sequenza in cui sono stati inviati, allora il receiver si occuperà di riordinarli appena li avrà ricevuti tutti. MESSAGGI SU CODE Che cosa accadrebbe nel caso in cui una delle due entità è offline? La certezza di recapito dei messaggi rimane inalterata ma come si può garantirli? In WCF si usa MSMQ che fornisce affidabilità, anche se una delle entità, per svariati motivi, non è disponibile. Ci sono due binding che consentono di registrare i messaggi o di recuperarli da MSMQ. 1. <msmqIntegrationBinding>: è il binding che garantisce la totale interoperabilità con le attuali tecnologie esistenti. 2. <netMsmqBinding>: ottimizzato se sia il client sia il server sono entrambi sviluppati utilizzando WCF. TRANSAZIONI Che cosa accade se durante le fasi d’invio, ricezione o elaborazione di un messaggio o di una richiesta, è generata un’eccezione? L’esecuzione di una serie di passaggi può essere considerata conclusa con successo se e solo se, tutti i passaggi sono andati a buon fine. Le transazioni nascono proprio per garantire il corretto funzionamento dell’applicazione anche alla presenza di eccezioni. Questo tipo di operazioni sono definite di tipo ACID (Atomicity, Consistency, Isolation, Durability). In WCF le transazioni sono gestite utilizzando la specifica WS-AT (Atomic Transaction), un protocollo sviluppato per garantire l’interoperabilità tra sistemi eterogenei. Usa un protocollo a due fasi chiamato Two-Phase Commit. I binding che supportano le transazioni sono i seguenti. <netTcpBinding> <netNamedPipeBinding> <WSHttpBinding> <WSDualHttpBinding> <WSFederationHttpBinding> SESSIONE Come si controllano le sessioni dei servizi in esecuzione all’interno dei ServiceHost. È bene notare che quando si parla di sessione non c’è alcun riferimento alla gestione della sessione di ASP.NET sebbene, per certi versi concettualmente simili. La sessione in WCF è il mantenimento dei valori di un’istanza attraverso più chiamate provenienti dallo stesso client. Il meccanismo che regola le sessioni è basato su un identificativo, generato a seguito della prima richiesta e che, come in ASP.NET, identifica univocamente il client. In WCF la sessione è supportata con specifici canali che implementano l’interface TPSIT 5 434 um 117 di ISessionChannel:IInputSessionChannel,IOutputSessionChannel, IDuplexSessionChannel. Questi canali si occupano di generare e gestire il session ID. La gestione della sessione è un’esigenza del servizio e perciò definita lato server. Il client ne dev’essere assolutamente a conoscenza. Per questo motivo è sul contratto di servizio che si deve scegliere se quello specifico contratto può funzionare solo in presenza di una sessione. Purtroppo, anche se il contratto lo prevede, non tutti i binding possono gestire la sessione. A run-time è, infatti, effettuato un controllo per verificare se il binding scelto è compatibile con l’impostazione data al controllo di sessione. Se la sessione non è supportata allora sarà generata un’eccezione. I binding che consentono di utilizzare la sessione sono i seguenti. <WSHttpBinding> <WSDualHttpBinding> <netTcpBinding> <netNamedPipeBinding> <CustomBinding> CONCORRENZA Gestire la sessione è un’operazione delicata considerando che contemporaneamente possono esserci diverse richieste da diversi client. L’istanza del servizio può essere considerata o non thread safe. L’utilizzo dell’impostazione PerCall per l’InstanceContextMode rende l’istanza del servizio automaticamente thread safe proprio perché ogni richiesta verso il servizio avrà una particolare istanza dedicata all’esecuzione di quella specifica chiamata. In questo caso non c’è bisogno di proteggere le informazioni così come non c’è alcuna necessità di sincronizzarle. Al contrario, l’utilizzo della modalità PerSession necessità di gestire la concorrenza e la sincronizzazione. Uno stesso proxy, infatti, potrebbe gestire diversi thread lato client che inviano contemporaneamente richieste verso il servizio. Infine, la modalità Single è quella che maggiormente deve prevedere meccanismi di sincronizzazione e di gestione della concorrenza per garantire un corretto funzionamento. Più client diversi tra loro hanno, infatti, accesso alla stessa istanza. In WCF, l’enumeratore ConcurrencyMode consente di stabilire la modalità di gestione della concorrenza che dev’essere messa in atto. SICUREZZA Sicurezza applicativa WCF permette di utilizzare diversi meccanismi di autenticazione. Windows: il client correda la richiesta con le sue credenziali e il servizio autentica utilizzando, per esempio Kerberos. Username: il client invia username e password e il servizio provvede alla verifica dell’esistenza di queste credenziali, per esempio un DB. Certificati: la richiesta è inoltrata con un certificato assegnato al client e che consente d’identificarlo univocamente, il servizio valida il certificato e autenticare la richiesta. Custom: è possibile creare una tipologia di credenziale personalizzata. Issued Token: un client ottiene da un Security Token Service, un token che poi presenta al servizio richiesto, quest’ultimo, fidandosi di chi ha rilasciato il token, autentica il client e di conseguenza la sua richiesta. WS-Security. TPSIT 5 434 um 118 di XML Encryption, per la criptazione delle informazioni e quindi la loro confidenzialità. XML Digital Signature, per la firma digitale e l’integrità dei dati. PROGETTO Si basa sulla creazione di tre componenti. 1. Un oggetto che può essere invocato in modo remoto. 2. Un application domain che aspetta le richieste e le gestisce, applicazione host. 3. Un application domain che esegue le richieste, applicazione client. Lo scopo è di gestire la comunicazione fra questi tre componenti utilizzando .NET Remoting. Occorre realizzare un oggetto che esponga metodi che possono essere invocati in modo remoto. using System; using System.Data; using System.Data.SqlClient; namespace progetto.RemotingServices { public class Server : MarshalByRefObject { // apertura e connessione al DB } public Server() { InitializeComponent(); } public string ScalarMethod(string parametro1, ref int parametro2) { parametro2 = parametro2 + 2; return "prova: " + parametro1; } public DataSet GetData() { DataSet ds = new DataSet("Ds Prova"); sqlDataAdapter1.Fill(ds); sqlDataAdapter3.Fill(ds); sqlDataAdapter2.Fill(ds); return ds; } A parte il dover ereditare dalla classe MarshalByRefObject, requisito che comunque non ne pregiudica l’esecuzione in locale, non vi è alcun elemento che lascerebbe supporre la natura “remota” di questa classe. C’è un costruttore di default, il metodo ScalarMethod che accetta parametri scalari, una stringa per valore e un intero per referenza e restituisce un altro scalare di tipo stringa. Il metodo GetData restituisce un oggetto strutturato, in altre parole DataSet ed è un trasferimento di oggetti complessi via remoting. Tale metodo si occupa autonomamente di aprire una connessione ad un SQL (Structured Query Language) Server locale puntando ad un DB. La connectionstring è letta da una costante presente nella classe. Host È un’applicazione che si pone in attesa di richieste per un oggetto e le gestisce. A differenza di altri sistemi di remotizzazione più complessi quali COM+ o CORBA che sono tenuti in piedi da un application server che funge da Object Broker, .NET Remoting fornisce solo una tecnologia di remotizzazione delle chiamate e non una vera e propria infrastruttura che funga da incubatrice delle chiamate remote. TPSIT 5 434 um 119 di Non esiste un’applicazione standard che funge da host, si deve scriverne una da zero. Un host non è altro che una qualsiasi applicazione .NET eseguibile: WinForms, Console o un Windows Service. L’unica alternativa alla scrittura di un proprio host è di utilizzare IIS come host accettando, però, alcune limitazioni come quella di poter utilizzare solo il canale HTTP. Scrivere un proprio host, corrisponde a scrivere una linea di codice, tutto, infatti, si basa sul file di configurazione che, opzionalmente, può accompagnare le applicazioni .NET di tipo WinForms, Console o Windows Service, in altre parole il file APP.CONFIG, oppure, se si usa IIS come host, il file WEB.CONFIG. L’unico requisito è che l’applicazione e quindi il processo Windows che l’ha generato, resti in vita per tutto il tempo. <?xml version="1.0" encoding="utf-8" ?> <configuration> <system.runtime.remoting> <customErrors mode="off" /> <application name="remotingData"> <lifetime leaseTime="100D" sponsorshipTimeOut="3M" renewOnCallTime="5M" leaseManagerPollTime="1M" /> <channels> <channel ref="tcp" port="8737"> <serverProviders> <formatter ref="binary" typeFilterLevel="Full" /> </serverProviders> </channel> </channels> <service> <wellknown mode="SingleCall" type="RemotingServices.InterfaceServer, Server" objectUri="myFirstService" /> </service> </application> </system.runtime.remoting> </configuration> È un file XML con un nodo principale <configuration>. Sotto di esso trovano posto diversi altri nodi che servono a configurare aspetti e tecnologie standard del Framework: dai file di log, alla versione degli assembly. Si può usare per salvare le proprie proprietà applicative in luogo dei file INI o di file XML. C’è anche la specifica per remoting, il nodo <system.runtime.remoting>, composto dal nodo <customErrors>, di default impostato a false che indica se dev’essere permesso che le eccezioni del componente di remoting risalgano fino al client che l’ha invocato, valore false oppure che tutti gli errori siano filtrati e sostituiti con messaggi di errore applicativi. A questo punto c’è un nodo <application> che rappresenta il servizio di remoting da pubblicare, ce ne può essere uno solo per host. Nell’esempio l’applicazione si chiama remotingData, in essa sono definiti tutti gli aspetti del servizio esposto: dal <channels> usato per le comunicazioni, al <formatter> usato per lo streaming dei dati, al nome della classe vera e proprio da pubblicare. TPSIT 5 434 um 120 di Il nodo <channels> enumera i canali che s’intendono usare nell’host; nell’esempio è usato il canale tcp fornito con il .NET. Il servizio è pubblicato sulla porta 8737 e farà uso del formatter di tipo binary. Il nodo <service> rappresenta il servizio esposto, è definito nel sotto nodo <wellknown> che riporta, nell’attributo type, proprio il full qualified name del tipo da pubblicare, in altre parole il nome della classe comprensivo di namespace e l’assembly in cui è contenuto separati da una virgola, l’alias simbolico del tipo sarà myFirstService. Codifica UTF-8 (Unicode Transformation Format 8 bit) Simboli e caratteri che non rientrano nell’intervallo standard ASCII vanno trasformati in entità HTML. Client Anche in questo caso serve un file di configurazione simile a quanto visto per l’host. <?xml version="1.0" encoding="utf-8" ?> <configuration> <system.runtime.remoting> <application name="myClient"> <channels> <channel ref="tcp" port="0"> <clientProviders> <formatter ref="binary" /> </clientProviders> </channel> </channels> <lifetime leaseTime="5000M" sponsorshipTimeout="5000M" renewOnCallTime="5000M" leaseManagerPollTime="5000M" /> <client> <wellknown type="RemotingServices.InterfaceServer, Server" url="tcp://localhost:8737/remotingData/myFirstService" /> </client> </application> </system.runtime.remoting> </configuration> All’apparenza le due versioni sono quasi indistinguibili: esiste un nodo <client> al posto del nodo <service>, in cui è definito il tipo di cui effettuare l’invocazione remota e il suo URL. L’altra differenza sta nel nodo <channels> che, oltre all’alias del canale da usare, definisce anche un attributo port che non è altro che la parte del client su cui il server potrà aprire una comunicazione in senso contrario, in pratica il server che contatta il client, in modo da realizzare una comunicazione bidirezionale. A differenza dell’host, nel caso del client è richiesta anche l’aggiunta dell’assembly del componente remoto tra i riferimenti del progetto. Il client effettuerà una chiamata e un’esecuzione remota della classe seguente. TPSIT 5 434 um 121 di System.Runtime.Remoting.RemotingConfiguration.Configure(Application.ExecutablePath + ".config"); Server srv = new Server(); DataSet ds = srv.GetData(); Basta la sola chiamata del caricamento della configurazione di remoting, fatta una sola volta per tutta la durata dell’applicazione e, da quel momento in poi, ogni volta che il client cercherà d’invocare la classe Server, il sotto sistema di remoting di .NET si accorgerà che il tipo RemotingServices.Server è un tipo remoto raggiungibile ad un indirizzo. Invocherà la chiamata remota, creerà una classe proxy, in altre parole una classe isomorfica con proprietà e metodi identici a quelli della classe remota, usando il formatter specificato dalla comunicazione, preparerà dei pacchetti di dati contenenti i parametri da passare al metodo remoto chiamato, trasferirà l’intera chiamata insieme a questi pacchetti all’oggetto remoto usando il canale previsto per la comunicazione, attenderà il risultato dell’esecuzione e poi eseguirà l’operazione contraria con i valori di ritorno della chiamata indietro verso il client. Tutto in modo trasparente all’utente e anche al programmatore. MICROSOFT AXUM Ha un grande supporto per la scrittura di applicazioni distribuite, in effetti, uno dei motivi per l’adozione di una tale linea d’isolamento è che i domini possono interagire a livello locale o in remoto senza alcun cambiamento nel modello. Per esempio, il modo in cui il web è programmato con domini, servizi, domande di applicazioni SOA, agenti di gestione dei protocolli. Per raggiungere un agente all’interno di un dominio, si deve avere un indirizzo, questo è vero in locale e in remoto, all’interno di un processo, è un po’ più facile, perché il nome del tipo di agente si comporta come un indirizzo di default. Il run-time di Axum attraverso un’interface denominata IHost, permette di dare un agente indirizzo all’interno di un dominio, per essere precisi, quello che si associa con un indirizzo è una fabbrica per gli agenti di tipo hosted che è utilizzato per creare istanze agente quando si crea una nuova connessione. Ogni comunicazione di base o servizio di hosting deve avere la propria implementazione di IHost, Axum è dotato di uno per WCF e uno per inprocess communication. L’indirizzo può essere associato ad un’istanza di dominio esistente, nel qual caso ha creato un’istanza agente associata a tale dominio. Per esempio, è possibile ospitare domini come servizi con il codice seguente. channel Simple { input string Msg1; output string Msg2; } domain ServiceDomain { agent ServiceAgent : channel Simple { public ServiceAgent () { // … } } } agent Server : channel Microsoft.Axum.Application { public Server () { var hst = new WcfServiceHost(new NetTcpBinding(SecurityMode.None, false)); hst.Host<ServiceDomain.ServiceAgent>("net.tcp://localhost/Service1"); } } TPSIT 5 434 um 122 di Ogni volta che un client si connette all’indirizzo net.tcp://localhost/Service una nuova istanza di ServiceAgent è creata, associata con una nuova istanza ServiceDomain. Se, invece, si vuole creare agenti da associare ad una singola istanza di dominio, si deve passare uno per un Host. hst.Host<ServiceDomain.ServiceAgent("net.tcp:...", new ServiceDomain()); Vi è un’interface corrispondente per il lato client, chiamata ICommunicationProvider che è utilizzata per creare una nuova connessione ad un servizio di Axum o qualsiasi altro servizio che è scritto in Axum: è un accoppiamento lasco. La connessione al servizio è simile a questa. var prov=new WcfCommunicationProvider(new NetTcpBinding(SecurityMode.None, alse)); var chan = prov.Connect<Simple>("net.tcp://localhost/Service1"); Non è necessario creare un nuovo provider di comunicazione per ogni connessione o un nuovo host per ogni chiamata Host. TPSIT 5 434 um 123 di WS INTRODUZIONE Un servizio è una potenzialità messa a disposizione da un fornitore e utilizzata da uno o più consumatori. Chi sviluppa S/W, indipendentemente dalle architetture utilizzate, sa quanto siano importanti i servizi messi a disposizione dalle librerie, siano esse dell’ambiente che si sta utilizzando oppure di terze parti che permettono di utilizzare servizi computazionali senza conoscere la loro implementazione, trattandoli come black box che dichiarano di ricevere certi parametri e di restituire un risultato computazionale certo e corretto. Importante diventa il canale attraverso il quale un servizio è erogato e distribuito. La distribuzione dei servizi, in altre parole la possibilità di avere fornitori di servizio che non siano soltanto accanto ai loro consumatori ma che siano geograficamente distribuiti, ha fatto nascere l’esigenza di avere la disponibilità di servizi dotati della massima forma di distribuzione, in pratica Internet e all’interno di essa il sistema di distribuzione delle informazioni che più si addice a diventare canale di erogazione di un servizio è il canale web, ecco nascere quindi il concetto di WS, in italiano servizio web, in altre parole un servizio computazionale erogato attraverso il web. Importante è il concetto d’interoperabilità di un servizio, in altre parole completa indipendenza dal linguaggio di programmazione, dal SO, dalla piattaforma, dal modello di oggetti e dalla particolare periferica utilizzata: grazie all’utilizzo di protocolli standard basati su XML, un WS può essere interrogato da qualsiasi applicazione, scritta in qualsiasi linguaggio, ospitato in un SO qualunque. L’unica cosa da conoscere per poterlo utilizzare è l’interfaccia dei metadati che espone, esattamente quello che serve per utilizzare un servizio tradizionale. Nelle organizzazioni industriali non si utilizza un’unica piattaforma informatica, in cui possono essere condivise applicazioni e dati; si trovano, invece, piattaforme diverse, con standard diversi che rendono difficile condividere le applicazioni e soprattutto i dati. La soluzione adottata è stata quella di duplicare i DB sulle diverse piattaforme: problemi. Allineamento dei dati tra le varie piattaforme. Utilizzo di risorse per il trasporto di dati da una parte all’altra. Verifiche che devono essere fatte dal personale. Lo standard XML ha reso possibile l’interoperabilità tra piattaforme diverse: vantaggi. È più semplice scambiare dati tra una piattaforma e l’altra. Non occorre duplicare i dati. Nessuna necessità di spazio di archiviazione aggiuntivo. Nessuna necessità di verifiche e allineamento di dati. Nessun utilizzo di risorse macchina per la replicazione di dati. I WS non sono il primo tentativo di creare un ponte di comunicazione fra elementi installati in luoghi geograficamente distanti, tuttavia hanno apportato un cambiamento proprio per la loro caratteristica di aggirare i limiti imposti dai linguaggi per diventare “intercomunicanti” in modo del tutto indipendente dalla piattaforma di sviluppo e dal SO. Il programmatore A crea un’applicazione, un WS e lo rende disponibile su un sito web. Il programmatore B interroga quel sito web e senza avere conoscenze di com’è implementato il WS, deve sapere solo quali metodi del WS invocare, ne usa le informazioni. Questo consentirebbe a chi scrive l’applicazione di evitare di dover perdere tempo con la creazione di funzionalità base e invece di potersi concentrare sulla personalizzazione richiesta dai clienti, realizzando applicazioni più funzionali in meno tempo. TPSIT 5 434 um 124 di Sono piccole unità di codice, sviluppate per eseguire un limitato insieme di compiti, gli esempi tipici possono essere: servizi di validazione per i numeri di carte di credito, aggiornamenti in tempo reale delle quotazioni di borsa, servizi di traduzione online. I WS consentono un veloce riutilizzo del codice: grazie alla facile reperibilità e alla semplicità con cui è possibile interrogarli, sarà sempre più frequente l’utilizzo di WS al posto di implementazione di codice ex novo. L’utilizzo dei WS è molto importante perché permette un più facile scambio d’informazioni tra un’applicazione e un client, anche quando il client è un’app installata su uno smartphone: l’uso dei WS può portare a nuovi tipi di app per utenti sempre in movimento. WS È un servizio disponibile in rete che utilizza il linguaggio XML per lo scambio dei messaggi tra il sistema richiedente e il sistema che eroga il servizio. Un WS presenta le seguenti caratteristiche. È ricercabile e recuperabile dinamicamente sulla rete. È definito dalla sua interfaccia, indipendente dall’implementazione del servizio stesso. Un WS è quindi un’entità debolmente accoppiata, da un punto di vista tecnologico poiché può essere implementato e invocato da sistemi realizzati con diverse tecnologie. Gli attori coinvolti in un’interazione con un WS sono i seguenti. 1. Consumer: è un sistema S/W che richiede un servizio. 2. Provider: è un sistema che fornisce il servizio. 3. Registry: è un repository in rete di tutti i servizi consultabili e fruibili. I WS danno “il meglio di se stessi” in un’architettura distribuita e in questo caso s’intende in particolar modo un’architettura geograficamente distribuita, in altre parole in una situazione peculiare di chi ha, ad esempio, più filiali, dislocate su un ampio territorio che in qualche modo devono comunicare tra loro. Il concetto principale in OOP (Object Oriented Programming) è che un oggetto può essere visto come una black box di cui è necessario conoscere solamente l’interfaccia senza doversi preoccupare della sua implementazione interna. Questo permette di sfruttare le librerie esterne, API, senza avere alcun tipo d’informazione su come esse siano state implementate. È ovvio che non è possibile utilizzare API scritte in un linguaggio su un altro; ad esempio una libreria scritta in .NET non è utilizzabile su una piattaforma Java. Sviluppare un’applicazione che interagisca con un DB centralizzato dislocato in un qualsiasi punto, inoltre, l’applicazione dovrà girare su piattaforme diverse Windows, Linux, Mc OS, Solaris e che implicitamente o esplicitamente sia imposto anche l’ambiente di sviluppo .NET, Java, PHP, PERL (Practical Extraction and Report Language). Una soluzione tra le più immediate ma meno efficiente dal punto di vista progettuale è quella di scrivere ogni volta l’applicazione ex novo, facendosi carico quindi sia della business logic sia dell’interazione con il DBMS (DataBase Management System). Se da una parte la business logic può essere progettata astraendo dallo specifico linguaggio di programmazione, per l’interazione con il DB occorrono i driver per ogni specifico linguaggio e si deve lasciare aperta un porta dedicata sia sul server di DB sia sulla macchina che ospiterà l’applicazione con tutti i problemi di sicurezza che ne conseguono. Inoltre, alcuni DBMS commerciali richiedono che le interrogazioni passino attraverso un loro client proprietario, quindi soldi per le licenze. È meglio progettare un’applicazione che si occupi di esporre un’unica interfaccia che TPSIT 5 434 um 125 di permetta l’interazione con il DB e che allo stesso tempo sia fruibile da qualsiasi linguaggio di programmazione. I WS sono la soluzione: si può progettare un’istanza di un qualsiasi WS come se s’istanziasse una qualsiasi classe da una libreria esterna e utilizzarla nel WS. Il WS funge da layer intermezzo tra DB e applicazioni client. Due filosofie progettuali diverse. 1. Progettare un WS “su misura” del DB Se si prende in considerazione un DB e si vogliono rendere disponibili le informazioni sui clienti presenti nella tabella Clienti, si deve ricevere in ingresso al metodo il nome, implementare all’interno la logica d’interrogazioni e restituire i risultati. Se da un lato questa soluzione è molto sicura perché il client non si occuperà mai del DB, dall’altra presuppone che ogni DB abbia il suo WS specifico, perché cambiando il DB dovrà cambiare tutta la logica che sta dietro il WS stesso. 2. Progettare un WS generico Si deve spostare la logica sul client per tenere il WS il più generale possibile, in pratica i suoi metodi si occupano di ricevere in ingresso un generico comando SQL. WS SOAP Immaginare di utilizzare nell’applicazione Windows i servizi di un componente S/W realizzato su Linux e in esecuzione in un server di cui s’ignora l’ubicazione geografica e che si può raggiungere solo tramite un canale di comunicazione. TPSIT 5 434 um 126 di Immaginare di chiamare metodi di questo componente, ricevere i risultati e restituirli a un’applicazione chiamante, di cui s’ignora tutto fuorché il fatto che ha chiamato tramite Internet l’applicazione. In altre parole, varie applicazioni, diverse per SO, linguaggio di programmazione, tipo e località del server in cui sono eseguite, possano collaborare automaticamente e scambiarsi dati, fare query su DB e completare calcoli, grazie all’architettura SOA. Occorrono quattro protocolli, oltre a quello di trasmissione sulla rete che può essere HTTP su TCP/IP che permettono il dialogo tra diverse piattaforme e rendono possibile l'esportazione di componenti già esistenti verso client diversi grazie a quattro protocolli. UDDI (Universal Description Discovery and Integration) È un protocollo, nato da un consorzio formato da Microsoft, IBM e Ariba che permette di localizzare i WS di proprio interesse, non è altro che un protocollo comune per la ricerca di WS che può aiutare a trovare soluzioni in breve tempo e facendolo da un’unica fonte. Si suddivide in tre elenchi (http://www.uddi.org). 1. Pagine Bianche: contengono le informazioni anagrafiche delle imprese registrate. 2. Pagine Gialle: descrivono i prodotti e i servizi offerti. 3. Pagine Verdi: mostrano i termini tecnici attraverso i quali basare lo scambio informativo diretto tra i partner. Alcune aziende hanno il compito di far si che i repositori UDDI siano sempre disponibili che dei dati presenti in tali repositori sia fatto il backup e che sia, inoltre, garantita un’opera di replica, di sincronizzazione delle informazioni tra i diversi repositori in modo tale da non obbligare una determinata azienda a fare advertising, a registrare i propri servizi su ognuno di questi siti. Un metodo per trovare i siti che espongono servizi: DISCO (Discovery). WSDL Come può il client sapere a priori quali sono le operazioni messe a disposizione da un TPSIT 5 434 um 127 di server e in che modo invocarle? Un file WSDL è un documento XML che risolve questo problema: contiene la definizione di tutti i metodi, i parametri e i tipi restituiti per il dato WS. Le operazioni e i messaggi sono definiti in maniera astratta e solo in seguito sono legati (binding) ad un protocollo e ad un formato per definire un servizio concreto. Un file WSDL rappresenta quindi un contratto pubblicato dall’entità che espone il servizio e che è sottoscritto da tutti i client che hanno intenzione d’interagire con esso. Ogni WS deve esporre il proprio file WSDL. Per la difficoltà d’implementazione del formato, il programmatore non scrive a mano il file WSDL ma si affida a framework, come ASP.NET che consente la generazione dinamica del file WSDL. Definisce i metodi, i tipi di dati e i parametri. Definisce i messaggi. Definisce le operazioni come combinazione di messaggi: one-way, richiesta/risposta. Definisce i rapporti tra protocolli e formato dei messaggi: SOAP, HTTP GET/POST e MIME. Interrogando il WS con il parametro ?WSDL, si ottiene la sua descrizione formale, generata automaticamente dal framework utilizzato per lo sviluppo. A fronte dello scaricamento di questo file è possibile poi iniziare a instaurare un dialogo con la controparte per creare i messaggi, in particolare per creare il payload, il contenuto da inserire all’interno dei messaggi di SOAP per invocare il servizio stesso. È formato da cinque sezioni. 1. types. 2. message. 3. portType. 4. binding. 5. service. <message name="execQueryRequest"> <part name="strSql" type="xsd:string"/> </message> <message name="execQueryResponse"> <part name="return" type="tns:MyType"/> </message> Esempio, il WS deve ricevere una stringa (input) e restituire (output) in formato MyType. <xsd:complexType name="MyType"> <xsd:all> TPSIT 5 434 um 128 di <xsd:element name="ID" type="xsd:string" ID="ID"/> <xsd:element name="Applicazione" type="xsd:string" ID="ID" Applicazione="Applicazione"/> </xsd:all> </xsd:complexType> WSDL definisce quattro primitive di trasmissione che un endpoint può supportare. Modalità di trasmissione Descrizione One-way Il client spedisce un messaggio al servizio. Il client spedisce un messaggio al servizio e riceve una Request-respone risposta. Solicit-response Il servizio invia un messaggio ad un client. Notification Il servizio invia un messaggio ad un client e questo risponde. È possibile creare binding differenti per la stessa porta astratta ottenendo così più di una porta “concreta” che espone le stesse funzionalità con modalità diverse. Il servizio vero e proprio è poi composto da un insieme di porte, ognuna definita tramite il suo indirizzo di rete. La sezione binding è estensibile consentendo di accettare la definizione di altri protocolli di comunicazione. SOAP È un protocollo applicativo, basato su XML che fornisce le regole per permettere ad applicazioni distribuite di scambiarsi informazioni in un formato comune e di richiedere l’esecuzione di servizi remoti, tra un consumer e un provider. Le applicazioni si scambiano messaggi attraverso un pacchetto informativo che prende il nome di payload, una struttura XML complessa. Progettato per garantire l’interoperabilità. Non è legato ad un particolare protocollo di trasporto sottostante, l’unico vincolo è che sia in grado di trasportare il semplice testo, sebbene non esistano controindicazioni di sorta verso altri protocolli, si usa HTTP per le caratteristiche simili a quelle di SOAP. Non è legato ad un particolare linguaggio o SO. Un client SOAP è un’applicazione in grado di creare un documento XML che contiene le informazioni per eseguire un’operazione su un server. Un server SOAP è in grado d’interpretare i messaggi SOAP ricevuti eseguendo le operazioni specificate, restituendo il risultato al client in formato XML. I messaggi SOAP seguono il seguente formato. Il SOAP Envelope è l’elemento radice del documento XML che rappresenta un messaggio SOAP ed è composto da due elementi. 1. Header: opzionale, estende il messaggio contenendo informazioni utili al suo TPSIT 5 434 um 129 di processamento come ad esempio l’identità dell’utente, informazioni riguardo alla cifratura del documento o informazioni utili per il routing. 2. Body: obbligatorio, è un contenitore dove sono presenti le informazioni applicative per il destinatario del messaggio. XML I WS si basano su documenti XML e quindi sono un modo efficiente per creare dei servizi leggeri, standard e utilizzabili da qualsiasi dispositivo. Standard di tutte le applicazioni web, consente d’immagazzinare, trasportare e scambiare dati in modo del tutto agnostico rispetto alla piattaforma. Si può, inoltre, dire che esiste una fase di scoperta che è effettuata, ad esempio interrogando i repositori UDDI. È un PAC (Programmable Application Component) accessibile tramite i protocolli standard d’Internet, sfrutta gli aspetti positivi sia dei componenti sia del web: tutte le volte che si deve mettere in comunicazione un client con un server ciò di cui si ha bisogno è di un canale di comunicazione, lungo il quale fluisca dell’informazione che per essere comprensibile ad entrambi, occorre utilizzare un protocollo sul quale sono in accordo. Racchiude le funzionalità in una black box. Interagisce con altri servizi con un’interfaccia ben definita. Ha bisogno del supporto dei protocolli Internet, è stateless e utilizza un’interfaccia basata su messaggi definiti da un contratto. In COM questo tipo di protocollo esiste ma è un’implementazione di cui è proprietaria Microsoft così come del canale di comunicazione. Il modello dei WS è indipendente dal linguaggio di programmazione, dalla piattaforma e dal modello a oggetti. È possibile creare un WS con qualsiasi linguaggio su qualsiasi piattaforma. È possibile usufruire di un WS con qualsiasi linguaggio su qualsiasi piattaforma. Quando si porta in Internet questa metafora, si possono usare i canali di comunicazione già esistenti e già utilizzati in maniera non dipendente dal SO e dalla piattaforma, per esempio HTTP, SMTP (Simple Mail Transfer Protocol), FTP. Ciò che serve è anche un protocollo in grado di dare significato al flusso d’informazioni che transita lungo questo canale. Questi protocolli sono basati su particolari grammatiche XML, basati quindi su file di testo. TPSIT 5 434 um 130 di SICUREZZA Si usa la specifica WS-Security che è un’estensione standard dell’envelope SOAP e consente di definire token di sicurezza mediante i quali verificare l’autenticità di un consumer, oltre all’integrità e confidenzialità del messaggio SOAP. L’autenticazione del consumer al provider avviene in due modi. 1. Token di sicurezza: username e password. 2. Binary token: firmare digitalmente l’envelope SOAP. L’username token profile è una specifica che definisce i token di sicurezza di tipo username. Un elemento dell’header può avere alcuni attributi speciali, tra questi gli attributi mustUnderstand e actor. L’attributo mustUnderstand può assumere, nella specifica SOAP, i valori 1 o 0 (default), oppure i valori true o false. Se l’attributo mustUnderstand vale 1 o true, il destinatario del messaggio deve obbligatoriamente analizzare, “capire” e processare l’header. In caso contrario, il destinatario deve segnalare un errore o, più precisamente usando il linguaggio dei WS, deve inviare una segnalazione di fault al consumer. L’attributo actor consente di specificare l’endpoint, intermediario al quale è indirizzato l’elemento header. Infatti, un messaggio SOAP può viaggiare dal mittente al destinatario attraversando differenti intermediari lungo il percorso e non tutti gli header del messaggio sono necessariamente indirizzati al destinatario finale. Il valore di quest’attributo è un’URI e, se non indicato, il default è l’ultimo nodo nel message-path. Di solito, un header di sicurezza che contiene le credenziali del consumer dovrà essere obbligatoriamente processato, con attributo mustUnderstand uguale a 1 o true, dal provider del servizio. L’elemento XML <wsse:UsernameToken> è presente in un header WS-Security, per specificare l’username e la password di un utente. Elementi “figli” del TAG <wsse:UsernameToken> sono i TAG <wsse:Username> e <wsse:Password>. TPSIT 5 434 um 131 di Esempio, un envelope SOAP è esteso con un header WS-Security. <S11:Envelope xmlns:S11="..." xmlns:wsse=""> <S11:Header> ... <wsse:Security> <wsse:UsernameToken> <wsse:Username>Andrea</wsse:Username> <wsse:Password> password </wsse:Password> </wsse:UsernameToken> </wsse:Security> ... </S11:Header> ... </S11:Envelope> L’utilizzo dell’username security token che, se da un lato consente di specificare in modo standard e semplice le credenziali di un consumer, dall’altro presenta le seguenti controindicazioni di sicurezza. La password è in chiaro. Il colloquio tra consumer e provider può essere soggetto ad attacchi di sicurezza di tipo replay, ossia attacchi ripetuti al provider utilizzando lo stesso messaggio SOAP. Due elementi opzionali sono introdotti nel TAG <wsse:UsernameToken> per fornire una serie di contromisure agli attacchi di tipo replay. Sono gli elementi <wsse:Nonce> e <wsu:Created>. Un nonce è un valore casuale che il consumer del WS crea e include nel token di sicurezza della chiamata SOAP. Il provider del servizio conserva una cache dei nonce ricevuti, rigettando le invocazioni SOAP che includono un valore di nonce già ricevuto in precedenza. La gestione da parte del server di questa cache è un’attività onerosa in termini di risorse computazionali e pertanto insieme al valore di nonce il consumer include nell’invocazione SOAP anche un timestamp che riporta la data di creazione del nonce consentendo al server di limitare la dimensione della cache rifiutando le invocazioni SOAP obsolete. I soli elementi nonce e timestamp non sono però da soli sufficienti: per non inviare in chiaro la password e per limitare gli attacchi di tipo replay il valore dell’elemento <wsse:Password> è il digest, in codifica Base64, della concatenazione del nonce, del timestamp e della password vera e propria. Password_Digest = Base64 ( SHA-1 ( nonce + created + password)) Esempio, un envelope SOAP è esteso con un username token che usa nonce, timestamp di creazione e il valore digest per la password. <S11:Envelope xmlns:S11="..." xmlns:wsse="..." xmlns:wsu= "..."> <S11:Header> ... <wsse:Security> <wsse:UsernameToken> <wsse:Username>pippo</wsse:Username> <wsse:Password Type="...#PasswordDigest"> TPSIT 5 434 um 132 di .... </wsse:Password> <wsse:Nonce>.........</wsse:Nonce> <wsu:Created>2013-07-07T07:27:37Z</wsu:Created> </wsse:UsernameToken> </wsse:Security> ... </S11:Header> ... </S11:Envelope> L’attributo Type del TAG <wsse:Password> ha il valore di #PasswordDigest per indicare che il valore del TAG è proprio il digest della password concatenata al nonce e al timestamp in codifica Base64. TPSIT 5 434 um 133 di VISUAL STUDIO: WS BASATO SU XML Aprire un progetto, File/Nuovo/Progetto… (CTRL+N), nella finestra Nuovo progetto, modificare l’opzione e selezionare il .NET Framework 3.5; nel riquadro Modelli installati selezionare Visual Basic/Web/Applicazione servizio Web ASP.NET. Dopo aver scelto il nome del progetto e il percorso, confermare per creare l’applicazione. Visual Studio mette a disposizione lo scheletro di un WS che non fa altro che restituire al client la stringa “Ciao, mondo!”. TPSIT 5 434 um 134 di Per creare WS con il .NET Framework si usano i file ASMX di ASP.NET. File MYWEBEXTENSION.VB Definisce le proprietà disponibili nello spazio dei nomi My per i progetti web. File ASSEMBLYINFO.VB Informazioni generali dell’applicazione, dal nome del progettista al copyright. RIFERIMENTI Librerie esterne utilizzate dall’applicazione. Cartella APP_DATA Contiene file di dati dell’applicazione, inclusi i file MDF (MicroSoft Data File), i file XML e altri file di archivio dati, è utilizzata da ASP.NET per archiviare un DB locale dell’applicazione. File WEB.CONFIG File di configurazione dell’applicazione, nel quale si trovano i namespace importati e le sezioni per impostare il livello di sicurezza. FileSERVICE1.ASMX Codice generato automaticamente da Visual Studio, definisce il metodo HelloWorld. La dichiarazione namespace identifica il servizio, inoltre rende più chiare la finalità e la modalità di utilizzo a chi dovrà interrogare il servizio. Per creare un WS basta ereditare Inherits System.Web.Services.WebService. Imports System.Web.Services Imports System.Web.Services.Protocols Imports System.ComponentModel ' Per consentire la chiamata di questo servizio Web dallo script utilizzando ASP.NET ' AJAX, rimuovere il commento dalla riga seguente. ' <System.Web.Script.Services.ScriptService()> _ <System.Web.Services.WebService(Namespace:="http://tempuri.org/")> _ <System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _ <ToolboxItem(False)> _ Public Class Service1 Inherits System.Web.Services.WebService <WebMethod()> _ Public Function HelloWorld() As String Return "Ciao, mondo!" End Function End Class Il namespace http://tempuri.org/ è un URI temporaneo che dev’essere obbligatoriamente indicato in assenza di un URI alternativo, per esempio il nome di dominio dell’organizzazione ma non deve indicare necessariamente un dominio o un URL effettivamente esistente. La parte più importante di codice è costituita dalla classe che eredita da System.Web.Services.WebService. In OOP si decide quali metodi esporre come pubblici, protetti o privati. Nei WS è necessario specificare anche quali metodi siano visibili “lato web”, ossia quali metodi siano utilizzabili o meglio invocabili, dai client che si collegheranno. TPSIT 5 434 um 135 di All’interno di questa classe, sono inseriti metodi con un attributo <WebMethod()> per indicare che si tratta di un metodo del WS. Nel modello predefinito si ha a disposizione solo un metodo, HelloWorld che restituisce la stringa ma si può modificarlo con uno diverso e aggiungere molti altri metodi al WS. Le informazioni relative a nome e posizione, sono sufficienti a Visual Studio per generare il codice di un WS, pronto per essere interrogato. Visual Studio include un tool di sviluppo web, Visual Studio Development Server che integra un server web per consentire di sperimentare subito le potenzialità di ASP.NET e l’applicazione sarà visualizzata all’interno del browser web predefinito. Il sito web del .NET Framework è noto come ASP.NET. I mondi delle applicazioni desktop e delle applicazioni web convergono con .NET. Per realizzare ed eseguire un’applicazione web è necessario che nel sistema sia installato il .NET Framework e un web server in grado d’interpretare ed eseguire i file ASP.NET che hanno estensione ASPX. Le pagine non sono altro che classi ed ereditano le funzionalità di base dalla classe Page come una Windows Form eredita dalla classe Form. Contemporaneamente all’avvio del servizio, sarà avviata un’istanza d’Internet Explorer che mostrerà la pagina, in altre parole l’indirizzo del servizio. Il numero di porta utilizzato nell’indirizzo del servizio è generato in modo casuale. Per impostare un numero di porta scelto dal programmatore, si deve configurarlo nelle proprietà dell’applicazione. Fare clic su Progetto/Proprietà di primoWs…, nella finestra delle proprietà, selezionare la scheda Web: si vede una serie d’informazioni relative all’applicazione web. La parte che interessa è quella centrale, nella sezione Server, infatti, è impostata l’opzione Assegnazione automatica porta, mentre per impostare una porta fissa si deve selezionare la voce Porta specifica e impostare il numero di porta da utilizzare. È aperta una pagina web con Internet Explorer avente URL simile a quello seguente. TPSIT 5 434 um 136 di TPSIT 5 434 um 137 di Si utilizza localhost al posto del dominio seguito dai due punti (:), dal numero della porta del web server locale (1126), quindi il nome del servizio e la sua estensione SERVICE1.ASMX. Nella finestra del browser ci sono delle informazioni, consigli sulla configurazione del namespace da assegnare al servizio e alcuni esempi di codice nei linguaggi Visual C#, Visual Basic e Visual C++; nella parte inferiore, poi, ci sono alcuni link a documenti riguardanti gli standard delle tecnologie indicate. Questo servizio web utilizza http://tempuri.org/ come spazio dei nomi predefinito. Consiglio: modificare lo spazio dei nomi predefinito prima della pubblicazione del servizio web XML. Per ogni servizio web XML è necessario uno spazio dei nomi univoco per consentire alle applicazioni client di distinguerlo dagli altri servizi disponibili sul web. Per i servizi web XML in fase di sviluppo è disponibile l'indirizzo http://tempuri.org/ ma quelli pubblicati devono usare uno spazio dei nomi permanente. Il servizio web XML dev’essere identificato da uno spazio dei nomi controllato dall’amministratore. Ad esempio, è possibile usare il nome di dominio Internet della propria società come parte dello spazio dei nomi. Anche se molti spazi dei nomi di servizi web XML sembrano URL, non fanno necessariamente riferimento a risorse effettive sul web, gli spazi dei nomi dei servizi web XML sono URI. Per i servizi web XML creati con ASP.NET, lo spazio dei nomi predefinito può essere modificato utilizzando la proprietà namespace dell’attributo WebService. TPSIT 5 434 um 138 di L’attributo WebService è applicato alla classe che contiene i metodi del servizio web XML. Nell'esempio di codice sottostante, lo spazio dei nomi è impostato su. http://microsoft.com/webservices/: Visual C# [WebService(Namespace="http://microsoft.com/webservices/")] public class MyWebService { // implementazione } Visual Basic <WebService(Namespace:="http://microsoft.com/webservices/")> Public Class MyWebService ' implementazione End Class C++ [WebService(Namespace="http://microsoft.com/webservices/")] public ref class MyWebService { // implementazione }; Per ulteriori informazioni sugli spazi dei nomi XML, vedere la raccomandazione W3C riguardante gli spazi dei nomi in XML (informazioni in lingua inglese). Per ulteriori informazioni su WSDL, vedere la specifica WSDL (informazioni in lingua inglese). Per ulteriori informazioni sugli URI, vedere la RFC 2396 (informazioni in lingua inglese). Per visualizzare le informazioni sul servizio, in formato WSDL, si deve aggiungere la stringa ?WSDL all’URL precedente http://localhost:14658/Service1.asmx?WSDL oppure si può semplicemente cliccare sul link descrizione del servizio. Descrizione del servizio in formato WSDL. <?xml version="1.0" encoding="UTF-8"?> -<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://tempuri.org/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" TPSIT 5 434 um 139 di xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://tempuri.org/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"> -<wsdl:types> -<s:schema targetNamespace="http://tempuri.org/" elementFormDefault="qualified"> -<s:element name="HelloWorld"> <s:complexType/> </s:element> -<s:element name="HelloWorldResponse"> -<s:complexType> -<s:sequence> <s:element name="HelloWorldResult" type="s:string" maxOccurs="1" minOccurs="0"/> </s:sequence> </s:complexType> </s:element> </s:schema> </wsdl:types> -<wsdl:message name="HelloWorldSoapIn"> <wsdl:part name="parameters" element="tns:HelloWorld"/> </wsdl:message> -<wsdl:message name="HelloWorldSoapOut"> <wsdl:part name="parameters" element="tns:HelloWorldResponse"/> </wsdl:message> -<wsdl:portType name="Service1Soap"> -<wsdl:operation name="HelloWorld"> <wsdl:input message="tns:HelloWorldSoapIn"/> <wsdl:output message="tns:HelloWorldSoapOut"/> </wsdl:operation> </wsdl:portType> -<wsdl:binding name="Service1Soap" type="tns:Service1Soap"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/> -<wsdl:operation name="HelloWorld"> <soap:operation style="document" soapAction="http://tempuri.org/HelloWorld"/> <wsdl:input> <soap:body use="literal"/> </wsdl:input> -<wsdl:output> <soap:body use="literal"/> </wsdl:output> </wsdl:operation> </wsdl:binding> -<wsdl:binding name="Service1Soap12" type="tns:Service1Soap"> <soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/> -<wsdl:operation name="HelloWorld"> <soap12:operation style="document" soapAction="http://tempuri.org/HelloWorld"/> <wsdl:input> <soap12:body use="literal"/> </wsdl:input> -<wsdl:output> <soap12:body use="literal"/> </wsdl:output> </wsdl:operation> </wsdl:binding> -<wsdl:service name="Service1"> -<wsdl:port name="Service1Soap" binding="tns:Service1Soap"> <soap:address location="http://localhost:1126/Service1.asmx"/> </wsdl:port> -<wsdl:port name="Service1Soap12" binding="tns:Service1Soap12"> <soap12:address location="http://localhost:1126/Service1.asmx"/> </wsdl:port> TPSIT 5 434 um 140 di </wsdl:service> </wsdl:definitions> Se invece, si fa clic sul link HelloWorld, nome che corrisponde al nome del metodo esposto dal servizio, si ottiene un’altra pagina come questa. La pagina contiene degli esempi di richiesta e di risposta. SOAP 1.1 Di seguito è riportato un esempio di richiesta e risposta SOAP 1.1. I segnaposto devono essere sostituiti con i valori appropriati. POST /WebService1.asmx HTTP/1.1 Host: localhost Content-Type: text/xml; charset=utf-8 Content-Length: length SOAPAction: "http://tempuri.org/HelloWorld" <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <HelloWorld xmlns="http://tempuri.org/" /> </soap:Body> </soap:Envelope> HTTP/1.1 200 OK Content-Type: text/xml; charset=utf-8 Content-Length: length <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> TPSIT 5 434 um 141 di <HelloWorldResponse xmlns="http://tempuri.org/"> <HelloWorldResult>string</HelloWorldResult> </HelloWorldResponse> </soap:Body> </soap:Envelope> SOAP 1.2 Di seguito è riportato un esempio di richiesta e risposta SOAP 1.2. I segnaposto devono essere sostituiti con i valori appropriati. POST /WebService1.asmx HTTP/1.1 Host: localhost Content-Type: application/soap+xml; charset=utf-8 Content-Length: length <?xml version="1.0" encoding="utf-8"?> <soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope"> <soap12:Body> <HelloWorld xmlns="http://tempuri.org/" /> </soap12:Body> </soap12:Envelope> HTTP/1.1 200 OK Content-Type: application/soap+xml; charset=utf-8 Content-Length: length <?xml version="1.0" encoding="utf-8"?> <soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope"> <soap12:Body> <HelloWorldResponse xmlns="http://tempuri.org/"> <HelloWorldResult>string</HelloWorldResult> </HelloWorldResponse> </soap12:Body> </soap12:Envelope> HTTP POST Di seguito è riportato un esempio di richiesta e risposta HTTP POST. I segnaposto devono essere sostituiti con i valori appropriati. POST /WebService1.asmx/HelloWorld HTTP/1.1 Host: localhost Content-Type: application/x-www-form-urlencoded Content-Length: length HTTP/1.1 200 OK Content-Type: text/xml; charset=utf-8 Content-Length: length <?xml version="1.0" encoding="utf-8"?> <string xmlns="http://tempuri.org/">string</string> TPSIT 5 434 um 142 di Fare clic sul pulsante Richiama per ottenere il risultato fornito dal metodo HelloWorld che restituisce il seguente risultato in formato XML. Per installare un WS in IIS, basta copiare i file che lo compongono nella cartella che ospiterà il servizio, tuttavia in IIS la cartella in questione dev’essere configurata come applicazione, per far questo si può procedere in due modi. 1. Primo modo Creare una cartella di sotto a C:\INETPUB\WWWROOT. Fare clic su Start/Tutti i programmi/Strumenti di amministrazione e avviare Gestione Internet Information Services (IIS). Fare clic con il tasto destro sulla cartella e selezionare Converti in applicazione. Nel riquadro Pool di applicazioni… fare clic sul pulsante Seleziona… nella casella a discesa della versione di ASP.NET installata, selezionare quella relativa al WS sviluppato. TPSIT 5 434 um 143 di Per procedere all’installazione del WS anziché copiare i file, modificando manualmente alcuni riferimenti, si ha a disposizione una modalità di pubblicazione molto più comoda, veloce ed efficiente: la pubblicazione diretta da Visual Studio. Nella finestra Esplora Soluzioni, fare clic con il tasto destro del mouse sul nome del progetto e scegliere Pubblica…, apparirà una finestra di dialogo seguente. Si è indicato il metodo di pubblicazione File system e si è indicato l’indirizzo dove trovare il WS che, in questo caso, si trova nel web server C:\INETPUB\WWWROOT\WS. Si è selezionata l’opzione Elimina tutti i file esistenti prima della pubblicazione, per evitare che eventuali file preesistenti possano rimanere nella cartella dell’applicazione e creare qualche problema. Una volta concluso il completamento dei dati richiesti, premere il pulsante Pubblica. 2. Secondo modo In questo caso la cartella che conterrà i file sarà messa al di fuori di WWWROOT. Fare clic su Start/Tutti i programmi/Strumenti di amministrazione e avviare Gestione Internet Information Services (IIS). Portarsi nel sito web principale, cliccare con il tasto destro e selezionare Aggiungi directory virtuale… TPSIT 5 434 um 144 di Si avvierà una procedura guidata. Al secondo punto bisogna inserire un alias per la directory virtuale. Usare un’etichetta significativa ad esempio il nome del progetto. Nel passo successivo selezionare la cartella fisica cui la directory virtuale è associata. Impostare i permessi per la nuova cartella, selezionare almeno il permesso d’esecuzione. Se IIS restituisce errore, è probabile che non siano stati creati gli utenti corretti per eseguire le applicazioni ASP.NET in Windows. Per risolvere il problema portarsi nella cartella d’installazione del .NET Framework. C:\Windows\Microsoft.NET\Framework\v4.0.30319>aspnet_regiis /? Utilità di amministrazione (4.0.30319) per l'installazione e la disinstallazione di ASP.NET nel computer locale. Copyright (c) Microsoft Corporation. All rights reserved. -- OPZIONI DI REGISTRAZIONE DI ASP.NET --i Installazione della versione di ASP.NET e aggiornamento della configurazione di IIS a livello radice per l'utilizzo di questa versione di ASP.NET. -ir Installazione della versione di ASP.NET eseguendo solo la registrazione. Non verranno apportate modifica alle applicazioni Web per l'utilizzo di questa versione. -iru Installazione della versione di ASP.NET. Se sono presenti applicazioni esistenti che utilizzano ASP.NET, la configurazione di IIS non verrà modificata per l'utilizzo di questa versione. -enable Se viene specificato -enable con -i, -ir o -r, ASP.NET verrà abilitato nella console di sicurezza di IIS (IIS 6.0 o versione successiva). -disable Se viene specificato -disable con -i, -ir o -r, ASP.NET verrà disabilitato nella console di sicurezza di IIS (IIS 6.0 o versione successiva). -s <percorso> Installazione degli scriptmap per la versione corrente nel percorso specificato, in modo ricorsivo. Es: aspnet_regiis.exe -s W3SVC/1/ROOT/SampleApp1 -sn <percorso> Installazione degli scriptmap per la versione corrente nel percorso specificato, in modo non ricorsivo. -r Installazione della versione corrente di ASP.NET e aggiornamento degli scriptmap nella radice della metabase di IIS e in tutte le sottodirectory. Gli scriptmap esistenti vengono aggiornati alla versione corrente indipendentemente dalle versioni originali. TPSIT 5 434 um 145 di -u Disinstallazione della versione corrente di ASP.NET. Gli scriptmap esistenti con tale versione vengono rimappati alla più recente versione di ASP.NET ancora installata nel computer. -ua Disinstallazione di tutte le versioni di ASP.NET sul computer. -k <percorso> Rimozione ricorsiva di tutti gli scriptmap di tutte le versioni di ASP.NET dal percorso specificato, (opzione non supportata su Windows Vista e versioni successive). Es.: aspnet_regiis.exe -k W3SVC/1/ROOT/SampleApp1 -kn <percorso> Rimozione non ricorsiva di tutti gli scriptmap di tutte le versioni di ASP.NET dal percorso specificato (opzione non supportata su Windows Vista e versioni successive). -lv Elenco di tutte le versioni di ASP.NET installate nel computer, con stato e percorso di installazione. -lk Elenco dei percorsi di tutte le chiavi della metabase di IIS in cui sono presenti scriptmap di ASP.NET, indicandone la versione. Le chiavi che ereditano scriptmap di ASP.NET da una chiave padre non vengono visualizzate (opzione non supportata su Windows Vista e versioni successive). -c Installazione degli script lato client per la versione corrente nella sottodirectory aspnet_client della directory di ogni sito di IIS. -e Rimozione degli script lato client per la versione corrente dalla sottodirectory aspnet_client della directory di ogni sito di IIS. -ea Rimozione degli script lato client per tutte le versioni dalla sottodirectory aspnet_client della directory di ogni sito di IIS. -ga <utente> Concessione all'utente specificato dei diritti di accesso utente o gruppo alla metabase di IIS e alle altre directory utilizzate da ASP.NET. -- OPZIONI PER LA CRITTOGRAFIA DELLA CONFIGURAZIONE --pe sezione Crittografia della sezione di configurazione. Argomenti facoltativi: [-prov provider] Esecuzione della crittografia tramite il provider specificato. [-app percorso-virtuale] Esecuzione della crittografia al percorso virtuale specificato. Il percorso virtuale deve iniziare con una barra rovesciata. Se si utilizza il carattere "/", viene fatto riferimento alla radice del sito. Se -app non è specificato, verrà crittografato il file web.config radice. [-site nome-o-ID-sito] Il sito del percorso virtuale specificato in -app. Se non è specificato viene utilizzato il sito Web predefinito. [-location sottopercorso] Sottopercorso. [-pkm] Crittografia/decrittografia di machine.config anziché di web.config. -pd sezione Decrittografia della sezione di configurazione. Argomenti facoltativi: [-app percorso-virtuale] Decrittografia al percorso virtuale specificato. Il percorso virtuale deve iniziare con una barra rovesciata. Se si utilizza il carattere TPSIT 5 434 um 146 di "/", viene fatto riferimento alla radice del sito. Se -app non è specificato, verrà decrittografato il file web.config radice. [-site nome-o-ID-sito] Il sito del percorso virtuale specificato in -app. Se non è specificato viene utilizzato il sito Web predefinito. [-location sottopercorso] Sottopercorso. [-pkm] Crittografia/decrittografia di machine.config anziché di web.config. -pef sezione dir-fisica-app-web Crittografia della sezione di configurazione. Argomenti facoltativi: [-prov provider] Esecuzione della crittografia tramite il provider specificato. -pdf section web-app-physical-dir Decrittografia della sezione di configurazione. -pc contenitore Creazione di una coppia di chiavi RSA in ContainerName. Argomenti facoltativi: [-size dimensione-chiave] Dimensione della chiave. Il valore predefinito è 1024. [-pku] Utilizzo del contenitore dell'utente anziché quello del computer. [-exp] Impostazione delle chiavi private come esportabili. [-csp provider] Provider Csp da utilizzare. -pz contenitore Eliminazione di ContainerName. Argomenti facoltativi: [-pku] Utilizzo del contenitore dell'utente anziché quello del computer. -pi file contenitore Importazione di una coppia di chiavi RSA dal file XML specificato. Argomenti facoltativi: [-pku] Utilizzo del contenitore dell'utente anziché quello del computer. [-exp] Creazione di chiavi esportabili. [-csp provider] Provider Csp da utilizzare. -px file contenitore Esportazione di una coppia di chiavi RSA nel file XML. Argomenti facoltativi: [-pku] Utilizzo del contenitore dell'utente anziché quello del computer. [-pri] Inclusione di chiavi private. [-csp provider] Provider Csp da utilizzare. -pa account contenitore Aggiunta dei diritti di accesso dall'account al contenitore. Argomenti: [-pku] Utilizzo del contenitore dell'utente anziché quello del computer. [-csp provider] Provider Csp da utilizzare. [-full] Aggiunta di diritti di accesso completo (per impostazione predefinita è consentito solo l'accesso in lettura). -pr account contenitore Rimozione dei diritti di accesso per l'account dal contenitore. Argomenti: [-pku] Utilizzo del contenitore dell'utente anziché TPSIT 5 434 um 147 di quello del computer. [-csp provider] Provider Csp da utilizzare. -- OPZIONI DI ACCESSO REMOTO DELLA CONFIGURAZIONE --config+ Abilitazione dell'accesso remoto per la configurazione. -configDisabilitazione dell'accesso remoto per la configurazione. C:\Windows\Microsoft.NET\Framework\v4.0.30319>aspnet_regiis –i<INVIO> Sono automaticamente creati gli utenti necessari a far funzionare IIS con ASP.NET. Per utilizzare il WS, creare un nuovo progetto VB.NET Windows Form e trascinare un pulsante sul form. Fare clic con il tasto destro in Esplora soluzioni, sul nome del progetto, selezionare Aggiungi riferimento al servizio… Nella finestra che compare, indicare l’URL che espone il WS. Fare clic su OK. Fare doppio clic sul bottone per gestire il codice relativo all’evento. Public Class Form1 Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click Dim ws As New ServiceReference1.WebService1SoapClient End Sub TPSIT 5 434 um 148 di End Class Microsoft commercializza MapPoint esponendo le funzionalità dell’applicazione come WS. In altre parole, l’applicazione può interrogare MapPoint WS per conoscere ad esempio il percorso stradale da compiere per arrivare da un luogo ad un altro. Ciascun’interrogazione ha un costo per chi la compie. Ad esempio, per utilizzare il servizio web di MapPoint, dopo aver ricevuto da Microsoft le credenziali di accesso, si deve aggiungere un riferimento web al progetto. Sono visualizzati tutti i metodi che il servizio espone e il nome da assegnare al riferimento web: net.mappoint.staging. Da questo momento è possibile usare tutti i metodi esposti dal servizio come se fossero relativi ad un oggetto locale. L’aggiunta del riferimento ha, infatti, creato una classe chiamata proxy che ha lo scopo di rendere trasparente, per il programmatore, il WS. Esempio. File WEBSERVICE1.ASMX Non basta avere un metodo che, senza accettare parametri, mandi un saluto ma occorre modificare il codice con due metodi pubblici, di cui il primo accetta un parametro e ritorna un valore. I metodi devono essere richiamabili come servizi web, oltre che pubblici, devono essere preceduti dalla dichiarazione <WebMethod()>. TPSIT 5 434 um 149 di Imports System.Web.Services Imports System.Web.Services.Protocols Imports System.ComponentModel ' Per consentire la chiamata di questo servizio Web dallo script utilizzando ASP.NET AJAX, rimuovere il commento dalla riga seguente. ' <System.Web.Script.Services.ScriptService()> _ <System.Web.Services.WebService(Namespace:="http://www.ubertini.it/")> _ <System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _ <ToolboxItem(False)> _ Public Class WebService1 Inherits System.Web.Services.WebService <WebMethod()> _ Public Function Saluta(ByVal saluto As String) As String Return "Ciao " + saluto End Function <WebMethod()> _ Public Function Chisei() As String Return "Sono il primo servizio web" End Function End Class Compilare ed eseguire il servizio. Per provare il servizio è sufficiente cliccare sul link Saluta, la pagina web che si aprirà consentirà d’inserire un nome: in realtà si sta simulando, via web, il passaggio di parametri che dovrà avvenire da codice. TPSIT 5 434 um 150 di Inserendo una stringa e cliccando sul pulsante Richiama, si ottiene il documento XML che rappresenta la risposta del metodo all’invocazione. Tutti i meccanismi di scambio dati SOAP rimangono nascosti al programmatore che non si deve preoccupare d’implementare la parte più a basso livello del codice. Esempio, DB. <WebMethod()> _ Public Function query(ByVal strSql As String, ByVal strConnect As String) As DataSet Dim oDataSet As New DataSet Dim objConn As New OleDb.OleDbConnection(strConnect) Try objConn.Open() Dim objcommand As New OleDb.OleDbCommand(strSql, objConn) Dim oAdapter As New OleDb.OleDbDataAdapter(objcommand) oAdapter.TableMappings.Add("Table", "Table0") oAdapter.Fill(oDataSet) Catch ex As Exception TPSIT 5 434 um 151 di Throw New Exception(ex.Message, ex) Finally objConn.Close() End Try Return oDataSet End Function Il <WebMethod()> riceve una stringa di connessione al DB e il comando SQL da eseguire. Grazie ad ADO (ActiveX Data Objects) si può poi eseguire la query e restituire il dataset, si può anche prevedere un metodo che esegua un inserimento o una modifica. <WebMethod()> Public Function nonQuery(ByVal command As String, ByVal strConnect As String) As Integer Dim objConn As New OleDb.OleDbConnection(strConnect) Try objConn.Open() Dim objcommand As New OleDb.OleDbCommand(command, objConn) Return objcommand.ExecuteNonQuery Catch ex As Exception Throw New Exception(ex.Message, ex) Finally ' chiusa connessione objConn.Close() End Try End Function Il parametro restituito è il numero di record coinvolti nel comando. Compilare ed eseguire il servizio. Il WS è pronto per essere usato da una qualsiasi applicazione client. TPSIT 5 434 um 152 di VISUAL STUDIO: WS BASATO SU WCF Creare il progetto WS WCF è una nuova tecnologia basata su XML che ha un approccio uniforme rispetto a vari tipi di comunicazione, non solo per i WS. In questo modo si può scrivere codice che varia di poco tra un tipo di comunicazione e l’altro, rendendo più semplice e produttiva l’attività di sviluppo. Aprire un progetto, File/Nuovo/Progetto… (CTRL+MAIUSC+N), nella finestra Nuovo progetto, nel riquadro Modelli selezionare Visual C#/WCF/Libreria del servizio WCF. Visual Studio mette a disposizione lo scheletro di un WS. File ISERVICE1.CS È il codice dell’interfaccia usata per definire il servizio e i due metodi che esporrà al client. 1. GetData permette al servizio di ricevere un valore numerico e di restituire lo stesso valore in formato stringa. 2. GetDataUsingDataContract permette al servizio di ricevere un tipo di dato composto da più valori e di restituirlo al consumatore del servizio. TPSIT 5 434 um 153 di In sostanza, questo WS non è altro che un servizio “echo” che restituisce al mittente tutto ciò che riceve. Tutto questo è predisposto in modo automatico e non c’è la necessità di alcun intervento da parte del programmatore, se non per personalizzare i metodi che il WS deve esporre. using System.Runtime.Serialization; using System.ServiceModel; namespace PrimoWebServiceWCF { /* ServiceContract descrive le operazioni che un client può effettuare su un * servizio */ [ServiceContract] public interface IService1 { // OperationContract definisce le operazioni consentite per quel contratto [OperationContract] string GetData(int value); [OperationContract] CompositeType GetDataUsingDataContract(CompositeType composite); // aggiungere qui le operazioni del servizio } /* DataContract descrive i dati scambiati con un servizio: definisce la classe che * implementa un contratto di dati che potranno essere serializzati e scambiati fra * servizio e client */ [DataContract] public class CompositeType { bool boolValue = true; string stringValue = "Hello "; // DataMember definisce le proprietà o i campi che devono essere serializzati [DataMember] public bool BoolValue { get { return boolValue; } set { boolValue = value; } } [DataMembe] public string StringValue { get { return stringValue; } set { stringValue = value; } } } } Anche se possono essere applicati indistintamente su class, il loro utilizzo più comune si ha con l’interface, perché nel rispetto di una corretta SOA in cui il contratto di servizio è differente dalla sua implementazione. File SERVICE1.CS Implementa i metodi che devono essere esposti dal WS e definiti dall’interfaccia. using System; namespace PrimoWebServiceWCF { public class Service1 : IService1 { public string GetData(int value) { return string.Format("Hai inviato il valore seguente: {0}", value); } TPSIT 5 434 um 154 di public CompositeType GetDataUsingDataContract(CompositeType composite) { if (composite == null) throw new ArgumentNullException("composite"); if (composite.BoolValue) composite.StringValue += "Vero"; return composite; } } } Compilare ed eseguire il servizio, si avvia come se si trattasse di un’applicazione Windows; cliccando sull’icona nell’area di notifica si ottiene l’apertura di una finestra che fornisce informazioni sul servizio WCF: l’indirizzo del servizio e il suo stato. Collaudare il WS Si apre la finestra di dialogo che rappresenta un’applicazione client che è creata automaticamente e in modo totalmente trasparente al programmatore, per consentirgli di testare il WS, per esempio invocando i metodi. Questo permette di sapere se il servizio web funziona correttamente o se si devono apportare delle modifiche, senza bisogno di creare subito il client, con il dubbio che sia TPSIT 5 434 um 155 di quest’ultimo a non funzionare. Nella parte sinistra della finestra ci sono i servizi attivati e i metodi che si possono utilizzare con tali servizi; nella parte destra ci sono rispettivamente i dati della richiesta che s’invia al WS attraverso il metodo chiamato e i dati che sono restituiti dal WS stesso. Fare doppio clic sul nome del metodo GetData, poi modificare il valore nella corrispondente cella del riquadro Richiesta e cliccare sul pulsante Richiama. Apparirà un messaggio informativo sui rischi connessi sull’uso di servizi che inviano informazioni in rete. Fare clic su OK. Nel riquadro Risposta appariranno i dati restituiti dal servizio, attraverso il metodo GetData. TPSIT 5 434 um 156 di WCF permette di definire le caratteristiche del servizio in due modalità. 1. Amministrativamente: è il file di configurazione, eventualmente combinato con l’utilizzo di attributi. 2. Programmaticamente: si configura il servizio completamente da codice. Nella sezione che si trova a sinistra, nella finestra del client di prova, si trova anche la voce File di configurazione, fare doppio clic per visualizzarlo. Si vede la configurazione dell’endpoint: la sezione <configuration> descrive l’endpoint del servizio BasicHttpBinding_IService1 che ha il seguente indirizzo e utilizzabile con un binding di tipo <basicHttpBinding> e un contratto IService1. http://localhost:8733/Design_Time_Addresses/PrimoWebServiceWCF/Service1 <?xml version="1.0" encoding="utf-8"?> <configuration> <system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService1" sendTimeout="00:05:00" /> </basicHttpBinding> </bindings> <client> <endpoint address = "http://localhost:8733/Design_Time_Addresses/PrimoWebServiceWCF/Service1/" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1" contract="IService1" name="BasicHttpBinding_IService1" /> </client> </system.serviceModel> </configuration> Le comunicazioni avvengono con l’utilizzo di documenti XML che sono scambiati nell’una e nell’altra direzione, per specificare cosa è inviato dal client al servizio e viceversa. La scheda XML visualizza il contenuto dei documenti XML scambiati al momento della richiesta del client al WS e al momento della risposta del WS al web client. Richiesta <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header> TPSIT 5 434 um 157 di <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/ISe rvice1/GetData</Action> </s:Header> <s:Body> <GetData xmlns="http://tempuri.org/"> <value>7</value> </GetData> </s:Body> </s:Envelope> Risposta <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header /> <s:Body> <GetDataResponse xmlns="http://tempuri.org/"> <GetDataResult>Hai inviato il valore seguente: 7</GetDataResult> </GetDataResponse> </s:Body> </s:Envelope> Allo stesso modo, con un doppio clic sul nome di metodo GetDataUsingDataContract si possono visualizzare i dati che sono inviati al corrispondente metodo del WS e cliccando sul pulsante Richiama si possono ottenere i dati restituiti dal servizio. Hosting in IIS Il self-hosting di servizi WCF soffre di alcune limitazioni. Per esempio, per scalare un’applicazione perchè deve gestire un maggiore carico di richieste, si è costretti a implementare manualmente tutta la logica di gestione dei processi, di multithreading, di sicurezza e identity. La soluzione in questi casi è data da IIS che consente di ospitare servizi WCF all’interno dei processi solitamente dedicati ad ASP.NET, ereditandone tutte le caratteristiche. Un servizio WCF, per essere ospitato in IIS, dev’essere configurato attraverso l’utilizzo dell’estensione SVC. Per installare un WS in IIS, creare la cartella seguente. TPSIT 5 434 um 158 di C:\INETPUB\WWWROOT\PRIMOWEBSERVICEWCF Quindi, nella finestra Esplora Soluzioni del progetto Visual Studio, fare clic con il tasto destro del mouse sul nome del progetto e scegliere Pubblica…. Fare clic su Start/Tutti i programmi/Strumenti di amministrazione e avviare Gestione Internet Information Services (IIS). Fare clic con il tasto destro sulla cartella e selezionare Converti in applicazione. Il sistema genera due file: WEB.CONFIG e PRIMOWEBSERVICEWCF.SERVICE1.SVC. TPSIT 5 434 um 159 di Il file con estensione SVC, similarmente a quanto è fatto nelle pagine ASPX o ASMX, ha una direttiva @ ServiceHost il cui scopo è quello d’indicare qual è il servizio ospitato. <%@ ServiceHost Service="PrimoWebServiceWCF.Service1" %> L’applicazione funziona, il servizio è pronto per l’uso, è sufficiente aprire il browser e digitare l’indirizzo seguente. http://127.0.0.1/PrimoWebServiceWCF/PrimoWebServiceWCF.Service1.svc È generato un file di configurazione e un file di codice contenente la classe Client. Aggiungere i due file all’applicazione client e utilizzare la classe Client generata per chiamare il servizio. Visual C# class Test { static void Main() { Service1Client client = new Service1Client(); /* utilizzare la variabile 'client' per chiamare le operazioni nel servizio * chiudere sempre il client */ client.Close(); } } Visual Basic Class Test Shared Sub Main() Dim client As Service1Client = New Service1Client() ' utilizzare la variabile 'client' per chiamare le operazioni nel servizio ' chiudere sempre il client client.Close() End Sub TPSIT 5 434 um 160 di End Class Creare il client per il WS Una volta definito l’endpoint, per interagire con esso è necessario progettare una classe proxy che sul client si occupa d’inoltrare le richieste verso lo specifico endpoint, rispettandone le caratteristiche. Il proxy client, infatti, è la classe in grado di preparare le richieste e formattare i messaggi nella modalità richiesta dal servizio e dev’essere il più disaccoppiato possibile dal servizio. Ogni applicazione in grado d’inviare una richiesta HTTP può fungere da client per il WS e non è necessario che sia un’applicazione .NET o Windows. Ci sono due possibilità per generare i proxy client: CLI e Visual Studio. C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\svcutil.exe targetNamespace associano tutti i targetNamespaces senza un mapping esplicito allo spazio dei nomi CLR. Impostazione predefinita: deriva dallo spazio dei nomi del documento di schema per i contratti dati. Lo spazio dei nomi predefinito viene utilizzato per tutti gli altri tipi creati. (Forma breve: /n) /messageContract - Genera i tipi di contratto messaggio. Forma breve: /mc) /enableDataBinding - Implementa l'interfaccia System.ComponentM odel.INotifyPropertyChanged su tutti i tipi di contratto dati per abilitare l'associazione dati. (Forma breve: /edb) /serializable - Crea classi contrassegnate con l'attributo Serializable. (Forma breve: /s) /async - Crea firme di metodi sincrone e asincrone di inizio/fine. Impostazione predefinita: crea firme di metodi sincrone e asincrone basate su attività. (Forma breve: /a) /internal - Genera le classi contrassegnate come interne. Impostazione predefinita: genera classi pubbliche. (Forma breve: /i) /reference:<file path> - Tipi di riferimento nell'assembly specificato. Durante la creazione dei client, utilizzare questa opzione per specificare assembly che potrebbero contenere tipi che rappresentino i metadati da importare. (Forma breve: /r) /collectionType:<type> - Nome completo o nome completo di assembly del tipo da utilizzare come tipo di dati delle raccolte quando un codice viene generato da schemi. (Forma breve: /ct) /excludeType:<type> - Nome di tipo completo o completo di assembly da tipi di contratto di riferimento. (Forma breve: /et) /noStdLib - Non fa riferimento alle librerie standard. Per impostazione predefinita mscorlib.dll e system.servicemodel.dll sono di TPSIT 5 434 um 161 di riferimento. /serializer:Auto - Seleziona automaticamente il serializzatore. Cerca di utilizzare il serializzatore dei contratti dati e utilizza XmlSerializer se l'operazione non riesce. (Forma breve: /ser) /serializer:DataContractSerializer - Crea i tipi di dati che utilizzano il serializzatore dei contratti dati per la serializzazione e la deserializzazione /serializer:XmlSerializer - Genera tipi di dati che utilizzano XmlSerializer per la serializzazione e la deserializzazione /importXmlTypes - Configura il serializzatore dei contratti dati per importare tipi di contratto non dati come tipi IXmlSerializable. /useSerializerForFaults - Questa opzione specifica se il serializzatore specificato nell'opzione 'serializer' viene utilizzato per i tipi di contratto di errore. Se l'opzione non è specificata, DataContractSerializer viene utilizzato per gli errori. (Forma breve: /fault) /targetClientVersion:Version30 - Genera un codice che fa riferimento a una funzionalità presente negli assembly di .NET Framework 3.0 e versioni precedenti. Utilizzare questa opzione se si sta generando un codice per client che utilizzano .NET Framework versione 3.0. (Forma breve: /tcv) /targetClientVersion:Version35 - Genera un codice che fa riferimento a una funzionalità presente negli assembly di .NET Framework 3.5 e versioni precedenti. Utilizzare questa opzione se si sta generando un codice per client che utilizzano .NET Framework versione 3.5. (Forma breve: /tcv) /wrapped - Il codice generato non annullerà il wrapping del membro "parameters" dei messaggi document-wrapped-literal. /serviceContract - Genera il codice per i contratti dei servizi. Non verranno generate la configurazione e la classe del client. (Forma breve: /sc) /syncOnly - Crea solo la firma del metodo sincrona. Impostazione predefinita: crea firme di metodi sincrone e asincrone basate su attività. -= ESPORTAZIONE METADATI =Descrizione: svcutil.exe può esportare metadati per tipi di dati, contratti e servizi negli assembly compilati. Per esportare metadati per un servizio, è necessario utilizzare l'opzione /serviceName per indicare il servizio da esportare. Per esportare tutti i tipi di contratto dati in un assembly TPSIT 5 434 um 162 di utilizzare l'opzione /dataContractOnly. Per impostazione predefinita i metadati vengono esportati per tutti i contratti di assistenza negli assembly di input. Syntax: svcutil.exe [/t:metadata] [/serviceName:<serviceConfigName>] [/dataContractOnly] <assemblyPath>* <assemblyPath> - Percorso di un assembly contenente tipi di contratto dati, contratti o servizi da esportare. Per specificare più file come input è possibile utilizzare caratteri jolly della riga di comando standard. Opzioni: /serviceName:<serviceConfigName> - Nome della configurazione di un servizio da esportare. Se questa opzione viene utilizzata, è necessario passare come input un assembly eseguibile con un file di configurazione associato. Per la configurazione del servizio Svcutil esegue la ricerca tra tutti i file di configurazione associati. Se i file di configurazione contengono qualsiasi tipo di estensione, gli assembly contenenti questi tipi devono trovarsi nella cache globale o essere specificati esplicitamente mediante l'opzione /r. /reference:<file path> - Aggiunge l'assembly specificato al set di assembly utilizzati per risolvere i riferimenti di tipo. Se si sta esportando o convalidando un servizio che utilizza estensioni di terze parti (Behaviors, Bindings e BindingElements) registrate nella configurazione, utilizzare questa opzione per trovare gli assembly delle estensioni non presenti nella cache globale. (Forma breve: /r) /dataContractOnly - Opera solo sui tipi di contratto dati. I contratti di assistenza non verranno elaborati. (Forma breve: /dconly) /excludeType:<type> - Nome completo o nome completo di assembly di un tipo da escludere dall'esportazione. Per escludere i tipi da esportare è possibile utilizzare questa opzione durante l'esportazione dei metadati per un servizio o un set di contratti di assistenza. Non è possibile utilizzare questa opzione con l'opzione /dconly. (Forma breve: /et) -= CONVALIDA SERVIZIO =Descrizione: la convalida è utile per rilevare gli errori nell'implementazione di un servizio senza l'hosting del servizio. È necessario utilizzare l'opzione /serviceName per indicare il servizio da convalidare. Syntax: svcutil.exe /validate /serviceName:<serviceConfigName> <assemblyPath>* <assemblyPath> - Percorso di un assembly contenente tipi di servizio da convalidare. L'assembly deve avere un file di configurazione associato per offrire la configurazione del servizio. È TPSIT 5 434 um 163 di possibile utilizzare caratteri jolly della riga di comando per specificare più assembly. Opzioni: /validate - Convalida l'implementazione di un servizio. Per la convalida di un servizio è necessario utilizzare l'opzione /serviceName per indicare il servizio da convalidare. Se questa opzione viene utilizzata, è necessario passare come input un assembly eseguibile con un file di configurazione associato. (Forma breve: /v) /serviceName:<serviceConfigName> - Nome della configurazione di un servizio da convalidare. Per la convalida di un servizio è necessario specificare questa opzione. Per la configurazione del servizio Svcutil esegue la ricerca tra tutti i file di configurazione associati. Se i file di configurazione contengono qualsiasi tipo di estensione, gli assembly contenenti questi tipi devono trovarsi nella cache globale o essere specificati esplicitamente mediante l'opzione /r. /reference:<file path> - Aggiunge l'assembly specificato al set di assembly utilizzati per risolvere i riferimenti di tipo. Se si sta esportando o convalidando un servizio che utilizza estensioni di terze parti (Behaviors, Bindings e BindingElements) registrate nella configurazione, utilizzare questa opzione per trovare gli assembly delle estensioni non presenti nella cache globale. (Forma breve: /r) /dataContractOnly - Opera solo sui tipi di contratto dati. I contratti di assistenza non verranno elaborati. (Forma breve: /dconly) /excludeType:<type> - Nome completo o nome completo di assembly di un tipo di servizio da escludere dalla convalida. (Forma breve: /et) -= DOWNLOAD METADATI =Descrizione: svcutil.exe può essere utilizzato per scaricare i metadati da servizi in esecuzione e salvarli in file locali. Per scaricare i metadati, è necessario specificare esplicitamente l'opzione /t:metadata. In caso contrario, verrà generato il codice client. Per schemi URL http e https, svcutil.exe tenterà di recuperare i metadati mediante WS-Metadata Exchange e DISCO. Per tutti gli altri schemi URL svcutil.exe utilizzerà solo WS-Metadata Exchange. Per impostazione predefinita svcutil.exe utilizza l'associazione definita nella classe System.ServiceModel.Description.MetadataExchangeBindings. Per configurare l'associazione utilizzata per WS-Metadata Exchange, è necessario definire un endpoint client nella configurazione che utilizza il contratto IMetadataExchange. Quest'ultimo può essere definito nel file di configurazione svcutil.exe o in un altro file specificato mediante l'opzione /svcutilConfig. Syntax: svcutil.exe /t:metadata <url>* | <epr> TPSIT 5 434 um 164 di <url> - Un URL a un endpoint del servizio che fornisce metadati o un URL che fa riferimento a un documento di metadati con hosting online. <epr> - Percorso di un file XML contenente WS-Addressing EndpointReference per un endpoint del servizio che supporta WS-Metadata Exchange. -= CREAZIONE TIPO XMLSERIALIZER =Descrizione: svcutil.exe può pregenerare il codice di serializzazione C# richiesto per i tipi che possono essere serializzati utilizzando XmlSerializer. svcutil.exe genera solo il codice per i tipi utilizzati dai contratti di assistenza trovati negli assembly di input. Syntax: svcutil.exe /t:xmlSerializer <assemblyPath>* <assemblyPath> - Percorso di un assembly contenente tipi di contratto di assistenza. I tipi di serializzazione verranno generati per tutti i tipi serializzabili in XML in ciascun contratto Opzioni: /reference:<file path> - Aggiunge l'assembly specificato al set di assembly utilizzato per risolvere i riferimenti di tipo. (Forma breve: /r) /excludeType:<type> - Nome completo o nome completo di assembly del tipo da escludere dall'esportazione o dalla convalida. È possibile utilizzare questa opzione durante l'esportazione dei metadati per un servizio o un set di contratti di servizio per escludere i tipi da esportare. Impossibile utilizzare questa opzione con l'opzione /dataContractOnly. (Forma breve: /et) /out:<file> - Nome del file per il codice generato. Questa opzione viene ignorata quando più assembly vengono passati come input allo strumento. Impostazione predefinita: deriva dal nome dell'assembly. (Forma breve: /o) In Visual Studio, fare clic con il tasto destro in Esplora soluzioni, sul nome del progetto, selezionare Aggiungi riferimento al servizio…, è possibile selezionare un WS tra quelli registrati localmente o in una directory UDDI. Nella finestra che si apre, inserire nella casella Indirizzo: il seguente valore del WS. http://127.0.0.1/PrimoWebServiceWCF/PrimoWebServiceWCF.Service1.svc?WSDL I metadati, il file WSDL, sono recuperati semplicemente accedendo allo stesso URI del servizio con in più la querystring ?WSDL. Selezionare il servizio e il contratto IService1, ci sono anche le operazioni ammesse dal servizio, GetData e GetDataUsingDataContract, fare clic su OK. Nel caso in cui il WS è presente nella stessa Soluzione dell’applicazione in Visual Studio, è possibile fare clic sul pulsante Individua per rilevare automaticamente il WS. TPSIT 5 434 um 165 di Nella finestra Esplora Soluzioni si vede la cartella Service References, all’interno della quale si trova il riferimento al servizio indicato con il nome ServiceReference1. File APP.CONFIG È definito un <endpoint> con i relativi parametri ABC. <?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" /> </startup> TPSIT 5 434 um 166 di <system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService1" /> </basicHttpBinding> </bindings> <client> <endpoint address="http://pcmax/PrimoWebServiceWCF/PrimoWebServiceWCF.Service1.svc" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1" contract="ServiceReference1.IService1" name="BasicHttpBinding_IService1" /> </client> </system.serviceModel> </configuration> File FORM1.CS Il primo pulsante, Button1, contiene il codice necessario a richiamare il metodo GetData del WS, passando il valore numerico rilevato dal controllo NumericUpDown. L’istruzione successiva non fa altro che prendere il valore numerico, espresso con il tipo di dato Decimal, convertirlo nel tipo Integer e passarlo al metodo GetData del servizio. Si ottiene come risposta un dato di tipo stringa che è costituito dalle cifre del numero che era stato inviato al WS. Il risultato sarà poi assegnato alla casella di testo preposta alla visualizzazione dei risultati. Il codice del secondo pulsante, Button2, crea un riferimento al tipo composito. In realtà, non sarebbe necessario creare una seconda variabile-riferimento ct2: serve solo a dimostrare che la stringa memorizzata nell’oggetto referenziato da ct2 è effettivamente il risultato della risposta del WS e non il valore preesistente nello stesso oggetto. using System; using System.Windows.Forms; namespace ClientPrimoWebServiceWCF { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void btnGetData_Click(object sender, EventArgs e) { // prima di poter richiamare il metodo è necessario creare un riferimento al WS ServiceReference1.Service1Client sr; sr = new ServiceReference1.Service1Client(); string numero = sr.GetData((int)numericUpDown1.Value); txtGetData.Text = numero; } private void btnGetUsingDataContract_Click(object sender, EventArgs e) { ServiceReference1.Service1Client sr; sr = new ServiceReference1.Service1Client(); // crea un riferimento al tipo composito ServiceReference1.CompositeType ct1 = new ServiceReference1.CompositeType(); ServiceReference1.CompositeType ct2 = new ServiceReference1.CompositeType(); ct1.StringValue = txtGetUsingDataContractInvia.Text; ct2 = sr.GetDataUsingDataContract(ct1); txtGetUsingDataContractRicevi.Text = ct2.StringValue; TPSIT 5 434 um 167 di } } } Un strumento utile è l’Editor configurazione servizi, avviabile sia dal men Start sia dalla cartella Microsoft Windows SDK Tools. TPSIT 5 434 um 168 di Aprire il file configurazione dell’applicazione APP.CONFIG. MESSAGECONTRACT È un particolare contratto che consente di definire direttamente il messaggio SOAP che è scambiato tra il client e server. È possibile indicare quali sono gli elementi che devono comporre l’header e quali sono quelli che, invece, definiscono il body ottenendo un controllo completo del messaggio. L’uso del MessageContracts si avvale sempre di attributi. class SubmitBookRequestMessage { /* MessageHeader indica uno o più elementi che compongono l’intestazione del * messaggio */ [MessageHeader] string Richiedente; // MessageBody indica uno o più elementi che compongono il corpo del messaggio [MessageBodyMember] Libro Libro; } FAULTCONTRACT Bisogna tenere presente che il concetto di eccezione è qualcosa di specifico, legato ad una tecnologia, in SOA, invece, si deve pensare alla definizione di un qualcosa di astratto rispetto alla tecnologia: ecco il perchè di un contratto di tipo FaultContract. Un contratto che sia il servizio sia il suo fruitore sono in grado di riconoscere ed interpretare. In WCF una qualsiasi eccezione è ripresa e inviata verso il client sotto forma di un TPSIT 5 434 um 169 di messaggio SOAP formattato secondo la versione delle specifiche in uso. Internamente, WCF fa uso della classe FaultException per generare le eccezioni. [FaultContract(typeof(ArgumentException))] void SubmitBook(SubmitBookRequestMessage request); CALLBACKCONTRACT Talvolta può essere necessario avviare una comunicazione bidirezionale nella quale client e server si scambiano messaggi. Il server è così in grado di contattare il client e d’inviare messaggi invertendo le posizioni. Questo è un ServiceContract la cui implementazione, però, è a carico del client. Dev’essere definito sul server e indicato sul servizio in questo modo. [ServiceContract(CallbackContract = typeof(ICurrentBookCallback))] Progettare un convertitore tra gradi Celsius e Fahrenheit. Celsius è un’unità, espressa in gradi, con la quale si misura la temperatura e in cui il valore 0° indica il punto di congelamento dell’acqua, mentre il valore 100° ne indica il punto di ebollizione, fu proposta dal fisico e astronomo svedese Anders Celsius (Uppsala, 27 novembre 1701 – 25 aprile 1744) nel 1742. Fahrenheit è un’unità, espressa in gradi, con la quale si misura la temperatura e in cui il valore 32° indica il punto di congelamento dell’acqua, mentre il valore 212° ne indica il punto di ebollizione, fu proposta dal fisico tedesco Gabriel Fahrenheit nel 1724. Le seguenti formule sono usate per la conversione dei valori. °C = (°F − 32) / 1.8 °F = °C X 1.8 + 32 TPSIT 5 434 um 170 di File ISERVICE1.CS Progettare il servizio il cui scopo è convertire la temperatura. File SERVICE1.CS Il contratto così dichiarato, per realizzare il servizio vero e proprio, dev’essere poi implementato. Testare il WS, invocando i metodi. TPSIT 5 434 um 171 di A questo punto il WS che espone funzioni per la conversione Farenheit/Celsius e viceversa, potrà essere utilizzato da qualunque applicazione che abbia un accesso a Internet e che sappia interpretare il documento WSDL prodotto. Procedere ora alla creazione dell’applicazione client per la comunicazione con il WS. Completa indipendenza dal linguaggio di programmazione. Applicazione client di tipo Windows Form, ClientVisualBasic. Applicazione client di tipo WPF, ClientVisualCS. TPSIT 5 434 um 172 di WEB SERVICE CREATOR Fare clic su New…, si apre la finestra, selezionare il DB Access e fare clic su OK. Nella finestra che si apre, selezionare il DB e fare clic su Next; in quella successiva, selezionare le tabelle e le viste che si desiderano includere nel servizio web e fare clic su Next; quindi digitare il nome di servizio, l’URL e il numero di porta, fare clic su Next. Nella finestra che si apre, fare clic sul pulsante Submit Database Metadata XML. TPSIT 5 434 um 173 di Inserire la login e la password e fare clic OK. Nella finestra che si apre, si visualizza il progetto e il segno di spunta verde indica che è completato ed è scaricato; selezionare il progetto e scegliere la cartella dove memorizzarlo, fare clic su Download Project. TPSIT 5 434 um 174 di La cartella BIN contiene tutti i file binari. La cartella DOTNET contiene il servizio in codice sorgente. La cartella CLIENTS contiene i client per il servizio web, in tutti i linguaggi gestiti dall’applicazione. La cartella SERVER contiene tutto il codice del servizio web e il WSDL. Tool/Code Monkey… TPSIT 5 434 um 175 di TPSIT 5 434 um 176 di Tool/Silverlight… Selezionare il progetto e la configurazione, il pannello di destra visualizza l’anteprima. Fare clic sul pulsante Parameters…, per visualizzare le relazioni. TPSIT 5 434 um 177 di Dopo aver fatto clic su OK, nella finestra compaiono due nuove schede: XAML e C#. TPSIT 5 434 um 178 di PHP (PHP: HYPERTEXT PREPROCESSOR) L’uso dei WS si basa su librerie esterne quali ad esempio NUSOAP. Occorre attivare l’estensione SOAP, in ambito Linux con il parametro –enablesoap, con Windows decommentando la linea extension=php_soap .dll, nella sezione relativa alle estensioni. NUSOAP implementa un framework intermedio per l’accesso alle funzioni SOAP e di conseguenza per creare e usare WS in PHP. Non solo si tratta di una libreria affidabile ma è l’unica che consente di generare automaticamente il WSDL. In PHP esiste una serie di funzioni interne per utilizzare i WS che sono molto più veloci di quelle implementate in NUSOAP, tuttavia rimangono ancora limitate per certi aspetti, in pratica non consentono la generazione automatica del WSDL. Esempio, creare un file INDEX.PHP all’interno di una cartella del server web e come prima operazione sviluppare la funzione che s’interfaccerà al DB. <?php function execQuery($strSql) { $connessione = mysql_connect("localhost", "root", "password") or die("Connessione nonriuscita: " . mysql_error()); mysql_select_db("mioDB") or die("Selezione del database non riuscita"); /* esecuzione di una query SQL */ $query = "SELECT * FROM file where Applicazione like '%$strSql%'"; $risultato = mysql_query($query) or die("Query fallita: " . mysql_error() ); while ($riga = mysql_fetch_array($risultato, MYSQL_ASSOC)) { $columns[] = $riga; } /* chiusura della connessione */ mysql_close($connessione); return new soapval('return','tns:Idiot',$columns); } /* includere il file NUSOAP.PHP */ require_once('nusoap.php'); /* istanziare il server e aggiungere un tipo che servirà per descrivere i valori di ritorno che devono essere contenuti nello schema XML SOAP */ $server = new soap_server(); // initialize WSDL support $ns="http://localhost/~fabio/stock/services.php"; $server->configureWSDL('services', $ns); $server->wsdl->schemaTargetNamespace=$ns; $server->wsdl->addComplexType("MyType","complexType","array","all","", array( "ID" => array("ID" => "ID","type" => "xsd:string"),"Applicazione" => array("Applicazione" => "Applicazione","type" => "xsd:string"),"File" => array("File" => "File", "type" => "xsd:string"),"Sottotitolo" => array("Sottotitolo" => "Sottotitolo", "type"=> "xsd:string"),"Descrizione" => array("Descrizione" =>"Descrizione","type" => "xsd:string"),"NumeroRivista" => array("NumeroRivista" => "NumeroRivista","type" => TPSIT 5 434 um 179 di "xsd:string"),"IDmc" => array("IDmc"=> "IDmc", "type" => "xsd:string"),"Dim" => array("Dim" => "Dim", "type" => "xsd:string"),"posiz" => array("posiz" => "posiz", "type" => "xsd:string"),"Link" => array("Link" => "Link", "type" => "xsd:string"),"Evidenza" => array("Evidenza" => "Evidenza", "type" => "xsd:string"), "Indispensabili" => array("Indispensabili" => "Indispensabili","type" => "xsd:string") ) ); Inserire le informazioni che si riferiscono al protocollo di trasporto e agli altri dati che serviranno per la descrizione del WSDL. $server->register('execQuery', // method name array('strSql' => 'xsd:string'), // input parameters array('return' => 'tns:Idiot'), // output parameters 'urn:services', // namespace 'urn:services#execQuery', // soapaction 'rpc', // style 'encoded', // use 'Ritorna un array contenente i valori richiesti' // documentation ); Infine, invocare il WS. $HTTP_RAW_POST_DATA = isset( $HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : ''; $server->service($HTTP_RAW_POST_DATA); ?> Aprire un browser e connettersi al WS. Ad esempio, http://localhost/WS/servizio/nomeservizio.php. La pagina web contiene un link al file WSDL e uno per uno i link ai metodi esposti dal WS. Cliccare sul link WSDL e si ottiene il file WSDL in formato leggibile. Cliccare su uno dei metodi esposti dal WS per ottenere un riferimento alla documentazione che si riferisce a quel particolare metodo. Per installare un WS in Apache, è sufficiente copiare il file PHP e il relativo NUSOAP.PHP nella cartella che esporrà il servizio. Per utilizzare il WS, includere la libreria NUSOAP.PHP che contiene le varie classi che consentono di lavorare in modo semplificato con il WS. <?php TPSIT 5 434 um 180 di require_once('nusoap.php'); Creare un’istanza del client e tramite il file WSDL informare l’istanza di quali sono i metodi che il WS espone e della sua descrizione. Ovviamente il parametro passato al client corrisponde all’indirizzo del WS. Nell’esempio, il WSDL è generato automaticamente dallo script, in altri casi si deve referenziare direttamente il file WSDL. $client = new soapclient_n("http://localhost/WS/services.php?wsdl", true); Controllare se ci sono eventuali errori. $err = $client->getError(); if ($err) { echo '<h2>Errore ne costruttore</h2><pre>'. $err . '</pre>'; } Richiamare il metodo esposto dal WS passandogli eventuali parametri sotto forma di array. $result2 = $client->call('execQuery', array('strSql' => 'test')); Controllare eventuali errori e stamparli a video. if ($client->fault) { echo '<h2>Fault</h2><pre>'; echo '</pre>'; } else { $err = $client->getError(); if ($err) { echo '<h2>Error</h2><pre>' . $err . '</pre>'; Se non ci sono errori, stampare il risultato a video. } else { // display the result echo '<h2>Result</h2><pre>'; print_r($result2); echo '</pre>'; } } Si può stampare anche il debug delle comunicazioni avvenute fra client e server. echo '<h2>Request</h2>'; echo '<pre>' . htmlspecialchars($client->request, ENT_QUOTES) . '</pre>'; echo '<h2>Response</h2>'; echo '<pre>' . htmlspecialchars($client->response, ENT_QUOTES) . '</pre>'; // display the debug messages echo '<h2>Debug</h2>'; echo '<pre>' . htmlspecialchars($client->debug_str, ENT_QUOTES) . '</pre>'; TPSIT 5 434 um 181 di ?> WS senza WSDL Esiste una tecnica che consente di non dover leggere il file WSDL ogni volta che si richiama il servizio, si usano le funzioni __call. Ha il vantaggio di non dovere parserizzare il file WSDL per ogni chiamata ma lo svantaggio di essere meno semplice da utilizzare. All’interno del file PHP.INI si trovano le seguenti linee che consentono di abilitare o meno una cache per i file WSDL diminuendo il tempo di accesso. [soap] ; Enables or disables WSDL caching feature. ; http://php.net/soap.wsdl-cache-enabled soap.wsdl_cache_enabled=1 ; Sets the directory name where SOAP extension will put cache files. ; http://php.net/soap.wsdl-cache-dir soap.wsdl_cache_dir="/tmp" ; (time to live) Sets the number of second while cached file will be used ; instead of original one. ; http://php.net/soap.wsdl-cache-ttl soap.wsdl_cache_ttl=86400 ; Sets the size of the cache limit. (Max. number of WSDL files to cache) soap.wsdl_cache_limit = 5 TPSIT 5 434 um 182 di JAVA Ha numerosi framework per lo sviluppo di WS. Per esempio, Apache Axis è una libreria che s’integra in un’applicazione web basata sulla tecnologia servlet. Basta aggiungere i JAR (Java ARchive) di Axis all’applicazione, si programma estendendo le classi e implementando le interfacce del framework, poi si pubblica il tutto servendosi di specifici descrittori XML. L’operazione, anche se svolta a mano, rimane semplice ma richiede comunque un certo grado di confidenza con la tecnologia. Eclipse facilita le cose: attraverso il progetto WTP (Web Tools Platform) la piattaforma è dotata di template, editor e procedure guidate per un più rapido controllo dei servizi Axis. Due diversi approcci Quando si realizza un WS basato su SOAP, due diversi approcci sono possibili. 1. Se la logica del S/W è stata già scritta e il WS non deve rispettare un design imposto a priori, allora si procede traducendo in SOAP l’interfaccia di programmazione Java già esistente; in pratica si prende la classe Java esistente e si trasforma in un servizio SOAP. 2. Se la logica del S/W non è stata già scritta, prima si definisce il WSDL che descrive e dettaglia il servizio, poi si realizza l’applicazione Java da questo. Esempio, implementare un WS che verifica se una carta di credito è valida. JAX-WS (Java API per XML WS) è la tecnologia Java di riferimento che definisce un modello che usa le annotazioni per sviluppare i provider e i consumer di servizi web. public class WS { @WebService (name="CartaCredito", portName="Carta CreditoPort", serviceName="CartaCreditoService", targetNamespace="http://www.miosito.it") public class CartaCreditoImpl { @WebMethod (operationName="verifica",action="che ck") public @WebResult(name="risultatoVerifica") boolean checkCC(@WebParam(name="codice") final String cc,@WebParam(name="dataScadenza") final Date scadenza) {...} } La classe Java CartaCreditoImpl è dichiarata come WS annotandola con la notazione @WebService. Il metodo checkCC, annotato con @WebMethod, è un’operazione pubblica del servizio. Nello specifico il metodo accetta come parametri d’input il codice della carta di credito, una stringa e la data di scadenza, un oggetto java.util.Date e ritorna un risultato booleano che indica la validità della carta. I parametri d’input e di output di un metodo Java per essere serializzabili in XML all’interno di un envelope SOAP devono soddisfare la specifica JAXB (Java Architecture per l’XML Binding), è una tecnologia che definisce le regole e un set di annotazioni per mappare e quindi serializzare i tipi e gli oggetti Java in XML e viceversa. Le annotazioni @WebParam e @WebResult permettono di specificare il nome dei parametri d’input e output di un WS. TPSIT 5 434 um 183 di Annotazione Descrizione Definisce una classe Java come WS, i principali attributi applicabili sono i seguenti. name: specifica il valore dell’attributo name dell’elemento wsdl:portType nel documento WSDL. Web Service targetNamespace: specifica il namespace per gli elementi wsdl:portType. serviceName: specifica il nome del servizio, attributo name dell’elemento wsdl:service nel documento WSDL. portName: specifica il valore dell’attributo name dell’elemento wsdl:port nel documento WSDL. Consente di esporre un metodo di una classa Java come operazione del servizio web, i principali attributi applicabili sono i seguenti. operationName: specifica il valore dell’attributo name dell’elemento wsdl:operation nel documento WSDL. WebMethod action: specifica l’azione per il metodo, attributo soapAction per i bindings SOAP. exclude: booleano che specifica se il metodo è esposto come operazione del WS. Definisce il mapping tra i parametri dei metodi Java e i parametri dell’operazione del WS, i principali attributi applicabili sono i seguenti. name: specifica il nome locale dell’elemento XML che rappresenta il parametro. partName: specifica il nome dell’elemento wsdl:part che rappresenta il WebParam parametro. targetNamespace: specifica il namespace XML del parametro. mode: specifica se il parametro è d’input (IN), di output (OUT) o d’input e output (INOUT). header: booleano che specifica se il parametro è presente nel body del messaggio (il default) o se è presente nell’header SOAP. Definisce il mapping tra i parametri di ritorno dei metodi Java e i parametri delle operazioni del WS, i principali attributi applicabili sono i seguenti. name: specifica il nome locale dell’elemento XML che rappresenta il parametro. WebResult partName: specifica il nome dell’elemento wsdl:part che rappresenta il parametro. targetNamespace: specifica il namespace XML del parametro. header: booleano che specifica se il parametro è presente nel body del messaggio (il default) o se è presente nell’header SOAP. Per installare un WS, basta eseguire il deploy di un’applicazione web che ha tra i sorgenti Java la classe CartaCreditoImpl e nel deployment descriptor, il file WEB.XML, la definizione di una servlet che dichiara come valore del TAG <servlet-class> proprio la classe CartaCreditoImpl. Il TAG <url-pattern> definisce l’URL relativa per invocare il servizio. <servlet> <servlet-name>CartaCreditoServlet</servlet-name> <servlet-class>CartaCreditoImpl</servlet-class> </servlet> <servlet-mapping> <servlet-name>CartaCreditoServlet< /servlet-name> TPSIT 5 434 um 184 di <url-pattern>/CarteCredito</url-pattern> </servlet-mapping> Il file WSDL del servizio è quindi visibile al seguente URL. http://localhost:8080/WS/CarteCredito?wsdl Per invocare il WS si usa il tool soapUI, installabile sia su piattaforma Window sia Linux, S/W open source che semplifica il test dei WS. È sufficiente referenziare il file WSDL presente all’URL e soapUI costruisce lo scheletro di un envelope SOAP che è possibile inviare al WS dopo aver digitato i parametri d’input. Questo è il messaggio SOAP inviato dal consumer al provider per richiedere la verifica della carta di credito. <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/""> <soap:Header/> <soap:Body> <ns2:verifica xmlns:ns2="http://www.miosito.it""> <codice>123456789</codice> <dataScadenza>2001-12-28T00:00:00.000+01:00</dataScadenza> </ns2:verifica> </soap:Body> </soap:Envelope> Questa è la risposta del provider. <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header/> <soap:Body> <ns2:verificaResponse xmlns:ns2="ttp://www.miosito.it""> <risultatoVerifica>true</risultatoVerifica> </ns2:verificaResponse> </soap:Body> </soap:Envelope> Documento WSDL del WS di verifica della carta di credito. Le sezioni types, message e portType danno una descrizione astratta del WS definendo le operazioni offerte e il tipo di messaggi scambiati. Viceversa, le sezioni binding e service danno una descrizione concreta “istanziando” le precedenti definizioni su un’implementazione reale che è caratterizzata da specifici protocolli di comunicazione e indirizzi di rete. La sezione types definisce, mediante un XML schema, il tipo di dati utilizzati dal WS. <wsdl:types> <xs:schema ...> <xs:element name="verifica" type="tns:verifica"/> ... <xs:complexType name="verifica""> <xs:sequence> <xs:element minOccurs="0" name="codice" type="xs:string" /> <xs:element minOccurs="0" name="dataScadenza" type="xs:dateTime" /> </xs:sequence> TPSIT 5 434 um 185 di </xs:complexType> ... </xs:schema> </wsdl:types> Il tipo complesso verifica è una sequenza di due elementi: il primo elemento, ossia il codice della carta di credito, è una stringa, xs:string, mentre il secondo elemento, la data di scadenza della carta di credito, è una data, xs:dateTime. La sezione message definisce la struttura dei parametri d’ingresso e di risposta delle operazioni del WS. <wsdl:message name="verifica""> <wsdl:part element="tns:verifica" name="parameters" /> </wsdl:message> Il codice specifica come il messaggio verifica, parametro d’ingresso del WS, sia formato da una sola “parte”, sezione wsdl:part e in particolare l’attributo element referenzia un tipo complesso definito nella sezione wsdl:types. La sezione portType definisce l’interfaccia del servizio descrivendo le operazioni e i messaggi scambiati. <wsdl:portType name="CartaCredito"> <wsdl:operation name="verifica"> <wsdl:input message="tns:verifica" name="verifica"/> <wsdl:output message="tns:verificaResponse" name="verificaResponse""/> </wsdl:operation> </wsdl:portType> Il portType di nome CartaCredito presenta una sola operazione di nome verifica che accetta un messaggio d’input sempre di nome verifica e ritorna un messaggio di output di nome verificaResponse. La sezione binding definisce le tecnologie usate per implementare le operazioni specificate nella sezione wsdl:portType. Questa è la sezione wsdl:binding con un SOAP/HTTP binding per l’interfaccia CartaCredito. <wsdl:binding name="CartaCreditoServiceSoapBinding" type="tns:CartaCredito""> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/> <wsdl:operation name="verifica"> <soap:operation soapAction="check" style="document"/> <wsdl:input name="verifica"> <soap:body use="literal"/> </wsdl:input> <wsdl:output ...> ... </wsdl:operation> </wsdl:binding> L’attributo name dell’elemento wsdl:binding specifica un nome univoco per riferirsi al binding nel resto del documento WSDL mentre l’attributo type riporta il nome di un wsdl:portType al quale si sta applicando il binding. L’elemento soap:binding descrive i dettagli del binding SOAP e in particolare l’attributo transport specifica HTTP come protocollo di trasporto, mentre l’attributo style che può TPSIT 5 434 um 186 di assumere i valori RPC o document, definisce lo stile del servizio. Il valore per l’attributo transport="http://schemas.xmlsoap.org/soap/http"/> corrisponde al protocollo di trasmissione HTTP. Altri valori per l’attributo transport possono essere specificati per indicare protocolli di trasmissione diversi: SMTP e FTP. L’elemento soap:operation definisce i dettagli SOAP di un’operazione esposta dal servizio e in particolare, l’attributo soapAction specifica il valore del parametro SOAPAction presente nell’header HTTP della richiesta. La sezione soap:body descrive come le parti del messaggio devono apparire all’interno del body dell’envelope SOAP. La sezione wsdl:service dichiara un WS come una raccolta di porte “concrete”, elementi wsdl:port. <wsdl:service name="CartaCreditoService"> <wsdl:port binding="tns:CartaCreditoServiceSoapBinding" name="CartaCreditoPort""> <soap:address location="http://localhost:8080/WS/CarteCredito"/> </wsdl:port> </wsdl:service> Una porta, wsdl:port, è l’istanza di una porta astratta, wsdl:portType, ottenuta tramite un binding. Pertanto ogni elemento wsdl:port ha un nome univoco, attributo name e fa riferimento ad un binding specifico, attributo binding. Se poi il binding è di tipo SOAP allora l’elemento wsdl: port deve contenere un elemento soap:address che specifica con l’attributo location l’indirizzo di rete della porta. Esempio, modificare il WS di verifica della carta di credito, affinché sia invocabile solo da consumers che forniscano le proprie credenziali di accesso. Il servizio espone l’operazione di verifica e richiede come parametri d’input il codice della carta di credito e la relativa data di scadenza e ritorna un risultato booleano per indicare la validità o meno della carta. L’operazione del WS è dichiarata nella sezione portType del file WSDL. <wsdl:portType name="CartaCredito""> <wsdl:operation name="verifica""> <wsdl:input message="tns:verifica" name="verifica"/> <wsdl:output message="tns:verificaResponse" name="verificaResponse"/> </wsdl:operation> </wsdl:portType> La sezione types specifica, mediante un XML schema, il tipo di dati utilizzati dal WS. verifica è un tipo complesso costituito da una stringa, il codice della carta di credito e da una data, la data di scadenza della carta. <wsdl:types> … <xs:complexType name="verifica"> <xs:sequence> <xs:element minOccurs="0" name="codice" type="xs:string"/> <xs:element minOccurs="0" name="dataScadenza" type="xs:dateTime"/> </xs:sequence> </xs:complexType> TPSIT 5 434 um 187 di … <wsdl:types> Il servizio dovrà ora richiedere obbligatoriamente l’username e la password del consumer e le credenziali dovranno essere presenti in un header WS-Security. Il messaggio SOAP di richiesta da parte del consumer dovrà quindi essere così strutturato. <S11:Envelope xmlns:S11="http://schemas.xmlsoap.org/soap/envelope/"> <S11:Header> <wsse:Security xmlns:wsse="..." S11:mustUnderstand="1"> <wsse:UsernameToken xmlns:wsu="..." wsu:Id="UsernameToken-256294"> <wsse:Username>pippo</wsse:Username> <wsse:Password>pluto</wsse:Password> </wsse:UsernameToken> </wsse:Security> </S11:Header> <S11:Body> <ns2:verifica xmlns:ns2="http://www.miosito.it"> <codice>123456789</codice> <dataScadenza>2013-07-07T00:00:00.000+01:00</dataScadenza> </ns2:verifica> </S11:Body> </S11:Envelope> L’attributo mustUnderstand per l’header <wsse:Security> ha valore uno, ossia le credenziali devono essere obbligatoriamente analizzate, “capite” e processate dal provider del servizio. Il provider del servizio sarà esteso affinché rifiuti i messaggi che non hanno una sezione header di tipo WS-Security e verifichi la correttezza delle credenziali, per esempio su un DB. Per estendere il servizio si usa l’handler framework presente nella specifica JAX-WS. Gli handler JAX-WS sono intercettori che possono essere collegati all’ambiente di esecuzione JAX-WS per l’elaborazione dei messaggi in entrata e in uscita. La specifica JAX-WS definisce due tipi di handler. 1. Logical handler: sono indipendenti dal protocollo e hanno accesso al solo payload del messaggio. 2. Protocol hander: hanno accesso all’intero messaggio SOAP, inclusi gli header. È possibile associare più di un handler al provider di un WS e questi sono assemblati in una catena di gestori indicata con il termine di handler chain. Un file di configurazione XML, referenziato con l’annotazione @HandlerChain, riporta l’insieme degli handler associati al servizio. Esempio, servizio di verifica con l’annotazione @HandlerChain. @WebService (name="CartaCredito", portName="Carta CreditoPort", serviceName="CartaCreditoService", targetNamespace="http://www.miosito.it") @HandlerChain(file=""/security.xml") public class CartaCreditoImpl { @WebMethod (operationName="”verifica",action="check") public @WebResult(name="risultatoVerifica") TPSIT 5 434 um 188 di boolean checkCC(@WebParam(name="codice") final String cc; @WebParam(name="dataScadenza") final Date scadenza) {...} } Il file SECURITY.XML definisce la lista di handler associati al servizio. In questo caso, è definito un solo handler, indicato con il nome di GestoreSicurezza che esegue i controlli di sicurezza. <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <javaee:handler-chains xmlns:javaee="http://java.sun.com/xml/ns/javaee" xmlns:xsd="http://www.w3.org/2001/XMLSchema""> <javaee:handler-chain> <javaee:handler> <javaee:handler-name>GestoreSicurezza</javaee:handler-name> <javaee:handler-class>it.wsecurity.Security</javaee:handler-class> </javaee:handler> </javaee:handler-chain> </javaee:handler-chains> L’handler GestoreSicurezza deve analizzare l’header WS-Security, recuperare le credenziali del consumer e verificarle. La classe it.wsecurity.Security che realizza l’handler GestoreSicurezza, dovendo avere accesso all’intero messaggio SOAP e in particolare alla sezione degli header sarà un protocol handler e implementerà l’interfaccia SOAPHandler. public class Security implements SOAPHandler<SOAPMessageContext> { public boolean handleMessage(SOAPMessageContext context) { ...} public boolean handleFault(SOAPMessageContext context) { ...} public void close(MessageContext context) { ...} public Set<QName> getHeaders() { ...} } Il metodo handleMessage consente di analizzare il messaggio SOAP sia in entrata sia in uscita dal provider del servizio. La direzione del messaggio è determinata attraverso un valore booleano ottenuto dal SOAPMessageContext. Boolean outbound = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); Se la direzione del messaggio è in entrata, dev’essere verificato l’header WS-Security. public boolean handleMessage(SOAPMessageContext context) { Boolean outbound =...; if (!outbound.booleanValue()) { // messaggio in entrata TPSIT 5 434 um 189 di SOAPMessage msg = context.getMessage(); SOAPHeader header = null; try { header = msg.getSOAPHeader(); } catch (SOAPException e) {...} if (header != null) { SOAPElement username = find("Username",header); SOAPElement password = find("Password",header); if (username != null && password !=null && "pippo".equals(username.getValue()) && "Staro".equals(password.getValue())) return true; else throw new SecurityException("Credenziali non corrette"); } if header != null if !outbound.booleanValue() return true; } } Dal contesto d’invocazione, è recuperato il messaggio SOAP e in particolare la sezione dei SOAP header. SOAPMessage msg = context.getMessage(); SOAPHeader header = msg.getSOAPHeader(); Dall’header devono essere recuperati i valori di Username e Password che sono verificati rispetto a valori indicati nel codice. Il metodo privato find cicla ricorsivamente sui nodi del SOAPElement e ritorna il SOAPElement cercato, Username o Password o il valore null se non lo trova. private SOAPElement find(String localName, SOAPElement element) { if (localName.equals(element.getLocalName())) return element; if (element.getChildElements() == null || !element.getChildElements().hasNext()) return null; Iterator childElements = element.getChildElements(); boolean founded = false; SOAPElement soapElement = null; while (childElements.hasNext() && !founded) { // ciclo ricorsivamente sull’elemento Object child = childElements.next(); if (child instanceof SOAPElement) { SOAPElement _soapElement = (SOAPElement)child; soapElement = find(localName, _soapElement); if (soapElement == null) founded = false; else founded = true; } TPSIT 5 434 um 190 di } return soapElement; } L’handler GestoreSicurezza implementa anche i metodi handleFault e close: il primo ritorna true mentre il secondo è void. L’ultimo metodo della classe handler Security è il metodo getHeaders, ha la seguente implementazione. public Set<QName> getHeaders() { Set<QName> header = newTreeSet<QName>(); String uri = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecuritysecext-1.0.xsd"; header.add(new QName(uri,"Security")); return header; } L’header WS-Security nel messaggio SOAP in ingresso ha l’attributo mustUnderstand valorizzato ad uno. Pertanto l’handler GestoreSicurezza deve necessariamente analizzare, “capire” e gestire l’header WS-Security o in caso contrario restituire un messaggio di fault. L’insieme degli header SOAP gestiti da un handler è individuato dall’elenco dei QName che l’identificano. Il metodo getHeaders dichiara che è in grado di processare la sezione WS-Security identificata univocamente nel messaggio SOAP dalla URI che è proprio il namespace di WS-Security versione 1.0. TPSIT 5 434 um 191 di GRID SERVICE INTRODUZIONE Si può immaginare Internet come una rete neurale (cervello umano), nella quale ogni nodo rappresenta un neurone che scambia informazioni con gli altri e mette a disposizione le proprie risorse di calcolo per eseguire elaborazioni complesse: è una griglia computazionale, un’evoluzione del concetto di rete di calcolatori. Infatti, sono progettate per lavorare su reti eterogenee, permettendo di mettere in comunicazione macchine che si possono trovare ovunque nel mondo e che mettono a disposizione una quantità di risorse variabile nel tempo. I sistemi cluster, invece, usati nei centri di calcolo sono progettati per lavorare su una rete tecnologicamente omogenea, in altre parole con nodi che hanno la stessa piattaforma H/W e S/W, solitamente in locale e dove ogni macchina mette a disposizione delle risorse di calcolo prestabilite. I WS presentano molti vantaggi ma hanno un grosso limite: sono stateless, il servizio non è in grado di mantenere informazioni sullo stato delle risorse. Le griglie computazioni permettono di ovviare a questo problema grazie ai GS (Grid Service) che si possono considerare come un’estensione dei WS che permette di gestire delle risorse e il loro stato implementando lo standard WSRF (WS Resource Framework). OGSA (Open Grid Service Architecture) definisce le caratteristiche fondamentali che un GS dovrebbe avere: il modello di programmazione, informazioni su come costruire un servizio e quali sono le componenti per costruire e distribuire un prodotto di GS. OGSI (Open Grid Service Infrastructure), utilizzando le tecnologie che stanno alla base dei GS e WS, definisce i meccanismi per la creazione, la gestione e lo scambio d’informazioni TPSIT 5 434 um 192 di tra i GS stessi. Le specifiche OGSI estendono WSDL e XML al fine di gestire le seguenti possibilità. WS con stato. Estensione delle interfacce dei WS. Notifica asincrona dei cambiamenti di stato. Riferimenti a istanze di servizi. Collezione d’istanze. In pratica, OGSA costituisce l’architettura, mentre WSRF costituisce l’infrastruttura sulla quale l’architettura è stata costruita: infatti, WSRF fornisce i servizi stateful di cui OGSA ha bisogno. TPSIT 5 434 um 193 di REST/POX INTRODUZIONE Lo stack SOAP definisce un proprio formato di messaggi. I WS SOAP sono molto complessi: un protocollo di scambio “astratto” che sarà implementato da un protocollo concreto HTTP, le funzionalità sono esposte tramite WSDL, una serie di tipi che saranno mappati in tipi concreti dipendenti dal linguaggio di programmazione. Una direzione completamente diversa è stata presa, invece, da quelle architetture che seguono l’approccio REST. Descritto nel 2000 nella discussione di dottorato di Roy Fielding. Considerare un tavolino da caffè sul quale si trovano un telecomando, una tazza, un vassoio e un giornale: questi sono i nomi. Prendi, elimina: questi sono i verbi. È possibile usare lo stesso verbo per tutti questi nomi? Sarebbe curioso inventare combinazioni per tutti questi elementi, come PrendiTazza, EliminaTazza e PrendiTelecomando. Un modello d’interfacce REST crea quindi un’URL che costituisce la rappresentazione di un oggetto gestito nel sistema e ne consente la manipolazione con i verbi standard del protocollo HTTP e in particolare i metodi GET, POST, PUT e DELETE per interrogare e gestire dati attraverso il web. In pratica, è un ritorno alle origini, da molto tempo, infatti, ancora prima di SOAP, esiste uno stack in grado di garantire affidabilità, sicurezza e scalabilità: è il web stesso. Riutilizzando la tecnologia del web è possibile integrare sistemi tra loro eterogenei. I social network sono stati i primi che hanno reso disponibile le WebAPI esposte tramite servizi REST/POX. L’idea consiste nel fornire un URI di accesso ad un’entità, per esempio http://www.miosito.it/bici e, se richiamato dal browser, restituisce la lista delle biciclette in XML, in JSON, in RSS (Really Simple Syndication). XML può essere letto con Visual C# e LINQ (Language INtegrated Query) to XML. JSON può essere letto con JavaScript all’interno di pagine HTML. In conclusione, i siti web, quindi, non restituiscono solo HTML ma offrono informazioni attraverso API che i client possono consumare. A differenza di un’API REST, una streaming API non restituisce dati immediatamente ma è una connessione “live” e continua tra un client e un server o tra due server. Una streaming API può essere assimilata ad una chiamata telefonica in corso, mentre l’API REST è assimilabile ad una lettera che si riceve per posta. Le API REST hanno un limite di 180 richieste ogni 15 secondi ma questo limite può variare a seconda del tipo di richiesta. WEBAPI Fare clic su File/Nuovo/Progetto… (CTRL+N), nella finestra Nuovo progetto selezionare Altri linguaggi/Visual C#/Web/Applicazione Web ASP.NET MVC 4, fare clic su OK. Per progettare servizi REST, il .NET Framework di ASP.NET è stato arricchito con le ASP.NET WebAPI, in pratica è una libreria che basa il suo funzionamento su ASP.NET. Per iniziare usare il template WebAPI di Visual Studio che crea un’applicazione con servizi REST. TPSIT 5 434 um 194 di Nella finestra successiva, selezionare WebAPI. TPSIT 5 434 um 195 di Visual Studio genera il progetto seguente. TPSIT 5 434 um 196 di Eseguire l’applicazione. TPSIT 5 434 um 197 di Esempio, nella finestra Nuovo progetto ASP.NET MVC 4 selezionare Vuoto. Visual Studio genera il progetto seguente. Selezionare la cartella Controllers e dal Aggiungi/Controller… (CTRL+M, CTRL+C). menu contestuale fare clic su Selezionare Controller API vuoto e fare clic su Aggiungi. Si ottiene una nuova classe vuota che si chiama Prodotti.Controllers ed eredita da ApiController. Il prefisso del controller, nell’esempio Prodotti, determina il percorso del servizio. namespace Prodotti.Controllers { public class Default1Controller : ApiController { TPSIT 5 434 um 198 di Tutti i servizi sviluppati con questa tecnologia si trovano sotto il percorso api. Nell’esempio, il servizio, anche se non è ancora implementato, è raggiungibile all’indirizzo http://localhost:1145//api/prodotti. Inserire nella classe le funzioni che saranno invocate secondo il metodo HTTP usato per chiamare l’indirizzo. Per esempio, inserire le funzioni seguenti per fare sì che siano invocate su chiamate HTTP di tipo GET o POST sull’indirizzo. Get[esegui] Post[esegui] La funzione seguente, ritorna la lista dei prodotti. public IEnumerable<Prodotto> GetProdotto() { return new Prodotto[] { new Prodotto { ID = 1, Nome = "prodotto 1" } }; } Se nel browser s’invoca l’indirizzo del servizio, siccome è una chiamata GET, si ottiene il risultato da GetProdotto. Il motore serializza automaticamente la lista in JSON e il risultato è il seguente. [{"Nome":"prodotto 1","ID":1}] Il motore legge, dagli header HTTP, quali formati di risposta il chiamante accetta, per esempio aggiungendo l’header seguente: Accept : text/xml, si richiede la risposta in XML che è serializzata con DataContractSerializer. <ArrayOfProdotti xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ProductsService"> <Prodotto> <ID>1</ID> <Nome>prodotto 1</Nome> </Prodotto> </ArrayOfProdotti> I servizi possono rispondere secondo il formato preferito dal chiamante, inoltre si ha la possibilità di creare formatter personalizzati. Creata l’operazione per la lista, la regola di routing permette di aggiungere un’altra funzione che accetta un parametro e ritorna un solo prodotto. public GetProdotto(int id) { if ((id != 1)) throw new HttpResponseException(HttpStatusCode.NotFound); return new Prodotto { ID = 1, Nome = "prodotto 1" }; } TPSIT 5 434 um 199 di È possibile lanciare eccezioni con la classe HttpResponseException che il motore poi traduce in status code del protocollo HTTP, permettendo d’informare il chiamante dell’esito dell’operazione, nell’esempio per id diverso da uno ritorna l’errore 404 NotFound. Implementazione di un’operazione di POST per aggiungere un nuovo prodotto. public HttpResponseMessage PostProdotto(Prodotto p) { // assegno un id p.ID = 2; // salvo sul repository HttpResponseMessage response=new HttpResponseMessage(HttpStatusCode.Created); // URI della risorsa dynamic link = Url.Link("DefaultApi", new { id = p.ID }); response.Headers.Location = new Uri(link); return response; } Si riceve l’oggetto Prodotto direttamente come parametro della funzione perché sarà inoltrato nella richiesta nel formato JSON o XML. Anche per quanto riguarda la richiesta, quindi, il motore capisce il formato. Nella funzione si esegue l’attività sul repository e ritorna l’oggetto HttpResponseMessage. Quest’operazione è facoltativa ma permette di rispondere al chiamante secondo la filosofia REST, nell’esempio si specifica lo status 201 Created e si restituisce nell’header location l’indirizzo della risorsa. HTTP/1.1 201 Created Date: Fri, 07 Nov 2023 05:37:08 GMT X-Aspnet-Version: 11.0.70711 Location: http://localhost:1229/api/prodotti/2 Content-Length: 0 Connection: Close ODP (OPEN DATA PROTOCOL) Con il passare del tempo è nata l’esigenza di uniformare il modo di accedere alle entità, d’interrogarle e modificarle. L’ODP ha come scopo l’accesso e la manipolazione di entità con chiamate REST/POX. Microsoft ha implementato questi protocolli con i WCF Data Services. TPSIT 5 434 um 200 di Esempio, dato un progetto, mappare il DB ADVENTUREWORKSLT.MDF con ADO.NET Entity Framework per avere a disposizione l’oggetto AdventureWorks come ObjectContext e le varie entità. Posizionarsi sul progetto e fare clic con il pulsante destro del mouse, dal menu contestuale selezionare Aggiungi/Nuovo elemento…(CTRL+MAIUSC+A)/Visual C#/Web/WCF Data Service. Visual Studio crea automaticamente ADVENTUREWORKS.SVC e il relativo code-behind ADVENTUREWORKS.SVC.CS. TPSIT 5 434 um 201 di File ADVENTUREWORKS.SVC.CS Si ha un servizio già implementato: una classe che eredita da DataService<T> dove T è l’unica informazione che si deve specificare. Il metodo InitializeService configura il servizio, per esempio a quale entità dare accesso e con quali diritti, "Products" e la versione di AtomPub, il formato di serializzazione usato quando s’interroga il servizio. Il resto dell’implementazione è demandato alla classe DataService che, in base al contesto che il programmatore le ha fornito, conosce i metadati, le entità e le proprietà che l’object model dispone ed è in grado di eseguire automaticamente query tramite l’interfaccia IQueryable supportata da LINQ to Entities. using System.Data.Services; namespace DataServicesWebApp { public class AdventureWorks : DataService<AdventureWorksLTEntities> { // questo metodo è chiamato solo una volta per inizializzare i criteri a livello di servizio public static void InitializeService(IDataServiceConfiguration config) { // impostare regole per indicare i set di entità e le operazioni del servizio visibili, // Esempi: // config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead); // testing utilizzare "*" per indicare tutti i set di entità/tutte le operazioni del servizio // "*" NON dev’essere utilizzato in sistemi di produzione. // espone solo i set di entità necessari per l'applicazione in fase di compilazione // utilizza EntitySetRight.All che consente l'accesso sia in R sia in W al set di entità config.SetEntitySetAccessRule("Products", EntitySetRights.All); config.SetEntitySetAccessRule("ProductCategories", EntitySetRights.All); config.SetEntitySetAccessRule("ProductDescriptions", EntitySetRights.All); config.SetEntitySetAccessRule("ProductModelProductDescriptions", EntitySetRights.All); config.SetEntitySetAccessRule("ProductModels", EntitySetRights.All); } } } Fare clic sul file ADVENTUREWORKS.SVC, dal menu contestuale selezionare Imposta come pagina iniziale, eseguire l’applicazione. TPSIT 5 434 um 202 di Si ottiene come risposta un feed AtomPub che elenca le entità disponibili. I WCF Data Services supportano le specifiche di ODP e permettono quindi d’interrogare l’entità "Products" navigando all’indirizzo seguente, ottenendo così, sotto forma di feed, la lista dei prodotti. Fare clic su File/Salva con nome… (CTRL+S) per ottenere il file PRODUCTS.XML. TPSIT 5 434 um 203 di <?xml version="1.0" encoding="utf-8" standalone="yes"?> <feed xml:base="http://localhost:50000/AdventureWorks.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom"> <title type="text">Products</title> <id>http://localhost:50000/AdventureWorks.svc/Products</id> <updated>2023-07-31T03:02:50Z</updated> <link rel="self" title="Products" href="Products" /> <entry> <id>http://localhost:50000/AdventureWorks.svc/Products(680)</id> <title type="text"></title> <updated>22023-07-31T03:02:50Z</updated> <author> <name /> </author> <link rel="edit" title="Product" href="Products(680)" /> <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ProductCategory" type="application/atom+xml;type=entry" title="ProductCategory" href="Products(680)/ProductCategory" /> <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ProductModel" type="application/atom+xml;type=entry" title="ProductModel" href="Products(680)/ProductModel" /> <category term="AdventureWorksLTModel.Product" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> <content type="application/xml"> <m:properties> <d:ProductID m:type="Edm.Int32">680</d:ProductID> <d:Name>HL Road Frame - Black, 58</d:Name> <d:ProductNumber>FR-R92B-58</d:ProductNumber> <d:Color>Black</d:Color> <d:StandardCost m:type="Edm.Decimal">1059.3100</d:StandardCost> <d:ListPrice m:type="Edm.Decimal">1431.5000</d:ListPrice> <d:Size>58</d:Size> <d:Weight m:type="Edm.Decimal">1016.04</d:Weight> <d:SellStartDate m:type="Edm.DateTime">1998-06-01T00:00:00</d:SellStartDate> <d:SellEndDate m:type="Edm.DateTime" m:null="true" /> <d:DiscontinuedDate m:type="Edm.DateTime" m:null="true" /> <d:rowguid m:type="Edm.Guid">43dd68d6-14a4-461f-9069</m:properties> </content> </entry> Le specifiche prevedono la possibilità di passare delle opzioni con operatori e usare delle funzioni per filtrare, ordinare, paginare o aggregare. Esempio, ricercare le biciclette con numero di telaio 58. http://localhost:50000/AdventureWorks.svc/Products?$filter=Size eq '58' TPSIT 5 434 um 204 di Fare clic su File/Salva con nome… (CTRL+S) per ottenere il file PRODUCTS.XML. $metadata permette di ottenere una descrizione del modello, delle entità e delle proprietà. Se si referenzia l’indirizzo http://localhost:50000/AdventureWorks.svc/$metadata si ottiene la classe seguente. -<EntityContainer Name="AdventureWorksLTEntities" m:IsDefaultEntityContainer="true"> Da un punto di vista logico, è simile all’omonima classe di ADO.NET Entity Framework ma l’oggetto IQueryable, sul quale è possibile lavorare e creare espressioni non è tradotto in istruzioni SQL ma in un URI per l’interrogazione del servizio. In modo simile al WSDL (Web Services Description Language), permette al consumatore del servizio di eseguire chiamate REST e di conoscere quale sarà il tipo di risposta. TPSIT 5 434 um 205 di APPLICAZIONI ORIENTATE AI SERVIZI INTRODUZIONE I Windows Service, letteralmente servizi Windows, sono applicazioni progettate per essere eseguite senza che vi sia un controllo diretto da parte dell’utente. Nella maggior parte dei casi, sono avviati automaticamente da Windows, non appena il SO ha completato la fase di boot. Queste caratteristiche li rendono ideali per essere impiegati in ambito server, per eseguire azioni complesse e per fornire funzionalità di supporto alle altre applicazioni. Esempi, di servizi Windows. Spooler di stampa. Visualizzatore eventi. Windows Update. Gestione attività. Windows Firewall. IIS. Un servizio può essere lanciato, fermato, sospeso o riavviato in tre modi, anche se non presenta alcun tipo d’interfaccia utente e gira in background con un’identità indipendente dall’utente eventualmente collegato al PC. 1. Start/Tutti i programmi/Strumenti di amministrazione/Servizi. TPSIT 5 434 um 206 di 2. A linea di comando: NET START, NET STOP, NET PAUSE e NET CONTINUE. Microsoft Windows [Versione 6.1.7601] Copyright (c) 2009 Microsoft Corporation. Tutti i diritti riservati. C:\Users\Massimo>net start I seguenti servizi di Windows sono avviati: Acquisizione di immagini di Windows (WIA) Agente criteri IPsec Agente mapping endpoint RPC Alimentazione Audio di Windows avast! Antivirus BFE (Base Filtering Engine) Browser di computer Centro sicurezza PC Client di Criteri di gruppo Client DNS COM+ Event System Connessioni di rete EFS (Encrypting File System) … Esecuzione comando riuscita. 3. Programmazione: si usa la classe ServiceController. PROGRAMMAZIONE Un servizio Windows non è altro che una specializzazione della superclasse ServiceBase, contenuta nel namespace System.ServiceProcess. In pratica, per implementare un servizio bisogna definire un tipo che derivi dalla classe ServiceBase, configurare le sue proprietà e implementare alcuni metodi affinché il servizio possa essere eseguito. Fare clic su File/Nuovo/Progetto… (CTRL+N), nella finestra di dialogo Nuovo Progetto selezionare Modelli/Altri linguaggi/Visual C#/Windows/Servizio Windows. TPSIT 5 434 um 207 di Visual Studio genera il progetto seguente. File PROGRAM.CS È definita la classe statica Program con il metodo Main che è l’entry point del servizio. using System.ServiceProcess; namespace WindowsService1 { static class Program { static void Main() { ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new Service1() }; ServiceBase.Run(ServicesToRun); } } } File SERVICE1.CS A questo file sono associati un designer e una sezione di codice che contiene due metodi TPSIT 5 434 um 208 di OnStart (accetta parametri) e OnStop che sono eseguiti all’avvio e all’arresto del servizio. using System.ServiceProcess; namespace WindowsService1 { public partial class Service1 : ServiceBase { public Service1() { InitializeComponent(); } protected override void OnStart(string[] args) { } protected override void OnStop() { } } } File SERVICE1.DESIGNER.CS Il codice è generato dal designer in modo automatico, c’è la dichiarazione parziale della classe che rappresenta il servizio. namespace WindowsService1 { partial class Service1 { // variabile di progettazione necessaria private System.ComponentModel.IContainer components = null; // liberare le risorse in uso protected override void Dispose(bool disposing) { if (disposing && (components != null)) components.Dispose(); base.Dispose(disposing); } #region Codice generato da Progettazione componenti // Metodo necessario per il supporto della finestra di progettazione // non modificare il contenuto del metodo con l'editor di codice private void InitializeComponent() { components = new System.ComponentModel.Container(); this.ServiceName = "Service1"; } #endregion } } In compile-time il codice scritto dal programmatore, è unito a quello generato in modo automatico dal designer per formare un’unica classe, eseguire l’applicazione. Il programmatore deve implementare solo i metodi che sono eseguiti durante il ciclo di vita TPSIT 5 434 um 209 di del servizio: avvio, arresto e sospensione. Metodo OnContinue OnCustomCommand OnPause OnPowerEvent OnSessionChange OnShutdown Descrizione Eseguito al riavvio del servizio dopo una sospensione. Eseguito quando il SCM (Service Control Manager) inoltra un comando personalizzato (numero intero) al servizio. Eseguito quando il servizio è sospeso. Eseguito al cambio di stato dell’alimentazione del PC. Eseguito alla notifica di notifica ricevuta da una sessione Terminal Server. Eseguito prima dello spegnimento del PC. CONFIGURAZIONE Per rendere un servizio installabile, si deve creare un installer che va aggiunto allo stesso progetto. Fare clic con il tasto destro del mouse in Esplora soluzioni e selezionare la voce Aggiungi/Aggiungi nuovo elemento… (CTRL+MAIUSC+A)/Classe installer. Visual Studio aggiunge al progetto il file seguente. File WINDOWSSERVICEINSTALLER1.CS Anche in questo caso, al file è associato un designer. using System.ComponentModel; using System.Configuration.Install; namespace WindowsService1 { [RunInstaller(true)] public partial class WindowsServiceInstaller1 : System.Configuration.Install.Installer { public WindowsServiceInstaller1() { InitializeComponent(); } } } L’installer di un servizio è una classe che deriva dalla superclasse Installer contenuta nel namespace System.Configuration.Install. Quest’ultimo include due componenti che sono generati automaticamente alla creazione dell’installer. 1. Service Installer: di tipo ServiceInstaller per definire il comportamento del servizio e le sue dipendenze. 2. Service Process Installer: di tipo ServiceProcessInstaller per configurare il contesto di sicurezza del servizio. È buona regola di programmazione, anche se non è obbligatorio, rinominarli con nomi che facciano riferimento al servizio associato all’installer. La proprietà Account di ServiceProcessInstaller specifica il tipo di utente usato dal servizio e può assumere quattro valori. 1. User: utente Windows personalizzato, è il default. 2. LocalSystem: utente built-in di Windows con privilegi di amministratore. 3. NetworkService: utente built-in di Windows con bassi privilegi che sulla rete si presenta come utente autenticato con le credenziali del PC locale. 4. LocalService: utente built-in di Windows con bassi privilegi che sulla rete si presenta con credenziali anonime. Se le credenziali non sono indicate all’interno dell’installer, saranno richieste durante l’installazione. TPSIT 5 434 um 210 di È buona regola di programmazione, associare ad un servizio le credenziali più restrittive tra quelle possibili in relazione al tipo di operazioni svolte dal servizio stesso. INSTALLAZIONE Una volta compilato il progetto di sviluppo, si deve usare l’utility a linea di comando seguente con i privilegi di amministratore. L’utility opera in modalità transazionale: cancella ogni azione parziale svolta durante l’installazione in caso di errori imprevisti. È usata anche per disinstallare i servizi ma affinché la disinstallazione abbia successo, il servizio dev’essere prima arrestato. C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC>InstallUtil Utilità di installazione di Microsoft (R) .NET Framework, versione 4.0.30319.17929 Copyright (C) Microsoft Corporation. Tutti i diritti riservati. Uso: InstallUtil [/u | /uninstall] [option [...]] assembly [[option [...]] assembly] [...]] InstallUtil esegue i programmi di installazione in ciascun assembly specificato. Se si specifica l'opzione /u o /uninstall, gli assembly vengono disinstallati, altrimenti vengono installati. Diversamente da altre opzioni, /u può essere applicata a tutti gli assembly, indipendentemente dalla posizione in cui si trova sulla riga di comando. L'installazione viene effettuata in modo transazionale: se l'installazione di un assembly non riesce, viene ripristinato lo stato precedente dell'installazione di tutti gli altri assembly. La disinstallazione non avviene in modo transazionale. Le opzioni hanno il formato /switch=[valore]. Qualsiasi opzione che precede il nome di un assembly verrà applicata all'installazione di quell'assembly. Le opzioni sono cumulative ma sottoponibili a override. Le opzioni specificate per un assembly verranno applicate anche al successivo, a meno che non sia specificato un nuovo valore. Il valore predefinito per tutte le opzioni è empty o false, a meno che non venga specificato diversamente. Opzioni riconosciute: Opzioni per l'installazione di qualsiasi tipo di assembly: /AssemblyName Il parametro dell'assembly viene interpretato come nome dell'assembly (nome, impostazioni locali, PublicKeyToken, versione). Per impostazione predefinita tale parametro viene interpretato come nome di file dell'assembly sul disco. /LogFile=[nomefile] File in cui viene registrato lo stato. Se non viene specificato, tali informazi oni non verranno registrate nel log. Il nome predefinito è <nomeassembly>.InstallLog /LogToConsole={true|false} Se si imposta false, non verrà generato l'output da visualizzare sulla console. /ShowCallStack Se durante l'installazione si verifica un'eccezione, lo stack di chiamate verrà registrato nel log. /InstallStateDir=[nomedirectory] Directory in cui viene memorizzato il file InstallState. La directory predefinita è quella dell'assembly. I singoli programmi di installazione utilizzati in un assembly potrebbero ricono scere altre opzioni. Per conoscere tali opzioni, eseguire InstallUtil TPSIT 5 434 um 211 di specificando i percorsi degli assembly sulla riga di comando con l'opzione /? o /help. Progettare un servizio per monitorare una particolare cartella del file system e registrare nel Visualizzatore eventi tutte le modifiche dei file contenuti. TPSIT 5 434 um 212 di MODULO 3 PROTOCOLLI APPLICATIVI Browser WebClient FTP POP SPAM SNMP Voice over IP TV over IP eMule TOR BT IRC Second Life Facebook Bing RSS Twitter TPSIT 5 434 um 213 di BROWSER INTRODUZIONE Qual è l’attività più dispendiosa e meno produttiva di un programmatore di applicazioni desktop? È il disegno della GUI. Qual è la tecnologia che permette la massima flessibilità in campo di disegno della GUI? È l’HTML arricchito dai CSS (Cascading Style Sheet) e da JavaScript. Inoltre, ogni applicazione è sviluppata partendo dal concetto che i dati sono residenti su un file o su un DB al quale l’applicazione accede direttamente. Con Internet, invece, c’è la necessità di rendere disponibili all’esterno i dati del DB in modo da essere consumati da applicazioni esterne. Per standardizzare la distribuzione dei dati sono stati progettati protocolli di comunicazione standard tra piattaforme eterogenee. Quindi le applicazioni non accedono più direttamente ad un DB ma a dei servizi in rete: LAN, intranet, extranet e Internet che espongono i dati del DB. Accedere ai dati via rete è completamente diverso dall’accedere ad un DB locale. Per recuperare i dati da un DB si usa una sola tecnica e si ha un solo formato: ADO.NET (ActiveX Data Objects) che astrae la complessità di ogni DB. Per recuperare i dati via rete si hanno a disposizione i protocolli di comunicazione TCP, HTTP, UDP e WebSocket e i formati di serializzazione HTML, JSON, RSS e XML. WEBBROWSER Permette l’interazione tra applicazione host e il controllo WebBrowser, in pratica consente di utilizzare, in tutto o in parte, una perfetta GUI web-like in modo semplice ed efficace. Il controllo espone le seguenti proprietà. CanGoBack Determina se sia possibile tornare indietro nella cronologia. CanGoForward Determina se sia possibile andare avanti nella cronologia. Document Un oggetto di tipo HtmlDocument contenente tutte le informazioni sulla pagina. DocumentStream Permette di leggere la pagina web come da un file, restituisce un oggetto System.IO.Stream. DocumentText Restituisce o imposta il codice della pagina, dopo aver caricato una pagina, contiene il suo codice HTML, modificando questa proprietà, anche la pagina visualizzata sarà rielaborata e ricaricata di conseguenza. DocumentTitle Il titolo del documento. DocumentType TPSIT 5 434 um 214 di Il tipo del documento. GoBack Torna indietro alla pagina precedente. GoForward Procede alla pagina successiva. GoHome Ritorna alla pagina iniziale, per ottenere l’indirizzo di quest’ultima, ricercare nel registro di sistema le preferenze che l’utente ha impostato per il browser predefinito. GoSearch Si reca alla pagina di ricerca predefinita. IsBusy Indica se il controllo sta caricando un nuovo documento. IsOffline Indica se il controllo è in modalità offline, sta processando pagine web su disco fisso. IsWebBrowserContextMenuEnabled Determina se sia attivo il menu contestuale predefinito per il browser. Navigate(p) Apre la pagina referenziata dall’indirizzo URL p. ObjectForScripting Consente di esporre un oggetto al codice JavaScript della pagina contenuta. Print Stampa il documento aperto con i settaggi impostati della stampante corrente. ReadyState Restituisce lo stato del controllo, l’enumeratore può assumere cinque valori. 1. Complete: pagina completa. 2. Interactive: le parti della pagina caricate sono sufficienti a garantire un minimo di interazione con l’utente. 3. Loaded: il documento è caricato e inizializzato ma non tutti i dati sono ancora stati ricevuti. 4. Loading: il documento è in caricamento. 5. Uninitialized: nessun documento è stato aperto. ShowPageSetupDialog Visualizza le impostazioni pagina con una finestra di dialogo Internet Explorer. ShowPrintDialog Visualizza la finestra di stampa d’Internet Explorer. ShowPrintPreviewDialog Visualizza l’anteprima di stampa in una finestra Internet Explorer. TPSIT 5 434 um 215 di ShowPropertiesDialog Visualizza la finestra delle proprietà pagina come Internet Explorer. ShowSaveAsDialog Visualizza la finestra di dialogo di salvataggio d’Internet Explorer. URL Restituisce un oggetto URI rappresentante l’indirizzo della pagina caricata. Version La versione d’Internet Explorer installata. Ci sono molti browser disponibili ma l’unico modo di far fare ad un’applicazione quello che si vuole nel modo che si vuole è di scriversela! Esempio, progettare un browser, caricare al proprio interno una pagina web e visualizzarla. Trascinare sul form il controllo WebBrowser, una volta posizionato si mostra come un’area bianca perchè non contiene ancora alcuna pagina e aggiungere anche i controlli seguenti. Inserire un ToolStrip. Tutti i pulsanti seguenti sono di default disattivati, poiché all’inizio non è caricata alcuna pagina e di conseguenza non si può effettuare alcuna operazione. btnBack: per andare indietro. btnForward: per andare avanti. btnRefresh: per aggiornare la pagina. btnVai: per avviare il caricamento della pagina. txtUrl: per contenere l’indirizzo cui recarsi. Public Class Form1 Private Sub Form1_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Resize ' per modificare in modo dinamico la dimensione del browser nella mia finestra Dim altezza As Integer = Me.Height Dim lunghezza As Integer = Me.Width altezza -= 100 lunghezza -= 20 wbBrowser.Height = altezza wbBrowser.Width = lunghezza End Sub Private Sub txtUrl_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) ' quando si preme invio durante la digitazione, naviga alla pagina indicata TPSIT 5 434 um 216 di If e.KeyCode = Keys.Enter Then wbBrowser.Navigate(txtUrl.Text) ' poiché s'inizia a navigare, si ferma il caricamento, quindi attiva btnCancel btnCancel.Enabled = True End If End Sub Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) ' ferma l'attività del browser wbBrowser.Stop() btnCancel.Enabled = False btnRefresh.Enabled = True End Sub Private Sub wbBrowser_DocumentCompleted(ByVal sender As System.Object, ByVal e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs) Handles wbBrowser.DocumentCompleted ' l'evento DocumentCompleted si verifica quando una pagina è stata completamente ' caricata, se anche una sola delle parti della pagina non è completa, l'evento non è ' generato, per evitare brutte soprese, potete utilizzare l'evento Navigated che si ' verifica dopo la navigazione indipendentemente dal successo o meno dell'operazione btnCancel.Enabled = False btnBack.Enabled = wbBrowser.CanGoBack btnForward.Enabled = wbBrowser.CanGoForward btnRefresh.Enabled = True End Sub Private Sub wbBrowser_Navigating(ByVal sender As System.Object, ByVal e As System.Windows.Forms.WebBrowserNavigatingEventArgs) Handles wbBrowser.Navigating btnCancel.Enabled = True ' la proprietà StatusText contiene in forma leggibile ' un resoconto dell'operazione che il controllo sta svolgendo lblStatus.Text = wbBrowser.StatusText End Sub Private Sub wbBrowser_Navigated(ByVal sender As System.Object, ByVal e As System.Windows.Forms.WebBrowserNavigatedEventArgs) Handles wbBrowser.Navigated ' l'evento Navigating si genera prima della navigazione btnCancel.Enabled = False btnBack.Enabled = wbBrowser.CanGoBack btnForward.Enabled = wbBrowser.CanGoForward btnRefresh.Enabled = True lblStatus.Text = "Pagina caricata" End Sub Private Sub btnRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRefresh.Click wbBrowser.Refresh() End Sub Private Sub btnBack_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnBack.Click wbBrowser.GoBack() End Sub Private Sub btnForward_Click(ByVal sender As System.Object, ByVal e As TPSIT 5 434 um 217 di System.EventArgs) Handles btnForward.Click wbBrowser.GoForward() End Sub Private Sub btnVai_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnVai.Click If txtUrl.Text <> "" Then wbBrowser.Navigate(txtUrl.Text) Else MessageBox.Show("Inserire l'indirizzo della pagina web.", "Errore!", MessageBoxButtons.OK, MessageBoxIcon.Error) End If End Sub End Class Progettare un’applicazione che indica se la connessione di rete è attiva o meno. Tutte le volte che lo stato della connessione di rete è modificato, è generato l’evento Computer.Network.NetworkAvailabilityChanged. La logica che aggiorna il pannello di stato è inclusa nel metodo SetConnectionStatus dell’oggetto MainForm. TPSIT 5 434 um 218 di Startup Generato all’avvio dell’applicazione, prima della creazione del form di avvio. Shutdown Generato dopo la chiusura di tutti i form dell'applicazione. L’evento non è generato se l’applicazione termina in modo anormale. UnhandledException Generato se l’applicazione rileva un’eccezione non gestita. StartupNextInstance Generato quando si avvia un’applicazione a istanza singola che è già attiva. NetworkAvailabilityChanged Generato all’attivazione o alla disattivazione della connessione di rete, aggiorna lo stato di connettività della rete nel form principale ogni volta che lo stato della connessione cambia. All’avvio dell’applicazione, impostare lo stato della connessione nel controllo StatusStrip, quindi aggiornarlo per indicare lo stato corrente della connessione. TPSIT 5 434 um 219 di WEBCLIENT INTRODUZIONE Nel protocollo TCP occorre realizzare una specifica controparte, il “server”, con la quale instaurare una connessione per l’invio e la ricezione dei dati. Con i protocolli HTTP, FTP o SMTP, la parte “server” è eseguita dal server web, per esempio IIS, questo servizio integrato con il SO rende facile la distribuzione d’informazioni pubbliche attraverso la rete Internet. Un server web ben configurato e collegato alla rete, infatti, è accessibile da ogni client che ne esegua specifiche richieste, per esempio, attraverso la porta 80, riservata proprio al protocollo http, con il browser si ha la possibilità di consultare le informazioni presenti sul server con il linguaggio HTML. Per queste operazioni di base si ha a disposizione la classe WebClient che fornisce metodi comuni per l’invio o la ricezione di dati da qualsiasi risorsa locale, rete intranet o Internet identificata da un URI. La classe WebClient utilizza la classe WebRequest per fornire l’accesso alle risorse. Le istanze di WebClient possono accedere ai dati con qualsiasi classe WebRequest discendente registrata con il metodo WebRequest.RegisterPrefix. Esempio, leggere il contenuto di un file di testo in un sito web. WebClient File MAINWINDOW.XAML.CS Il metodo OpenReadTaskAsync accede ad una risorsa presente in un sito web, apre uno stream di dati e recupera il testo corrispondente. using System; using System.Windows; TPSIT 5 434 um 220 di using System.Net; using System.IO; namespace WebClient1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private async void btnAvanti_Click(object sender, RoutedEventArgs e) { var client = new WebClient(); var dataStream = await client.OpenReadTaskAsync(new UriBuilder("http", "localhost", 1503, txbNome.Text).Uri); var reader = new StreamReader(dataStream); var data = await reader.ReadToEndAsync(); txbTesto.Text = data; } private async void btnsalva_Click(object sender, RoutedEventArgs e) { var client = new WebClient(); // invia il testo nella TextBox allo stream aperto con OpenWriteTaskAsync var dataStream = await client.OpenWriteTaskAsync(String.Format(@"C:\{0}", txbNome.Text)); var str = new StreamWriter(dataStream); await str.WriteAsync(txbTesto.Text); str.Close(); dataStream.Close(); } } } File MAINWINDOW.XAML <Window x:Class="WebClient1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="WebClient" Height="200" Width="300" WindowStartupLocation="CenterScreen"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal"> <TextBox Height="23" x:Name="txbNome" Text="Testo.txt" Width="180" /> <Button Content=">>" Height="23" x:Name="btnAvanti" Click="btnAvanti_Click" Width="35" /> <Button Content="Salva" Height="23" x:Name="btnSalva" Click="btnsalva_Click" Width="35" /> </StackPanel> <TextBox Grid.Row="1" x:Name="txbTesto" /> TPSIT 5 434 um 221 di </Grid> </Window> WebApplication1 Si utilizza il sito web del .NET Framework per rendere l’esempio più facilmente eseguibile, senza dover effettuare particolari configurazioni. File TESTO.TXT File UPLOAD.ASPX.CS Recupera lo stream in ingresso con la proprietà InputStream dell’oggetto Request, di tipo HttpRequest e scrive sul file system con il metodo OpenWrite di FileStream. Con il metodo Write dell’oggetto Response si scrive il testo del messaggio di risposta, per recuperarlo nell’applicazione. using System; using System.Web; using System.IO; namespace WebApplication1 { public partial class Upload : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (this.Request.QueryString["path"] != null) { string _physicalApplicationPath = HttpContext.Current.Request.PhysicalApplicationPath; string _path = string.Format(@"{0}\{1}", _physicalApplicationPath, Convert.ToString(this.Request.QueryString["path"])); using (FileStream stream = File.OpenWrite(_path)) { byte[] dataByte = new byte[1024]; int i = 0; TPSIT 5 434 um 222 di do { i = this.Context.Request.InputStream.Read(dataByte, 0, 1024); if (i > 0) stream.Write(dataByte, 0, i); } while (i > 0); } this.Response.Clear(); this.Response.Write("OK"); this.Response.End(); } else { this.Response.Clear(); this.Response.Write("Errore!"); this.Response.End(); } } } } File UPLOAD.ASPX <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Upload.aspx.cs" Inherits="WebApplication1.Upload" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <div></div> </form> </body> </html> TPSIT 5 434 um 223 di Si è richiamato con successo un file TXT, poichè si tratta di un tipo di risorsa il cui MIME è mappato e quindi gestito da IIS. Il registro di mappatura prevede una configurazione predefinita per la quale alcune risorse entrano nel processo di gestione di ASP.NET, mentre altre sono semplicemente restituite dal server web sotto forma di risposta. Oltre a OpenReadTaskAsync, è possibile leggere i dati con i metodi seguenti. DownloadData Restituisce direttamente l’array di byte di una risorsa, è un metodo sincrono, ossia blocca il funzionamento dell’applicazione fino a quando il download non è terminato. DownloadDataTaskAsync È la corrispondente modalità asincrona di DownloadData che esegue la richiesta e svincola il thread dall’attesa della ricezione dei dati. Le versioni asincrone dei metodi sono riconoscibili dal suffisso Async dopo il nome del metodo, questi eseguono il download in un thread separato, perciò non interferiscono con le normali operazioni dell’applicazione e si richiamano usando esattamente gli stessi parametri delle versioni sincrone. DownloadFile Esegue il download di un file remoto su un file locale. DownloadString Restituisce direttamente il contenuto della risorsa, eseguendo automaticamente l’encoding in stringa con la modalità specificata nella proprietà Encoding. È possibile inviare testi, dati e file con i metodi seguenti. OpenWrite Apre uno stream in scrittura con la risorsa remota. TPSIT 5 434 um 224 di UploadData Invia direttamente un array di byte ad una risorsa remota. UploadFile Esegue l’upload di un file locale. UploadString Invia una stringa ad una risorsa remota, previo encoding dei byte con la modalità specificata nella proprietà Encoding. UploadValues Invia una collezione nome/valore d’invio POST di richieste HTTP. Ognuno di questi metodi dispone della controparte asincrona che esegue la richiesta e svincola il thread; l’accesso alle risorse del file system è vincolato alle autorizzazioni dell’utente con cui si esegue l’applicazione. La classe WebClient incapsula l’elaborazione con i protocolli applicativi relativi al tipo di risorsa cui si ha accesso, in particolare, nell’invio di dati con il protocollo HTTP, è automaticamente utilizzato il metodo POST, qualora non sia specificato come parametro nei metodi che lo consentono. Nell’aggiornamento dei dati di questo tipo di risorse, è necessario che il server web intercetti tali richieste, recuperi lo stream ed esegua l’aggiornamento nel proprio contesto applicativo. FTP INTRODUZIONE Consente ai client FTP di caricare (upload), scaricare (download), cancellare, rinominare e spostare file sul/dal server, si basa sul codice ASCII e in particolare ne usa, solo, la parte inferiore, ASCII a 7 bit. I servizi FTP sono richiesti al server sulla porta 21 (Porta FTP di comando) e poi il trasferimento dei file avviene sulla porta 20 (Porta FTP dati). Le entità coinvolte in una sessione FTP sono due. 1. Il PC remoto usa il client FTP che invia al server le richieste di trasferimento; sono inclusi nei browser web e nei SO o sono forniti come applicazionii autonome. 2. Un server FTP attende le richieste del client, esistono numerosi server FTP per tutti i SO e un numero ancora maggiore di client, sia testuali sia grafici. Il modello del protocollo FTP è connesso ovvero, una volta stabilita la connessione, il client può eseguire una serie di operazioni sul server prima di effettuare la disconnessione. Al server FTP è possibile accedere anche attraverso un browser, in questo caso l’indirizzo da specificare è del tipo ftp://ftp.miosito.com, con questo tipo di richiesta le informazioni visualizzate non saranno delle pagine HTML ma dei nomi di cartelle e di file. In una configurazione client/server FTP, il server è un sistema in cui sono archiviati i file organizzati in cartelle che attraverso un client FTP possono essere manipolati in remoto. Sul server FTP sono definiti gli account dei client. I client dopo aver specificato login e password possono accedere, in R/W, ad una parte oppure a tutte, dipende dal tipo di account, le cartelle del server. Su alcuni server, di solito, è definito l’account anonymous con password un indirizzo di TPSIT 5 434 um 225 di posta elettronica o guest che contiene le cartelle pubbliche del sito. Il protocollo FTP, per la trasmissione di dati tra host basato su TCP che ha subìto una lunga evoluzione negli anni, il primo meccanismo di trasmissione file risale al 1971, fu sviluppato presso il MIT (Massachusetts Institute of Technology) è ancora utilizzato soprattutto dagli host providers per garantire ai clienti l’accesso al proprio sito web. Gli obiettivi principali di FTP sono i seguenti. Promuovere la condivisione di file, programmi o dati. Incoraggiare l’uso indiretto o implicito di computer remoti. Risolvere in maniera trasparente l’incompatibilità tra differenti sistemi di stoccaggio file. Trasferire dati in maniera affidabile ed efficiente. La specifica di FTP non prevede alcuna cifratura per i dati scambiati tra client e server, questo comprende nomi utenti, password, comandi, codici di risposta e file trasferiti i quali possono essere visionati in determinate situazioni, per esempio in ambienti intranet. Per ovviare al problema è stata definita una nuova specifica che aggiunge al protocollo FTP originale un layer di cifratura SSL/TLS più una nuova serie di comandi e codici di risposta, il protocollo prende il nome di FTPS (FTP Secure). CLI C:\Documents and Settings\Administrator>ftp -? Trasferisce i file da e verso un computer che dispone di un servizio server FTP (detto anche daemon). È possibile utilizzare FTP in modo interattivo. FTP [-v] [-d] [-i] [-n] [-g] [-s:nomefile] [-a] [-w:dimensionifinestra] [-A] [host] -v Disabilita la visualizzazione delle risposte del server remoto. -n Disabilita l’autoconnessione dopo la connessione iniziale. -i Disattiva le richieste interattive durante i trasferimenti di più file. -d Attiva il debug. -g Disattiva il globbing su nomi file (vedere il comando GLOB). -s:nomefile Specifica un file di testo contenente comandi FTP; i comandi vengono eseguiti automaticamente all’avvio di FTP. -a Utilizza qualunque interfaccia locale nel binding delle connessioni dati. -A Connessione anonima. -w:dimensionibuffer Prevale sulle dimensioni predefinite del buffer 4096. host Specifica il nome o l’indirizzo IP dell’host remoto a cui connettersi. Note: - i comandi mget e mput accettano y/n/q per sì/no/chiudi. - Per annullare i comandi, premere CTRL+C. I comandi FTP sono i seguenti. C:\Documents and Settings\Administrator>ftp ftp> help I comandi possono essere abbreviati. I comandi sono: ! delete literal prompt send ? debug ls put status append dir mdelete pwd trace ascii disconnect mdir quit type bell get mget quote user binary glob mkdir recv verbose bye hash mls remotehelp cd help mput rename close lcd open rmdir ftp>quit TPSIT 5 434 um 226 di Esempio, sessione FTP, con interfaccia a caratteri, account utente pippo. Host: alice.cs.unibo.it. ftp alice.cs.unibo.it Connected to alice.cs.unibo.it. 220 alice.cs.unibo.it FTP server (Version 6.2/OpenBSD/Linux-0.10) ready. Name (alice.cs.unibo.it:pippo): pippo 331 Password required for pippo. Password: 230- Linux alice 2.2.19pre17 #5 Wed Apr 4 15:05:17 CEST 2001 i686 unknown 230- SCIENZE DELL’INFORMAZIONE - UNIVERSITA’ DI BOLOGNA CS.UNIBO.IT 230- -------------------------------------230- cluster: Linux CS 230- -------------------------------------230-(lm 10.01.2003) 230- -------------------------------------230 User pippo logged in. Remote system type is UNIX. Using binary mode to transfer files. ftp> A questo punto l’host alice.cs.unibo.it è pronto per l’operazione FTP vera e propria. Download del file PIPPO.TXT da alice.cs.unibo.it. ftp> get pippo.txt local: pippo.txt remote: pippo.txt 227 Entering Passive Mode (130,136,2,13,17,206) 150 Opening BINARY mode data connection for ‘pippo.txt’ (0 bytes). 226 Transfer complete. ftp> L’operazione inversa, upload, è eseguita usando il comando put come segue. ftp> put pippo.txt local: pippo.txt remote: pippo.txt 227 Entering Passive Mode (130,136,2,13,17,100) 150 FILE: pippo.txt 226 Transfer complete. ftp> ARCHITETTURA Il modello è il seguente. PI (Protocol Interpreter) è l’interprete del protocollo, utilizzato da client, User-PI e server, Server-PI, per lo scambio di comandi e risposte, in gergo comune si riferisce ad esso come “canale comandi”. DTP (Data Transfer Process) è il processo di trasferimento dati, utilizzato da client, UserDTP e server, Server-DTP, per lo scambio di dati, in gergo comune si riferisce ad esso come “canale dati”. FTP utilizza due connessioni separate per gestire comandi e dati. Un server FTP rimane in ascolto sulla porta 21 TCP cui si connette il client. La connessione da parte del client determinerà l’inizializzazione del canale comandi attraverso il quale client e server si scambieranno comandi e risposte. Lo scambio di dati richiede l’apertura del canale dati il quale può essere di due tipi. 1. In un canale dati di tipo attivo il client apre una porta tipicamente random (1023), tramite il canale comandi rende noto il numero di tale porta al server e attende che TPSIT 5 434 um 227 di esso si connetta, una volta che il server ha attivato la connessione dati al client FTP, quest’ultimo effettua il binding della porta sorgente alla porta 20 del server FTP, a tale scopo possono essere impiegati i comandi PORT o EPRT, a seconda del protocollo di rete utilizzato IPv4 o IPv6. 2. In un canale dati di tipo passivo il server apre una porta tipicamente random (1023), tramite il canale comandi rende noto il numero di tale porta al client e attende che esso si connetta, a tale scopo possono essere impiegati i comandi PASV o EPSV, a seconda del protocollo di rete utilizzato IPv4 o IPv6. Sia il canale comandi sia il canale dati sono delle connessioni TCP, FTP crea un nuovo canale dati per ogni file trasferito all’interno della sessione utente, mentre il canale comandi rimane aperto per l’intera durata della sessione utente, in altre parole il canale comandi è persistente mentre il canale dati non lo è. PROGRAMMAZIONE Per gestire le risorse esposte da un server FTP, si usano le classi FtpWebRequest e FtpWebResponse che ereditano da WebRequest. Sono due classi comode da utilizzare ma hanno un limite: utilizzano il modello disconnesso delle classi per la gestione delle richieste HTTP, in pratica, ad ogni richiesta FTP, occorre inviare le credenziali di accesso al server e collegarsi. Occorre instanziare un oggetto FtpWebRequest impostandone modalità di connessione e URL, impostare il comando FTP con la proprietà Method dell’oggetto FtpWebRequest. Grazie alla proprietà Method si specificano le diverse operazioni che si possono compiere sia con una singola risorsa remota sia nell’intero contesto di una cartella. Eventualmente scrivere sul flusso dei dati dal client al server recuperando lo stream in uscita attraverso la funzione GetRequestStream dell’oggetto FtpWebRequest, questa operazione serve solo per l’upload dei file verso il server. Recuperare la risposta del server con la funzione GetResponse dell’oggetto FtpWebRequest che restituisce un oggetto di tipo FtpWebResponse e, attraverso questo, recuperare il flusso di dati dal server verso il client con il metodo GetResponseStream. Si possono descrivere queste operazioni con i seguenti membri della classe WebRequestMethods.Ftp. AppendFile DeleteFile DownloadFile GetDateTimestamp GetFileSize ListDirectory ListDirectoryDetails MakeDirectory PrintWorkingDirectory RemoveDirectory Rename UploadFile UploadFileWithUniqueName Esempio, upload di un file. TPSIT 5 434 um 228 di File MAINWINDOW.XAML.CS C’è la possibilità si specificare le credenziali di accesso con la proprietà Credentials di tipo ICredential grazie all’oggetto NetworkCredential. In base a queste credenziali, il server FTP può o meno autorizzare le operazioni che si andranno a eseguire. using System.IO; using System.Net; using System.Windows; using System.Windows.Media.Imaging; using Microsoft.Win32; namespace FtpApplication { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void btnSfoglia_Click(object sender, RoutedEventArgs e) { OpenFileDialog fileDialog = new OpenFileDialog { Filter = "immagini (*.jpg)|*.jpg" }; bool? show = fileDialog.ShowDialog(); if (show.HasValue & show.Value) { using (Stream fileStream = fileDialog.OpenFile()) { this.txbNome.Text = fileDialog.SafeFileName; this.txbNome.Tag = fileDialog.FileName; MemoryStream dataStream = new MemoryStream(); byte[] dataByte = new byte[1024]; int i = 0; do { i = fileStream.Read(dataByte, 0, 1024); if (i > 0) dataStream.Write(dataByte, 0, i); } while (i > 0); dataStream.Seek(0L, SeekOrigin.Begin); BitmapImage bmpImage = new BitmapImage(); bmpImage.BeginInit(); bmpImage.StreamSource = dataStream; bmpImage.EndInit(); this.imgImmagine.Source = bmpImage; } } } TPSIT 5 434 um 229 di private async void btnInvia_Click(object sender, RoutedEventArgs e) { using (Stream fileStream = File.OpenRead(this.txbNome.Tag.ToString())) { var request = (FtpWebRequest)WebRequest.Create(string.Format("ftp://127.0.0.1/{0}", this.txbNome.Text)); request.Method = WebRequestMethods.Ftp.UploadFile; request.Credentials = new NetworkCredential("username", "password"); Stream requestStream = await request.GetRequestStreamAsync(); byte[] sendBuffer = new byte[1024]; int bytesRead = 0; do { bytesRead = await fileStream.ReadAsync(sendBuffer, 0, 1024); if (bytesRead > 0) await requestStream.WriteAsync(sendBuffer, 0, bytesRead); } while (bytesRead > 0); requestStream.Close(); this.txbNome.Text = ((FtpWebResponse)request.GetResponse()).StatusDescription; } } } } File MAINWINDOW.XAML <Window x:Class="FtpApplication.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Applicazione FTP" Height="350" Width="425" WindowStartupLocation="CenterScreen"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal" FlowDirection="LeftToRight" HorizontalAlignment="Center" > <TextBlock Height="23" x:Name="txbNome" Width="180" /> <Button Content="Sfoglia" Height="23" Click="btnSfoglia_Click" x:Name="btnSfoglia" Width="55" /> <Button Content="Invia" Height="23" Click="btnInvia_Click" x:Name="btnInvia" Width="55" /> </StackPanel> <Image Grid.Row="1" TPSIT 5 434 um 230 di x:Name="imgImmagine" Stretch="Uniform" /> </Grid> </Window> Progettare un client FTP che utilizza il modello disconnesso per le richieste. File FTPMANAGER.CS Per la gestione delle operazioni FTP, il costruttore non fa altro che impostare il valore delle proprietà. Tutte le funzioni della classe FTPManager utilizzano un metodo base Connect che crea una richiesta FTP al server tramite il metodo Create e associa alla richiesta le credenziali di accesso da inviare al server. Il metodo Create restituisce un oggetto di tipo WebRequest che si converte in FtpWebRequest. Il metodo GetFileList è usato per ottenere la lista dei file presenti in una cartella del server FTP, restituisce una lista di stringhe in cui saranno presenti i nomi dei file presenti della cartella scelta, come prima cosa costruire l’URI cui inviare la richiesta. Eseguire il metodo Connect passandogli come parametro la variabile _Uri. Impostare, quindi, il metodo della richiesta a ListDirectory in modo da ottenere in risposta la lista delle cartelle. Il metodo GetResponse della classe WebRequest fornisce un oggetto di tipo WebResponse; a questo punto è sufficiente ottenere uno StreamReader tramite il metodo GetResponseStream e leggerlo tramite un ciclo While. Il metodo Dowload è usato per scaricare i file in locale. Creare un FileStream in cui andare a salvare il file scaricato del server FTP. Impostare, quindi, l’URI ed eseguire il metodo Connect. Il metodo della richiesta dovrà essere impostato a DownloadFile. Una volta ottenuto lo stream di risposta, leggere il contenuto e salvare i byte letti nel FileStream creato in precedenza. È istanziato un oggetto di tipo FTPManager e eseguito il metodo Download. Difetti dell’applicazione: il modello disconnesso non è il massimo in fatto di performance e sicurezza, esempio fare l’upload di cento file su una cartella: si devono effettuare cento TPSIT 5 434 um 231 di connessioni e cento disconnessioni invece di una soltanto. TPSIT 5 434 um 232 di Progettare un client FTP che vive all’interno di Internet Explorer per uploadare file in remoto senza scomodare applicazioni di terze parti. Il problema è quello della pubblicazione di applicazioni web presso provider, dove non si ha il controllo sui permessi assegnati alle cartelle, il provider, infatti, prevede soltanto una TPSIT 5 434 um 233 di cartella nella quale l’utente web ha anche diritti di scrittura: la cartella PUBLIC del sito. Alcune applicazioni invece richiedono percorsi di scrittura diversi, purtroppo non parametrizzati, grazie all’API FTP è possibile scrivere non attraverso il file system ma attraverso l’account FTP che il provider mette a disposizione. L’applicazione permette di svolgere i seguenti compiti. Consentire il browsing delle cartelle di un server FTP. Consentire la cancellazione, il download e l’upload di file. Configurare la cartella virtuale del servizio FTP, con il nome ftp, di IIS puntandolo sul disco C:\ del PC che quindi risponde all’URL ftp://localhost/ftp. La maschera di login chiede la URL del server FTP, il nome utente e la password per la connessione, una volta fornite queste informazioni, l’applicazione web presenta una vista sulla cartella principale, root, del server FTP. Cliccando sulle cartelle si apre la cartella inferiore, mentre cliccando sui file si procede al download, inoltre, nel browser è presente un campo upload per i file da caricare, un link per cancellare il file, una barra degli indirizzi e un bottone per il logout. Inserire le classi nella cartella App_Code e usare i file ASPX per la sola logica di presentazione perché il codice deve risiedere in file separati: modello code-behind. La cartella App_Code contiene le classi deputate a gestire il colloquio FTP. File FTPCLIENT.VB Si crea la classe base che sarà utilizzata dal lato ASP.NET per colloquiare con il server FTP, sono stati dichiarati quattro campi. 1. Credentials di tipo NetworkCredential che rappresenta le credenziali di autenticazione presso il server. 2. Port di tipo Integer che rappresenta la porta TCP per la connessione, la 21. 3. Proxy di tipo IWebProxy che rappresenta l’eventuale proxy da utilizzare nella connessione. 4. Url di tipo Uri che rappresenta l’URL della richiesta FTP. Tali variabili sono valorizzate nei costruttori di cui sono fornite due versioni. Nel secondo costruttore, più semplice e di uso più comune, si omette il proxy e si costruisce direttamente la credenziale a partire da userName e password. È stata poi aggiunta una funzione, di uso comune a tutti i metodi, per creare la richiesta al server FTP, il parametro method, rappresenta il comando FTP da inviare al server. Non è necessario scrivere manualmente la stringa del comando in quanto è possibile utilizzare le costanti esposte dalla classe System.Net.Web RequestMethods.Ftp. TPSIT 5 434 um 234 di I metodi pubblici esposti dalla classe sono i seguenti. DeleteFile invia un comando di cancellazione del file. DownloadFile scarica un file dal server come flusso. List ottiene una lista di oggetti contenuti in una directory del server. UploadFile invia un file al server come matrice di byte. Parsing L’API FTP ha due comandi per ottenere la lista di oggetti di una cartella nel server. 1. WebRequestMethods.Ftp.List Directory fornisce soltanto il nome degli oggetti senza dire, ad esempio, se si tratta di file o cartelle. 2. WebRequestMethods.Ftp.ListDirectory Details fornisce la lista comprendente informazioni su: natura dell’oggetto file o cartella, dimensioni e data di creazione. Il problema è che la struttura della lista dipende dal server, nei server Unix/Linux la risposta sarà di un tipo, mentre per i server Windows di un altro tipo. Per questo motivo si è dovuto creare una classe astratta, FtpListParserBase che poi sarà ereditata dalle classi deputate ad interpretare la risposta del server. Si è implementato FtpListParserIIS e FtpListParserUnix ognuna di queste sotto classi deve avere un metodo pubblico, Match, che, partendo dalla prima linea di testo ricevuta dal server, determina se è essa a poter effettuare il parsing oppure no. Il parsing è effettuato con le Regular Expressions, le classi specializzate di FtpListParserBase si comportano tutte nello stesso modo prendono una matrice di linee di testo, ne interpretano il contenuto e creano una lista corrispondente di oggetti. Questi oggetti sono basati su un’altra classe, FtpListItem che raccoglie le informazioni dalla linea di testo inviata dal server dimensioni, data, nome. TPSIT 5 434 um 235 di Tutti gli oggetti FtpListItem sono raccolti dal parser in un insieme FtpList che dispone di metodi per l’ordinamento e la contestualizzazione degli elementi contenuti. Il metodo List della classe FtpClient legge le linee trasmesse dal server, sceglie il parser adatto e restituisce un insieme di oggetti FtpListItem. Il metodo DeleteFile cancella un file dal server FTP. Il metodo DownloadFile restituisce un flusso, stream, dei dati provenienti dal server FTP. Il metodo UploadFile restituisce la stringa contenente il messaggio di ritorno del server, tuttavia richiede, come parametro la matrice di byte che rappresenta il file da caricare. Lato Web La pagina ASP.NET utilizza la classe FTPclient, per l’implementazione è stato utilizzato il flusso Request Response che è sempre alla base del ciclo della pagina. Su OnLoad della pagina, quindi, s’innesta tutto il ciclo delle operazioni. Si controlla se la Request o la Session contengono i valori di autenticazione, se non li contengono sarà mostrata la maschera di login, altrimenti sarà effettuato il listing richiamando il metodo. GetList richiama una serie di metodi definiti nello script della pagina volti a formattare l’output, anche per richiamare la funzione List di FtpClient si è utilizzato una versione statica del metodo nella classe FtpClient. I metodi statici, shared, sono molto comodi perché permettono di richiamare al volo una funzione senza dover tutte le volte instanziare un nuovo oggetto. Prima di effettuare il browsing della cartella corrispondente all’URL corrente, OnLoad dovrà però valutare se tra le operazioni richieste c’è una cancellazione o un upload di file, queste operazioni sono racchiuse in due metodi. EvalDelete funziona esaminando se nella Request esiste il valore del che sarà corrispondente alla URL del file da cancellare, se esiste richiama la funzione statica DeleteFile di FtpClient. EvalUpload, invece, valuta se il flusso Request contenga o meno un Post di file ed eventualmente richiama la funzione statica UploadFile di FtpClient. File DOWNLOADFILE.ASPX Per effettuare il download del nuovo file è necessario progettare questa pagina ASP.NET, alla quale tutti i nomi dei file che compaiono nella tabella saranno linkati. In pratica in OnLoad si richiama la funzione statica DownloadFile di FtpClient e si legge il flusso che restituisce scrivendolo nella Response. Il browser capisce che si tratta di un file da scaricare e non di una pagina da mostrare in quanto gli si è passato, nella Response, un apposito Header contentdisposition e un ContentType application/xdownload. File SHFILEICON.VB Le icone delle cartelle e dei vari tipi di file, senza dover ricercare i file grafici corrispondenti, sono state ottenute predisponendo un file che utilizza l’API Win32 richiamando la funzione SHGetFileInfo di SHELL32.DLL del SO, questa consente di ricavare la bitmap con l’icona del file passandole un nome di file con la relativa estensione, il file non dev’essere fisicamente presente su disco, basta il nome; la stessa API consente anche di ottenere le bitmap delle icone utilizzate dal SO per le cartelle. File ICON.ASPX Recupera la bitmap dal nome di file passato nella Request e la salva nel flusso Response. Per ottenere la GIF (Graphics Interchange Format) corrispondente nella pagina è sufficiente utilizzare ICON.ASPX come src di un TAG HTML img. TPSIT 5 434 um 236 di <img src=”icon.aspx?n=file.txt”> TPSIT 5 434 um 237 di POP (POST OFFICE PROTOCOL) INTRODUZIONE È Il protocollo che consente di ricevere la posta, la gestione è implementata in Internet attraverso la cooperazione di due categorie di sottosistemi. 1. MUA (Mail User Agent) È l’applicazione di gestione della posta, per esempio Outlook e Eudora installata sul PC, deve possedere alcune caratteristiche fondamentali. Interfaccia utente per la gestione dei messaggi di posta elettronica, ossia deve consentire la composizione, la ricezione e la lettura dei messaggi. Dev’essere in grado di “colloquiare” con i mail server che provvedono all’invio dei propri messaggi all’MTA. Riconoscere la sintassi di composizione dei messaggi. 2. MTA (Mail Transport Agent) È il canale di comunicazione tra due MUA, il suo scopo principale è quello di consentire la ricezione di tutti i messaggi e il loro recapito, può essere. Un server SMTP che si occupa della spedizione e della ricezione dell’email da e verso altri server SMTP. Un server POP3 che si occupa di spedire i messaggi ad ogni client. Un server IMAP4 che consente la gestione dei messaggi direttamente sul server. Naturalmente, ognuno di questi servizi è attivo su una porta TCP/IP diversa, ognuno di essi potrebbe risiedere su di uno stesso server o su server diversi. Protocollo SMTP TPSIT 5 434 um 238 di È usato dai client per inviare la posta al loro server locale che poi decide se la casella destinataria è locale come il mittente o se la posta va inoltrata ad un altro server, sempre usando SMTP sulla Porta 25. Quando un server SMTP invia la posta, un altro server SMTP la riceve e la smista, dispatch, ad un server POP3 o IMAP. I messaggi di posta possono anche essere recapitati dopo essere stati incapsulati in altri protocolli applicativi, quali HTTP, come avviene nei servizi di web mail. L’utente destinatario si connette al server POP3 e scarica la posta tramite username e password. L’indirizzo preciso del server è restituito in un record MX attraverso una ricerca su DNS. Record MX È un tipo d’informazione contenuto nel sistema di nomi dei domini DNS che indica come dev’essere reindirizzata la posta Internet; quando un messaggio è inviato ad un server di posta, questo esegue una query DNS richiedendo il record MX del destinatario. I comandi principali di questo protocollo sono i seguenti. HELO Dà inizio al colloquio con il server SMTP e serve ad identificare il client, mediante il parametro Hostname. MAIL FROM Inizializza la vera e propria transazione, si deve indicare il mittente, reversepath, cui è riferita la creazione della mail, un generico formato del comando è MAIL. RCPT TO Serve a identificare i destinatari del messaggio. RSET Consente di “resettare” le impostazioni correnti dell’email che si sta configurando per l’inoltro, permettendo di ricominciare con un nuovo messaggio di posta elettronica. DATA Lanciato senza parametri, offre la possibilità d’inserire il testo dell’email che dev’essere necessariamente completato da questa sequenza: <CRLF><CRLF>, se tutto è andato a buon fine, il server risponde con un messaggio di esito positivo in cui s’indica che il messaggio è stato accettato per l’invio. VRFY Consente di verificare l’email di un qualunque destinatario di posta elettronica. HELP: Mostra l’elenco dei comandi accettati dal server. TPSIT 5 434 um 239 di QUIT Chiude il colloquio tra client e server SMTP. Elenco dei Reply Number ossia i messaggi possibili che il server SMTP restituisce al client quando quest’ultimo gli invia un qualunque comando. 211 System status, o system help reply. 214 Messaggio di Help. 220 <domain> Servizio pronto. 221 <domain> Servizio ha chiuso il canale. 250 Azione completata, OK. 251 Utente non locale; si spedirà a <forward-path>. 252 Non è possibile verificare la mail ma è comunque accettata. 354 Inizia l’input dei dati; termina con <CRLF>.<CRLF>. 421 Servizio non avviabile. 450 Richiesta di azione non avviabile. 451 Richiesta di azione abortita: errore locale durante il processo. 452 Richiesta di azione abortita: spazio di sistema insufficiente. 500 Errore di sintassi, comando non riconosciuto. 501 Errore di sintassi nei parametri o negli argomenti. 502 Comando non implementato. 503 Cattiva sequenza del comando. 504 Parametri del comando non implementati. 550 Richiesta di azione non presa. 551 Utente non locale; tentare con <forward-path>. 552 Richiesta di azione abortita: si eccede l’allocazione di spazio. 553 Richiesta di azione non presa: nome della mailbox non permesso. 554 Transazione fallita. Nella nuova versione di SMTP, ESMTP (Extended SMTP), il client che desidera farne uso invia, all’inizio della comunicazione con il server, la stringa EHLO anziché HELO. Se il server con cui si sta comunicando supporta ESMTP, la risposta inviata al client sarà un certo numero di righe con prefisso/codice 250. Questa risposta è di solito multilinea, poichè ognuna contiene una parola chiave e, opzionalmente, un argomento che specifica le estensioni SMTP supportate dal server. In caso contrario, ossia qualora il server non riconoscesse il comando EHLO, invierà al client un codice di errore. Esempio di sessione SMTP. biagio@venus> telnet localhost 25 Trying 127.0.0.1... Connected to localhost. Escape character is ‘^]’. 220 venus. Sendmail SMI-8.6/SMI-SVR4 ready at Mon, 21 Aug 2000 11:00:59 +0200 HELO 250 venus. Hello localhost [127.0.0.1], pleased to meet you mail from: [email protected] 250 [email protected]... Sender ok rcpt to: davide 250 davide... Recipient ok data TPSIT 5 434 um 240 di 354 Enter mail, end with "." on a line by itself Subject: In Microsoft we trust Why don’t you? 250 LAA00879 Message accepted for delivery quit 221 venus. closing connection Connection closed by foreign host. La mail ricevuta è la seguente: biagio@venus> Mail mailx version 5.0 Tue Jul 15 21:29:48 PDT 1997 Type ? for help. "/var/mail/biagio": 2 messages 2 new N 1 bill.gates@microso Mon Aug 21 11:09 15/403 In Microsoft we trust ?1 Message 1: From [email protected] Mon Aug 21 11:09:35 2000 Date: Mon, 21 Aug 2000 11:08:50 +0200 From: [email protected] Subject: In Microsoft we trust Why don’t you? ? Per testare l’invio di email usare il S/W fakeSMTP (https://nilhcem.github.io/FakeSMTP) che emula il funzionamento di un server SMTP, basta indicare la porta di ascolto e la cartella dove salvare l’email. Protocollo POP3 È usato dai client per scaricare la posta dal loro server locale, sulla porta 110; per default non conserva la posta anche sul server ma può farlo. In realtà esistono più versioni di questo protocollo, il numero 3 indica la terza versione. L’implementazione del protocollo POP3 è più complessa di quella impiegata per SMTP. Una generica conversazione in POP3 prevede essenzialmente tre fasi distinte. 1. Autenticazione: il client invia al server le proprie credenziali, username e password, per richiedere l’accesso alla propria casella postale, qualora la risposta del server sia positiva, quest’ultimo ne concede l’accesso, bloccandone l’utilizzo da parte di altre applicazioni. 2. Transizione: il client scarica effettivamente i messaggi dalla propria casella postale, l’utente termina questa conversazione con il comando QUIT. 3. Aggiornamento: il server procede ad effettuare un aggiornamento delle informazioni, eliminando gli eventuali messaggi contrassegnati per l’eliminazione nella Transaction Phase, sbloccando la casella postale e, infine, chiudendo la connessione definitivamente. Analogamente a quanto accadeva con SMTP, ogni utente che desidera scaricare i propri messaggi di posta elettronica, deve stabilire una connessione TCP sulla porta 110 del server POP3. Subito dopo aver stabilito la connessione, il server si “presenta” ed è pronto ad accettare i comandi ad esso inviati. Il protocollo POP3 prevede diversi modi con cui il client può identificarsi ma quello più utilizzato consiste nell’utilizzo dei comandi USER e PASS. Attraverso il primo, il client invia al server il nome utente registrato corrispondente ad una casella postale ben definita. Se il server accetta il nome allora è possibile spedire anche la password mediante il comando PASS, seguito dall’opportuna parola d’ordine associata al nome in questione. TPSIT 5 434 um 241 di A questo punto, avvenuta l’autenticazione, il client ha accesso alla propria casella postale e può, quindi, effettuare operazioni su di essa. Ha inizio la seconda fase del collegamento, Transaction Phase, durante lo scambio delle informazioni tra client e server, è possibile osservare due soli possibili indicatori di stato che, posti all’inizio di ogni riga, identificano immediatamente la risposta del server, oltre ad un’azione richiesta dal client: +OK e –ERR. Una volta entrati nella fase di transazione, il server assegna a ogni messaggio un identificativo numerico partendo dal numero uno, mentre il client può iniziare, a sua volta, la spedizione delle proprie richieste utilizzando una serie di comandi a sua disposizione. I comandi principali di questo protocollo sono i seguenti. RETR n Questo comando, seguito dal numero di messaggio, consente di scaricare una generica email e leggerla. STAT Restituisce il numero di messaggi presenti nella propria casella postale e la dimensione totale della stessa. LIST Restituisce l’elenco dell’email contenute all’interno della casella postale, numerate in ordine progressivo e la dimensione di ciascuna. NOOP Restituisce semplicemente +OK, controlla lo “stato” del server per assicurare che esso sia ancora raggiungibile e in ascolto. DELE n Contrassegna per la cancellazione un determinato messaggio. RSET Reimposta il contrassegno che identifica, per ogni email, la richiesta di eliminazione dal server. TOP n Intestazione di un determinato messaggio. PAS password Invio della password. USER username Invio del nome utente. Protocollo IMAP È tipo POP3 ma mantiene la posta scaricata anche sul server, risponde alle richieste indirizzate dai client alla porta 143. Supporre che due colleghi di lavoro devono accedere alla stessa casella di posta, ad esempio quella relativa all’assistenza clienti. Se il primo collega scaricasse la posta in locale il secondo collega non troverebbe alcun messaggio sul server. Se entrambi decidono di lasciare la posta sul server sarebbe impossibile sapere chi dei due ha risposto ad un messaggio, a meno che non lo si comunichi verbalmente l’uno TPSIT 5 434 um 242 di all’altro. Allo stesso modo non rimarrebbe traccia della risposta, quindi nessuno dei due potrebbe sostituire l’altro in caso di emergenza. IMAP4 è un protocollo che nelle intenzioni doveva sostituire POP3 e fornire delle soluzioni anche per questo tipo di problema, prevede che la posta rimanga centralizzata sul server e prevede che i client che vi accedano possano condividere le informazioni relative alla posta elettronica. Se uno dei due marca un messaggio come “risposto” l’altro ne avrà visione. Si tratta di un protocollo più lento, complesso e macchinoso di POP3. Si tratta di un metodo utile da utilizzare in reti aziendali che svolgano tramite posta elettronica anche funzioni di gestione della produzione. Formato MIME Inizialmente il protocollo per la rappresentazione dei documenti di posta elettronica specificava il formato per i messaggi di posta, limitandosi a messaggi esclusivamente di tipo testo ASCII, senza alcun riferimento ad altri formati. Una delle principali limitazioni del protocollo risiede nel fatto che il contenuto dei messaggi è limitato ai soli caratteri codificati in sette bit. Quest’imposizione obbliga, prima dell’invio, a convertire ogni messaggio che non contenga esclusivamente semplice testo ASCII. Per ovviare a questo inconveniente fu introdotto lo standard MIME. Definizione del formato di messaggi testuali ASCII e non. Definizione del formato di messaggi multimediali contenenti video, suono, immagini. Il MIME e, nello specifico, S/MIME, è stato decisivo per l’implementazione dei servizi di sicurezza che offrono all’utente la possibilità d’inviare messaggi corredati di firma digitale, di criptografia o di autenticazione. Il documento è costituito “semplicemente” da una stringa di testo formata da due parti distinte, separate da una linea vuota. 1. Header Racchiude in sé le informazioni per il trasporto della mail, può essere così schematizzato. TO: indica la lista dei destinatari. FROM: mittente della mail. CC: lista dei destinatari per conoscenza. BCC: lista nascosta di destinatari per conoscenza. DATE: data dei spedizione della mail. REPLY-TO: indirizzo alternativo del mittente cui inviare le risposte. SUBJECT: oggetto del messaggio. 2. Body Con lo standard MIME è possibile inserire, in una qualsiasi email, oltre al testo, anche file contenenti immagini, segnali audio e video. In particolare, è bene ricordare che il S/W che dovrà gestire la posta non deve preoccuparsi del contenuto del messaggio, poichè è l’utilizzatore finale a dover applicare l’opportuna decodifica in base alle “specifiche di tipo“ inserite nel messaggio stesso. TPSIT 5 434 um 243 di Un documento MIME contiene una testata in cui si trovano i seguenti campi. MIME version Identifica la versione dello standard MIME usato nell’email. Content-Transfer-Encoding Specifica un modo di codifica dei dati accessorio a quello principale, i possibili valori che può assumere questo attributo sono 7 bit, 8 bit, binary, quoted-printable e base64. Content-Type Consente di specificare il tipo e il sottotipo di dati contenuti all’interno del messaggio, MIME type, attraverso esso, il client che riceve l’email è in grado di rilevare in che maniera sono stati codificati i dati ricevuti, questo attributo, insieme a quello precedente, identificano la parte fondamentale di questo standard e, nel contempo, l’aspetto più complesso da gestire. Content-ID Identifica il messaggio in modo univoco, questo attributo è opzionale. Content-Description Rappresenta una descrizione testuale del contenuto del messaggio, questo attributo è opzionale. Codifica UUENCODE Un mail client qualunque consente di aggiungere degli allegati al messaggio, permettendo anche di salvarli una volta ricevuta la mail. Ciò, ovviamente, significa anche che non è possibile inserirlo “immediatamente” all’interno del messaggio poiché una generica email è costituita “necessariamente” da testo puro. Un modo per risolvere il problema è quello di applicare al file un algoritmo tale da convertirlo in una forma che possa essere inclusa all’interno della struttura dell’email ma che non “interferisca” con il testo stesso. L’algoritmo che consente di ottenere tutto questo esiste ed è definito con il nome UUENCODE e presuppone che il file d’input sia di tipo binario. Stabilito questo, estrae dal file binario 3 byte per volta e li converte in 4 caratteri ASCII. Algoritmo. Considerare i 3 byte come un’unica parola da 24 bit. Prendere 6 bit alla volta creando 4 nuovi byte. Aggiungere 32 ad ogni nuovo byte per ottenere un carattere di testo in formato ASCII. Il metodo è semplice e sicuramente efficiente poiché consente di ottenere, al termine di questa elaborazione, un file contenente soltanto caratteri di testo, anche se un pò più grande di quello originario. Esempio, un messaggio decodificato secondo questa tecnica. begin 664 Allegato.txt %3TLN+BX@ ‘ end Il valore 664 rappresenta un codice che consente d’indicare espressamente l’utilizzo della codifica Base64. TPSIT 5 434 um 244 di DIVERSI TIPI DI APPLICAZIONI Applicazioni abilitate alla posta La funzione principale non è specificatamente relativa all’email ma contiene alcuni servizi di messaggistica di posta elettronica, spesso in maniera trasparente per l’utente. Per esempio, un’applicazione che controlla macchine utensili e invia per email le informazioni che si riferiscono alla loro efficienza. Applicazioni di posta La funzione principale è l’email. Applicazioni sensibili alla posta La funzione principale non riguarda l’email, per esempio un elaboratore di testi permette di comporre un documento e d’inviarlo per posta ad altre persone. Microsoft Visual C# MailReader È un protocollo che garantisce l’accesso ad account di posta elettronica tramite user e password. Il server funziona implementando un Listener TCP che resta, in genere, in attesa sulla porta 110. I client comunicano con il server inviandogli dei comandi. USER Nuome_Utente: invia la username per la connessione. PASS Password_Utente: invia la password. LIST: legge la lista dei messaggi su server e restituisce ID e dimensione. RETR Id_Messaggio: restituisce il contenuto del messaggio. DELE Id_Messaggio: cancella un messaggio. QUIT: esce. Implementare un client di posta elettronica, dev’essere capace d’instaurare una connessione con un server POP, inviargli dei comandi e leggere le risposte. Fare clic su File/Nuovo/Progetto… (CTRL+N), nella finestra Nuovo progetto. File POP3CLIENT.CS È la classe che si occupa della comunicazione con il server di posta Pop3Client, eredita da TcpClient. Definisce le proprietà per gestire la connessione, inviare messaggi al server e leggerne le TPSIT 5 434 um 245 di risposte. La classe TcpClient fornisce dei metodi per creare un client TCP. Per funzionare ha bisogno di avere un server TCP in ascolto per collegarsi al quale può utilizzare il metodo Connect. Fornisce, inoltre, il metodo GetStream per ottenere un NetworkStream da cui poter leggere i dati ricevuti dal server. Metodi. Connect: effettua la connessione. Disconnect: effettua la disconnessione. ListMessages: restituisce la lista messaggi sul server. ReadMessage: legge un singolo messaggio. DeleteMessage: cancella un messaggio. Tutti i metodi della classe utilizzano le seguenti funzioni per comunicare con il server di posta. SendServerMessage: invia un messaggio al server. ReadServerResponse: riceve la risposta. La funzione ListMessages recupera la lista dei messaggi presenti in una casella di posta. Il metodo SendServerMessage invia dei messaggi contenenti dei comandi al server. Riceve in ingresso un messaggio in forma di stringa, lo codifica in un array di byte con il metodo GetBytes e lo invia tramite un NetworkStream al server. Lo stream è ottenuto con il metodo GetStream della classe System.Net.Sockets.TcpClient da cui la classe eredita. Il metodo ReadServerResponse legge i messaggi inviati dal server in risposta alle richieste. Ottiene dal server uno stream tramite il metodo GetStream, utilizza, quindi, un ciclo While per leggere losStream con il metodo Read della classe Stream e restituire una stringa contenente la risposta. File POP3MESSAGE.CS Si definisce la struttura di un messaggio POP3 tramite la classe Pop3Message, questa ha delle proprietà che definiscono il numero del messaggio, il mittente, la data di ricezione. Il costruttore non fa altro che valorizzare le proprietà del messaggio. Metodi. GetFrom: restituisce il mittente. GetFromName: restituisce il nome del mittente. GetDate: restituisce la data. GetSubject: restituisce l’oggetto. GetBody: restituisce il corpo. File MAINFORM.CS using System; using System.Collections.Generic; using System.Windows.Forms; namespace MailReader { public partial class MainForm : Form { private List<Pop3Message> MessageList; public MainForm() { InitializeComponent(); } private void btn_test_Click(object sender, EventArgs e) { try { Pop3Client pop = new Pop3Client(txt_popServer.Text, txt_userName.Text, txt_password.Text, Convert.ToInt32(txt_port.Text)); TPSIT 5 434 um 246 di try { pop.Connect(); if (pop.Connected) MessageBox.Show("Connesso."); else MessageBox.Show("Non connesso!"); } finally { if (pop.Connected) pop.Disconnect(); } } catch (Exception ex) { MessageBox.Show("Errore: " + ex.Message); } } private void btn_readMessages_Click(object sender, EventArgs e) { try { dgv_messages.DataSource = null; dgv_messages.AutoGenerateColumns = false; Pop3Client pop = new Pop3Client(txt_popServer.Text, txt_userName.Text, txt_password.Text, Convert.ToInt32(txt_port.Text)); try { pop.Connect(); if (pop.Connected) { MessageList = pop.ListMessages(); dgv_messages.DataSource = MessageList; } } finally { if (pop.Connected) pop.Disconnect(); } } catch (Exception ex) { MessageBox.Show("Errore" + ex.Message); } } private void dgv_messages_CellClick(object sender, DataGridViewCellEventArgs e) { if (e.RowIndex > -1) { long id = Convert.ToInt64(dgv_messages.Rows[e.RowIndex].Cells[2].Value); if (e.ColumnIndex == 0) { foreach (Pop3Message msg in MessageList) if (msg.MessageNumber == id) rtb_message.Text = msg.Message; } else if (e.ColumnIndex == 1) { Pop3Client pop = new Pop3Client(txt_popServer.Text, txt_userName.Text, txt_password.Text, Convert.ToInt32(txt_port.Text)); try { pop.Connect(); if (pop.Connected) { pop.DeleteMessage(id); dgv_messages.DataSource = null; TPSIT 5 434 um 247 di List<Pop3Message> tmp = new List<Pop3Message>(); tmp.AddRange(MessageList); foreach (Pop3Message msg in MessageList) if (msg.MessageNumber == id) tmp.Remove(msg); MessageList = tmp; dgv_messages.DataSource = MessageList; } } finally { if (pop.Connected) pop.Disconnect(); } } }} } } Mail Sender Il protocollo SMTP consente d’inviare dei messaggi ad un host, utilizza la porta 25. Il .NET Framework fornisce il namespace System.Net.Mail che contiene già tutte le classi e i metodi che servono. File MAILSENDER.CS Il metodo SendMessage crea un oggetto di tipo MailMessage, ne imposta le proprietà e poi lo invia. Proprietà del messaggio. Priority: priorità del messaggio. From: mittente di tipo MailAddress. ReplyTo: indirizzo di risposta di tipo MailAddress. To: lista destinatari di tipo MailAddress. TPSIT 5 434 um 248 di Bcc: lista destinatari in copia nascosta di tipo MailAddress. Subject: oggetto. Body: corpo. Attachments: la lista di allegati di tipo Attachment. Headers: una lista di headers inviati con il messaggio coppie chiave-valore. IsBodyHtml: se la mail è in formato HTML. L’invio è eseguito istanziando un oggetto di tipo SmtpClient e invocandone il metodo Send. TPSIT 5 434 um 249 di Progettare un client SMTP che invia e riceve email. Selezione di account multipli. Invio e ricezione allegati. Salvare i dati nel formato XML. Il progetto usa la libreria CODERDLL.DLL scritta in C++ Unmanaged Code. File ACCOUNTXML_DATA.CS Dati di account. File ATTACHMENTS.CS Gestione degli allegati. File BASECODER.CS Classe per la gestione della decodifica degli allegati di posta. File EMAILDATA.CS Struttura dati di un’email. File FORM2.CS Finestra per la composizione della posta. File LOADMAIL_XMLFILE.CS Legge e salva i dati di un’email dal file XML. File POP.CS TPSIT 5 434 um 250 di Legge l’email dal server POP3. File CODERDLL.CPP extern "C" void _declspec(dllexport) DECODE_BUFFER_TO_FILE(LPCTSTR mBuffer , LPCTSTR mFileName ) extern "C" LPTSTR _declspec(dllexport) _cdecl BrowseForFolder() TPSIT 5 434 um 251 di TPSIT 5 434 um 252 di Microsoft Visual Basic Inviare un messaggio di posta elettronica. Fare doppio clic sul pulsante Invia, si apre la finestra del codice con la struttura della routine di gestione dell’evento btnInvia_Click, al suo interno fare clic con il pulsante destro e selezionare Inserisci frammento di codice… (CTRL+K, CTRL+X). È inserito il seguente codice. Imports System.Net.Mail Dim message As New MailMessage("sender@address", "from@address", "Subject", "Message Text") Dim emailClient As New SmtpClient("Email Server Name") emailClient.Send(message) Progettare un’applicazione che include un solo form con controlli analoghi a quelli del client di posta elettronica Microsoft Outlook, quando il form è caricato, è verificato che nel PC dell’utente il servizio SMTP sia installato e in esecuzione. La proprietà SmtpServer dev’essere impostata sull’indirizzo IP o sul nome del server in cui TPSIT 5 434 um 253 di è in esecuzione il server SMTP, generalmente è il PC localhost 127.0.0.1. Il Server virtuale SMTP predefinito dev’essere in esecuzione, il servizio può essere in esecuzione ma il Server virtuale SMTP predefinito in IIS può essere interrotto. Vi è una differenza tra il servizio stesso e il server effettivo eseguito dal servizio. Se il Server virtuale SMTP predefinito non è in esecuzione, sul relativo nodo sarà visualizzata un’icona a forma di X rossa. TPSIT 5 434 um 254 di Impostare le limitazioni per l’inoltro, nella finestra fare clic con il pulsante destro del mouse su Server virtuale SMTP predefinito e scegliere Proprietà. Nella finestra di dialogo fare clic sulla scheda Accesso, quindi su Inoltro…. Nella finestra di dialogo selezionare la casella Tutti tranne quelli indicati nell’elenco. È indifferente che la casella di controllo in basso sia selezionata o deselezionata. Impostare il nome di SmtpServer che può essere uno degli elementi seguenti, secondo le impostazioni di sicurezza locale. Indirizzo IP locale presumendo che il server SMTP del PC locale abbia il diritto d’inviare messaggi tramite un firewall locale, se presente. 127.0.0.1, loopback del PC locale. Smarthost, il nome o l’indirizzo IP del server di Exchange utilizzato per l’invio di messaggi. Utilizzare la gestione strutturata degli errori per tentare d’inviare il messaggio di email e fornire una notifica all’utente sull’esito positivo o negativo del tentativo. TPSIT 5 434 um 255 di TPSIT 5 434 um 256 di Progettare un plugin per Outlook. Visual Studio mette a disposizione una serie di strumenti per creare plugin per la suite di Office, sono i VSTO (Visual Studio Tools for Office). Outlook conserva i messaggi di posta elettronica in file chiamati file delle cartelle personali con estensione PST; utilizzando intensivamente la posta elettronica, dopo qualche tempo, questi file crescono a dismisura, ciò è dovuto ai vari file allegati ai messaggi che sono salvati anch’essi nel file PST. Il plugin dovrà dotare Outlook di funzionalità aggiuntive. 1. Estrarre i file allegati dai messaggi e salvarli in una cartella su disco locale o in rete. 2. Cancellare i file fisici allegati al messaggio. 3. Sostituirli, nel messaggio, con dei collegamenti al file precedentemente salvato. Fare clic su File/Nuovo/Progetto… (CTRL+N), nella finestra Nuovo progetto selezionare Visual Basic/Office/2007/Componente aggiuntivo per Outlook 2007 È stato creato automaticamente il file THISADDIN.VB che rappresenta il plugin. TPSIT 5 434 um 257 di In pratica, si tratta dello scheletro vuoto che gestisce le due fasi principali della vita del plugin: l’avvio, Startup e la chiusura, Shutdown. In un plugin queste due fasi servono a integrare la GUI dell’applicazione host, nel progetto Outlook con gli elementi menu, pulsanti che consentono di richiamare le funzionalità dell’applicazione. Inserire due nuove voci al menu strumenti di Outlook. 1. La voce Salva allegati che serve a richiamare la funzionalità principale. 2. La voce Opzioni salvataggio allegati che farà aprire una finestra di dialogo per consentire l’impostazione della cartella che conterrà gli allegati salvati. Le voci dei menu File/Modifica sono oggetti di tipo CommandBarPopup contenuti nella barra del menu che è un oggetto di tipo CommandBar. Le voci contenute sotto quelle principali sono oggetti di tipo CommandBarButton. Ogni oggetto CommandBarPopup, CommandBarButton ha un ID identificativo univoco. Esempio. 30002 - File 30003 - Modifica 30004 - Visualizza 30328 - Vai 30007 - Strumenti 30131 - Azioni 30010 - ? Occorrerà trovare il riferimento al menu Strumenti, codice 30007, crearvi le due voci di tipo CommandBarButton e riunire tutto nel metodo AddMenuItems. File THISADDIN.VB Richiama AddMenuItems e RemoveMenuItems, all’interno del gestore di avvio. Così facendo, all’avvio, saranno eliminate e ricreate le voci di menu. La logica vorrebbe che AddMenuItems fosse contenuto nel gestore dell’evento Startup mentre RemoveMenuItems in quello dell’evento Shutdown, tuttavia a volte capita che Shutdown non sia eseguito, rischiando così di trovarsi al successivo riavvio con le voci di menu duplicate, per cui è consigliabile ricrearle a ogni avvio. Me.Application rappresenta l’applicazione Office in esecuzione. La gestione dell’evento clic sulla voce di menu è realizzata impostando un handler che punta a metodi ButtonMain_Click e ButtonOpt_Click che andranno impostati all’interno della classe. Assegnare alle voci di menu dei TAG univoci, menuTag e menuTag2, per poterle poi ritrovare con il metodo FindControl. AddMenuItems è il metodo che crea i pulsanti. RemoveMenuItems è il metodo che rimuove i pulsanti, cerca le due voci, identificate per mezzo dei TAG e, se le trova, le elimina. Salvare gli allegati, tutto il processo parte da una lista di messaggi selezionati dall’utente nella finestra di Outlook. Tali messaggi saranno esaminati sostituendo gli allegati; in primis, quindi, serve una funzione che restituisca la lista di messaggi selezionati dall’utente: GetSelectedItems restituisce un insieme di MailItem corrispondente ai messaggi selezionati. SaveAttach è il metodo più importante del plugin. Se il messaggio non ha allegati il metodo s’interrompe. Nella cartella dove andranno salvati gli allegati crea, se non esiste, la cartella relativa a quel mittente, ricavando il nome dal mittente del messaggio. Scorre l’insieme degli allegati, se un allegato non è un collegamento quindi è un file TPSIT 5 434 um 258 di incluso nel file di dati di Outlook, lo salva nella path di destinazione. Se il salvataggio è andato a buon fine conserva le informazioni del nome da visualizzare, DisplayName e della path di destinazione in un Dictionary e cancella l’allegato. Scorre il Dictionary reinserendo gli allegati ma questa volta come collegamenti OlAttachmentType.olByReference al file salvato. Il metodo SaveAttach Il metodo SaveAttach va richiamato per ogni messaggio della lista che si è recuperato, questo compito è assegnato a SaveAttachs. ButtonMain_Click è attivato al clic sulla voce di menu Salva allegati che è inserita automaticamente dal plugin sotto il menu Strumenti di Microsoft Outlook 2007. Gestione dell’apertura della finestra di dialogo delle impostazioni, al clic sulla voce Opzioni salvataggio allegati, il plugin apre la Windows Form che consente all’utente di specificare il percorso dove salvare gli allegati. Fare clic su Progetto/Proprietà di OutlookAddIn…. Selezionare la scheda Impostazioni, inserire il valore SavePath, impostando l’ambito come Utente in questo modo il valore sarà modificabile a run-time. In questo modo,Visual Studio creerà automaticamente tutto il codice necessario per gestire le impostazioni con il namespace My. È necessario provvedere a creare un’UI per cambiare le impostazioni. Creare quindi nel progetto una nuova Window Form scegliendo come modello quello delle finestre di dialogo. File FORMOPTIONS.VB Usando questo plugin si può notare che, sebbene i file allegati ai messaggi siano estratti e salvati in altra posizione, il file delle cartelle di Outlook resta sempre della stessa dimensione; per ridurre effettivamente le dimensioni di tale file occorre compattarlo con l’opzione dell’applicazione. TPSIT 5 434 um 259 di Per distribuire il plugin, si usa ClickOnce, fare clci su Compila/Pubblica OutlookAddIn. TPSIT 5 434 um 260 di JAVA Progettare un client CLI che invia email. File INVIO.JAVA package posta; import java.util.Properties; import java.util.Date; import javax.mail.*; import javax.mail.internet.*; public class Invio { public static void main(String[] args) throws Exception { // il server SMTP da impiegare per l’invio String smtp = "fauser.edu"; // il mittente della mail String mittente = "\"Massimo Ubertini\" <[email protected]>"; // il destinatario della mail String destinatario = "\"Massimo Ubertini\" <[email protected]>"; // l’oggetto della mail String oggetto = "Prova JavaMail"; // il corpo della mail String corpo = "Ciao!\nQuesta è una prova..."; // acquisisco le proprietà del sistema Properties props = System.getProperties(); // aggiungo alle proprietà un record per l’host smtp da impiegare props.put("mail.fauser.edu", smtp); // creo un oggetto java.mail.Session con le proprietà elaborate Session session = Session.getDefaultInstance(props, null); // preparo il messaggio da inviare (javax.mail.internet.MimeMessage) MimeMessage message = new MimeMessage(session); // imposto il mittente message.setFrom(new InternetAddress(mittente)); // imposto il destinatario message.addRecipient(Message.RecipientType.TO, new InternetAddress(destinatario)); // imposto l’oggetto message.setSubject(oggetto); // imposto la data di invio message.setSentDate(new Date()); // imposto il corpo della mail message.setText(corpo); // invio il messaggio TPSIT 5 434 um 261 di Transport.send(message); // avviso l’utente System.out.println("Mail inviata!"); } } Progettare un client GUI che invia email. File INVIO.JAVA package posta; import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.mail.*; import javax.mail.internet.*; public class Invio extends JFrame { // componenti della GUI JLabel label1 = new JLabel("Host SMTP:"); JLabel label2 = new JLabel("Mittente:"); JLabel label3 = new JLabel("Destinatario:"); JLabel label4 = new JLabel("Oggetto:"); JTextField text1 = new JTextField(""); JTextField text2 = new JTextField(""); JTextField text3 = new JTextField(""); JTextField text4 = new JTextField(""); JTextArea text5 = new JTextArea(""); JButton button1 = new JButton("Invia"); // un sinonimo di this, per convenienza Invio myself = this; // la finestra per far attendere l’utente durante l’invio SendDialog sendDialog; // costruttore della classe public Invio() { super("Invio"); // imposto le preferenze dei componenti text1.setPreferredSize(new Dimension(300, text1.getPreferredSize().height)); text2.setPreferredSize(new Dimension(300, text2.getPreferredSize().height)); text3.setPreferredSize(new Dimension(300, text3.getPreferredSize().height)); text4.setPreferredSize(new Dimension(300, text4.getPreferredSize().height)); text5.setLineWrap(true); text5.setWrapStyleWord(true); JScrollPane scrollPane = new JScrollPane(text5); scrollPane.setPreferredSize(new Dimension(400, 300)); // assemblo la GUI JPanel row1 = new JPanel(new FlowLayout(FlowLayout.RIGHT)); row1.add(label1); row1.add(text1); JPanel row2 = new JPanel(new FlowLayout(FlowLayout.RIGHT)); row2.add(label2); row2.add(text2); JPanel row3 = new JPanel(new FlowLayout(FlowLayout.RIGHT)); TPSIT 5 434 um 262 di row3.add(label3); row3.add(text3); JPanel row4 = new JPanel(new FlowLayout(FlowLayout.RIGHT)); row4.add(label4); row4.add(text4); JPanel north = new JPanel(new GridLayout(4, 1)); north.add(row1); north.add(row2); north.add(row3); north.add(row4); JPanel south = new JPanel(new FlowLayout(FlowLayout.RIGHT)); south.add(button1); JPanel all = new JPanel(new BorderLayout()); all.setBorder(new javax.swing.border.EmptyBorder(3, 3, 3, 3)); all.add(north, BorderLayout.NORTH); all.add(scrollPane, BorderLayout.CENTER); all.add(south, BorderLayout.SOUTH); getContentPane().add(all); // compatto la finestra pack(); // posiziono al centro dello schermo Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frame = getSize(); setLocation((screen.width - frame.width) / 2,(screen.height - frame.height)/2); // aggiungo il listener al pulsante di invio button1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { sendDialog = new SendDialog(myself); sendDialog.sendMessage(); } }); // alla chiusura della finestra termina il programma setDefaultCloseOperation(EXIT_ON_CLOSE); } // la finestra che fa attendere l’utente durante l’invio dalla posta private class SendDialog extends JDialog implements Runnable { // il thread per l’invio della mail Thread thread; // costruttore public SendDialog(JFrame owner) { super(owner, "Invio in corso...", true); // assemblo la GUI JPanel all = new JPanel(new GridLayout(5, 1)); all.setBorder(new javax.swing.border.EmptyBorder(3, 3, 3, 3)); all.add(new JPanel()); all.add(new JLabel("Invio in corso", JLabel.CENTER)); all.add(new JPanel()); all.add(new JLabel("Per favore attendi", JLabel.CENTER)); all.add(new JPanel()); getContentPane().add(all); // compatto la finestra pack(); TPSIT 5 434 um 263 di // posiziono il componente setLocationRelativeTo(owner); // non permetto il ridimensionamento della finestra setResizable(false); // non chiudere questa finestra su richiesta dell’utente setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); } // il metodo richiamato per invocare l’invio della mail @SuppressWarnings("deprecation") public void sendMessage() { // avvio il thread che cura l’invio della mail thread = new Thread(this); thread.start(); // mostro la finestra di attesa show(); } // l’effettivo invio della mail public void run() { // acquisisco le proprietà del sistema Properties props = System.getProperties(); // aggiungo alle proprietà un record per l’host smtp da impiegare props.put("mail.smtp.host", text1.getText()); // creo un oggetto java.mail.Session con le proprietà elaborate Session session = Session.getDefaultInstance(props, null); // preparo il messaggio da inviare (javax.mail.internet.MimeMessage) MimeMessage message = new MimeMessage(session); try { // imposto il mittente message.setFrom(new InternetAddress(text2.getText())); // imposto il destinatario message.addRecipient(Message.RecipientType.TO, new InternetAddress(text3.getText()) ); // imposto l’oggetto message.setSubject(text4.getText()); // imposto la data di invio message.setSentDate(new Date()); // imposto il corpo della mail message.setText(text5.getText()); // invio l’e-mail Transport.send(message); // chiudo la finestra d’attesa dispose(); // avviso l’utente del successo dell’operazione JOptionPane.showMessageDialog( myself,"La lettera è stata spedita","Invio eseguito!", JOptionPane.INFORMATION_MESSAGE); } catch (Exception e) { // chiudo la finestra d’attesa dispose(); // avviso l’utente in caso di errore JOptionPane.showMessageDialog( TPSIT 5 434 um 264 di myself,"Impossibile inviare la lettera.\n" + "Controllare i campi e ritentare.","Errore!",JOptionPane.ERROR_MESSAGE); } } } // punto d’ingresso dell’applicazione public static void main(String[] args) { Invio mailSender = new Invio(); mailSender.show(); } } Progettare un client SMTP che invia e riceve email, inclusi file allegati. TPSIT 5 434 um 265 di TPSIT 5 434 um 266 di SPAM (SPICY PORK AND HAM) INTRODUZIONE Sono messaggi pubblicitari, chiamati anche spamming che non sono stati richiesti. Letteralmente è definito “spalmare” ma l’origine del termine è americano, infatti esiste un prodotto spalmabile chiamato SPAM, è un impasto di carne di maiale in scatola prodotto dalla Hormel Foods Corporation, nato nel 1937. Il termine deriva da una scena dello spettacolo televisivo “Monty Python’s Flying Circus”, dove una coppia tentava inutilmente di ordinare al ristorante un piatto senza SPAM che invece la cameriera proponeva sempre. Per proteggersi da questi messaggi indesiderati sono nati diversi strumenti S/W. Le tecniche utilizzate per implementare dei sistemi anti SPAM possono riassumersi nelle tre seguenti categorie. 1. Ricerca di elementi chiave Il testo del messaggio è analizzato e per ogni caratteristica sospetta è assegnato un punteggio. 2. White/black list L’utente costruisce, in modi più o meno automatici, una lista di mittenti conosciuti e accreditati e/o una lista di mittenti non accreditati. Il filtro di posta sui messaggi in arrivo farà passare solo i messaggi i cui mittenti sono in white list, mentre cancellerà quelli in black list. Restringendo i parametri di filtro si rischia di ottenere falsi positivi, ovvero messaggi erroneamente indicati come posta spazzatura. Lato client Esempio, in Outlook si definiscono delle regole per l’elaborazione automatica dei messaggi. Prima si verificano le condizioni, poi le scelte relative alle azioni; quindi, prima di memorizzare la regola conviene assegnarle un nome. È possibile impostare più regole. Alcune condizioni non hanno bisogno d’informazioni aggiuntive, per esempio Il messaggio contiene allegati, mentre altre richiedono l’impostazione di qualche parametro. Per creare una regola, fare clic sul pulsante Regole del gruppo Sposta della scheda HOME. Nella finestra che si apre, selezionare la scheda Regole posta elettronica e fare clic su Nuova regola…, inizia la creazione guidata. TPSIT 5 434 um 267 di Lato server È invisibile all’utente finale, la cui conoscenza è però importantissima per chi gestisce un mail server o per chi ricevendo molta posta spazzatura desidera fare in modo che allo spammer sia negato l’utilizzo della rete per fini non legittimi. L’idea è molto semplice, alcuni siti web raccolgono le segnalazioni degli utenti in relazione a “denunce” di SPAM. I server SMTP degli ISP (Internet Service Provider) quando ricevono un’email interrogano questi siti web e, se il mittente del messaggio non è elencato nelle liste degli spammer, allora è regolarmente inviato all’utente, viceversa è bloccato. In sostanza, un utente non riceverà mai i messaggi di SPAM perché questi messaggi saranno filtrati a livello di server e non a livello utente. Perché una black list abbia effetto, è necessario che l’elenco dei mittenti sia il più possibile completo. Nella maggioranza di questi non è memorizzato il mittente ma l’indirizzo IP del server che invia il messaggio. Se il server che invia è inserito in black list e il server di posta che riceve supporta la gestione della black list, il messaggio è rifiutato. Le black list non possono essere scaricate integralmente, perché si compongono di megabyte d’informazioni, l’accesso avviene per interrogazione. Open Relay È una funzione dei server SMTP che consente a utenti anonimi d’inviare la posta, una TPSIT 5 434 um 268 di protezione è quella di limitare l’invio della posta ai soli utenti che utilizzano un indirizzo IP sulla stessa rete su cui è posizionato il server SMTP. L’autenticazione al server SMTP avviene quando l’utente fornisce uno username e una password per inviare la posta. Non confondere password e utente per l’invio con quello per la ricezione. Relay multi-hop Se gli ISP usano più server SMTP, alcuni per l’invio di posta tra utenti dello stesso dominio e altri MTA “di confine” usati per inoltrare la posta verso l’esterno, ovviamente, gli MTA di confine accettano il relay da parte dei server interni. Se lo spammer ha accesso ad uno dei server interni o se quest’ultimo non è ben configurato, può inviare messaggi di SPAM tramite il MTA di confine che, pur non facendo Open Relay, accetta di rispedirli verso l’esterno perché gli sembra che provengano da un mittente autorizzato. Dynamic addressing e recapito No Relay Gli ISP assegnano ai loro clienti indirizzi IP dinamici, questa prassi ha dato agli spammer un altro modo di aggirare il blocco dell’Open Relay: lo spammer recapita i messaggi di SPAM direttamente ai server SMTP dei destinatari, usando il suo indirizzo IP dinamico. Ogni volta che l’indirizzo IP dinamico dello spammer è notato e elencato su una black list, lo spammer può scollegarsi da Internet, riconnettersi e ricevere un nuovo indirizzo IP dinamico, il costo di eseguire uno spamming di questo tipo è alto anche per lo spammer, soprattutto in termini di tempo ma l’inoltro di SPAM con questa tecnica, detta No Relay, è molto efficace e combatterlo è estremamente difficile. Connection Sharing e recapito Open Proxy Molte organizzazioni usano proxy sui loro server connessi a Internet per consentire a altri PC della loro rete locale di condividere la connessione, se sono mal configurati permettono a host “parassiti” di attivare connessioni proxy di tipo Open Proxy. 3. Filtri statistici o bayesiani L’idea iniziale del filtraggio orientato al contenuto fu sfruttare il fatto che la maggior parte degli spammer inviava a tutti i destinatari una copia dello stesso messaggio. Anche se in un primo tempo il filtraggio collaborativo fu efficace, ci si accorse subito che gli spammer potevano aggirarlo usando tecniche di partizionamento e di personalizzazione delle liste dei destinatari, il List Splitting, in modo da aggiungere ai messaggi di SPAM delle porzioni variabili dipendenti dal destinatario. La soluzione bayesiana è basata sullo studio statistico del contenuto dei messaggi. Un filtro bayesiano decide se un messaggio è SPAM o non in base alle parole contenute nei messaggi ricevuti da uno specifico utente. Proposto per la prima volta nel 1998 nel AAAI-98 Workshop on Learning for Text Categorization e reso popolare da un articolo di Paul Graham nel 2002 “A plan for SPAM”. Thomas Bayes (Londra, 1702 – Tunbridge Wells, 17 aprile 1761) è stato un matematico e ministro presbiteriano britannico, deve la sua fama ai suoi studi nel campo della matematica e della filosofia; è noto soprattutto nella statistica per il suo teorema sulla probabilità condizionata, pubblicato postumo nel 1763. Teorema di Bayes Sia data un’osservazione O, un messaggio contiene la parola “sesso” e un’ipotesi H, un messaggio è SPAM. P(O|H), in pratica la probabilità che O accada dato H, ovvero la probabilità che un TPSIT 5 434 um 269 di messaggio di SPAM contenga la parola “sesso” è facile da stimare, ad esempio esaminando la cartella in cui l’utente destinatario mette lo SPAM e contando quanti dei messaggi che vi si trovano già contengono “sesso”. Per il futuro, interessa però sapere P(H|O), in pratica la probabilità che H accada, dato O e in pratica che un messaggio indirizzato a quell’utente e che contiene la parola “sesso” sia effettivamente SPAM. Secondo il teorema di Bayes tale probabilità è la seguente. P (H|O) = P (O|H) * P (H)/P(O) Dove sia P(H) la probabilità che un messaggio sia SPAM, sia P(O) la probabilità che un messaggio contenga la parola “sesso” possono essere stimate esaminando le caselle di posta dell’utente. P(H) si stima esaminando comparativamente la cartella dove l’utente mette lo SPAM e la casella di posta generale dell’utente e contando quanti sono i messaggi di SPAM rispetto al totale dei messaggi. P(O) si stima contando quanti messaggi contengono “sesso” sul totale dei messaggi ricevuti dall’utente. Purtroppo, però, questa tecnica è praticamente impotente contro lo SPAM grafico. Infatti, molti spammer hanno reagito a queste tecniche evolute di riconoscimento spostando la parte informativa dei loro messaggi all’interno d’immagini raster, da inviare poi come allegati MIME o agganciare ai messaggi scrivendoli in formato HTML. I messaggi ritenuti fidati dal server di posta, dai filtri, sono recapitati al destinatario, mentre i messaggi sospetti sono eliminati dal server di posta. Le operazioni di verifica sui messaggi sono applicate sia ai messaggi in ingresso sia ai messaggi in uscita e possono essere riassunte in sei passi. 1. Verifica della presenza del mittente nella white list. 2. Verifica della presenza del mittente nella black list. 3. Verifica dell’impronta digitale del messaggio. TPSIT 5 434 um 270 di 4. Verifica mediante filtri euristici. 5. Verifica mediante filtri bayesiani. 6. Verifica mediante antivirus. I filtri bayesiani eseguono un’analisi dei messaggi che l’utente indica essere posta spazzatura e costruiscono un dizionario di termini “sospetti”. Poi analizzano la posta accettata e costruiscono un dizionario di termini “legittimi”. Sulla base di questi dizionari, sono in grado d’identificare quali messaggi potrebbero essere spazzatura o meno. Questa analisi è svolta all’inizio e prende il nome di “allenamento”. In sostanza, appena dopo aver installato il filtro nell’applicazione di posta, è necessario passare qualche giorno indicando al sistema quale dei messaggi ricevuti è buono e quale invece è posta spazzatura. Quando la fase di allenamento è conclusa, il sistema filtra autonomamente i messaggi, marcandoli come spazzatura o posta accettata. Per implementare un filtro bayesiano occore costruire un dizionario che contiene una serie di parole, con il numero di occorrenze delle stesse passate per la fase di allenamento. PROGRAMMAZIONE Il sistema da progettare è costituito da un server contenente le definizioni e da un client il cui scopo è duplice: scaricare l’email e accedere alle definizioni di segnalazione di quelle sospette. Il client filtra l’email in arrivo, sulla base delle definizioni scaricate dal server in formato XML e marca quelle sospette con la dicitura “SPAM”. Gli utenti abilitati al servizio segnalano al server, direttamente dal client, eventuali mail indesiderate, il server immagazzina l’informazione all’interno di un DB che contiene le segnalazioni di tutti gli utenti registrati. Ciò che sarà archiviato sul server è solo l’indirizzo del mittente dell’email sospetta. Qualora lo stesso indirizzo fosse segnalato da più utenti, è incrementato un contatore che definirà il “rating” dell’indirizzo. Più il rating è alto, più è probabile che l’email ricevuta sia effettivamente SPAM. La lista degli indirizzi “sospetti” è scaricata in formato XML dal client di posta che, confrontando gli indirizzi dei mittenti dell’email in arrivo con quelli contenuti nel file delle definizioni, può marcare opportunamente quelle riconosciute come SPAM aggiungendo successivamente il valore del rating. L’interazione tra il client e il server avviene tramite un WS predisposto ad accettare le richieste degli utenti abilitati. Il progetto è una libreria di classi, quindi priva d’interfaccia utente ma il servizio è esposto attraverso un sito web, per farlo è possibile grazie a WSE 2. SoapService, usato per realizzare i servizi web, deriva infatti da SoapReceiver che implementa l’interfaccia I’HttpHandler. L’indirizzo cui le classi saranno raggiungibili si definisce attraverso un HttpHandler direttamente nel WEB.CONFIG del progetto web. Amministrazione del server Avviene attraverso un tool di gestione separato da installare sul PC dell’amministratore che fa uso di un secondo servizio web per comunicare con il server. TPSIT 5 434 um 271 di Digitare UserName e Password nella form di login e cliccare sul relativo pulsante. Una volta autenticati, si può eseguire una qualsiasi delle operazioni per cui il tool è preposto: inserimento di nuovi utenti, visualizzazione e cancellazione di utenti, visualizzazione delle segnalazioni di SPAM. Tutti i metodi pubblici marcati con l’attributo [SoapMethod()] sono descritti nel contratto WSDL e possono essere utilizzati nei progetti. Server CREATE TABLE tblUsers ( ID_Utente int IDENTITY (1,1), UserName varchar (50), TPSIT 5 434 um 272 di Password varchar (50), EMail varchar (50), Livello int, PRIMARY KEY(ID_Utente) ); CREATE TABLE tblEmail ( ID_Mail int IDENTITY (1,1), Email varchar (50), Rating int, PRIMARY KEY(ID_Mail) ) INSER INTO tblUsers VALUES ('Admin', 'Admin', 1) INSER INTO tblUsers VALUES ('User', 'User', 0) Dopo aver realizzato il DB, il primo problema da affrontare è quello di rendere disponibili i dati in esso contenuti verso l’esterno. Il server deve permettere le seguenti operazioni. 1. Segnalare un’email come SPAM. 2. Trasmettere l’elenco degli indirizzi email in formato XML. 3. Registrare nuovi utenti abilitati al servizio. 4. Eliminare utenti dal DB. Queste operazioni possono essere divise in due famiglie. 1. Gestione utenti. 2. Gestione SPAM. Si mantiene quindi la stessa distinzione logica anche sul server, realizzando due distinti WS, per la loro realizzazione si usa il riferimento Microsoft.Web.Services2 che si ottiene installando nel PC il pacchetto MICROSOFT WSE 2.0 SP3.MSI, perché avendo la necessità di proteggere l’accesso al servizio, implementa le specifiche WS-Security. Nel corso di tutto lo sviluppo si utilizzerà una sola soluzione in cui inserire tutti i progetti che compongono il sistema anti SPAM. TPSIT 5 434 um 273 di AntiSpamServerEngine File SPAMMANAGER.CS Definizione del servizio che si chiamerà SpamManager. Il metodo che consente di segnalare un indirizzo di posta da considerare come SPAM si chiama SegnalaMail e accetta come parametro d’ingresso l’email da segnalare. L’attributo [SoapMethod("urn:SegnalaMail")] non fa altro che comunicare a SoapService che il metodo SegnalaMail sarà disponibile via Soap Message. SoapMethod sostituisce il WebMethod dei servizi web tradizionali. SpamManagerDB è la classe che astrae la logica di accesso ai dati dal servizio web. Prima d’inserire l’indirizzo nel DB, bisogna assicurarsi che sia sintatticamente valido con il metodo IsValidAmail che, usando una Regular Expression, restituisce true se l’indirizzo è sintatticamente corretto, false in caso di indirizzo errato. File USERMANAGER.CS C’è la dichiarazione del [SoapMethod()] e quella del metodo stesso AddNewUser che accetta come parametro in ingresso un tipo complesso, ovvero un semplice oggetto che descrive l’utente che si vuole inserire nel DB. Leggere il SoapContext del messaggio SOAP in arrivo, è un meccanismo per applicare dei filtri al messaggio SOAP in uscita. Il primo controllo che si fa è relativo alla presenza o meno di un context. Le credenziali dell’utente per effettuare il login sono passate appunto nel context. Il controllo appena effettuato taglia tutte le richieste che non lo hanno. Il secondo tipo di controllo effettuato ha lo scopo di ciclare all’interno di tutti gli eventuali context presenti nel messaggio, recuperare il token con le credenziali dell’utente e verificarne il ruolo. CustomSecurityTokenManager Il comportamento di default del sistema di autenticazione si basa sugli account del SO. Questo vuol dire che è necessario creare tanti utenti sul server quanti sono quelli abilitati all’utilizzo del servizio. Questo progetto ha lo scopo di sostituire il meccanismo di autenticazione di default di WSE 2 con uno sviluppato ad hoc. Avendo predisposto una tabella nel DB per immagazzinare i dati degli utenti, si validano le credenziali ricevute direttamente sul DB. Un progetto separato offre il vantaggio non indifferente di poter sostituire il meccanismo di autenticazione in qualsiasi momento, senza dover intervenire sul resto della soluzione. CustomSecurityTokenManager estrae i dati dell’utente che “chiama” il servizio dal contex. I dati estratti, nome utente e password, saranno confrontati con quelli contenuti nel DB e, in caso di riscontro positivo, la chiamata al servizio potrà essere effettuata. DataLayer File SPAMMANAGER.CS La prima cosa da verificare è se l’email è stata segnalata qui; questo si fa richiamando il metodo EmailCounter passando come parametro l’email che si desidera controllare, assegnando il valore di ritorno ad una variabile chiamata Rating: se il rating è 0 allora l’indirizzo non è presente nel DB. È il caso in cui un indirizzo sia stato segnalato per la prima volta. TPSIT 5 434 um 274 di Il metodo AddNewEmail si occupa d’inserire l’email all’interno del DB. Se invece il rating è maggiore di 0, l’indirizzo passato come parametro al metodo EmailCounter è già presente nel DB e quindi va incrementato solo il rating. L’ultima cosa che manca è il download delle definizioni in formato XML. Il client utilizza un file XML come file di definizione dell’email di SPAM, questo file dev’essere generato sul server e salvato direttamente sul PC dell’utente. Per la generazione di questo file si usa la funzione di SQL Server for xml che restituisce un result set direttamente in formato XML. Nel DataLayer si ha un metodo chiamato MailList che interroga il DB. TestClient File CLASS1.CS Il result set generato è composto da TAG XML e sarà inviato al client in formato stringa. Una volta ricevuto dal client, la stringa dev’essere estratta dal messaggio SOAP e utilizzata per creare l’XML con le definizioni, per poi essere salvato sul disco. Ora inviare le credenziali dell’utente, per permettere al CustomSecurityTokenManager di controllarle. Iniziare col definire il messaggio che sarà di tipo SoapEnvelope. Il corpo di questo messaggio conterrà l’oggetto userInfo creato sulla base delle informazioni dell’utente che si vogliono inserire nel DB. ASService Usando WSE 2.0 è possibile esporre le classi in svariati modi, si può utilizzare un server web insieme al protocollo HTTP, oppure si può realizzare un servizio di Windows o un’applicazione di tipo CLI da interrogare via TCP. La soluzione usata è quella di utilizzare un sito web, esporlo su un sito dà il vantaggio di avere un servizio localizzabile attraverso un URL e, lo stesso sito potrebbe essere usato per promuovere il servizio e far registrare gli utenti. Creare un progetto chiamandolo ASService, si deve far sì che il sito intercetti le richieste SOAP in arrivo e le rimandi alle classi del progetto UserMamanager e SpamManager. TPSIT 5 434 um 275 di Le classi del progetto AntiSpamServerEngine sono ora accessibili attraverso il sito in ASService. File WEB.CONFIG. Si devono configurare due HTTP handler per reindirizzare le richieste HTTP alle classi. Usare quindi due nomi fittizi UserManager e SpamManager associandoli all’estensione ASHX già mappata su IIS. Questa configurazione permette di girare le richieste alla pagina USERMANAGER.ASHX alla classe programmo.AntiSpamServerEngine.UserManagerService che rappresenta il servizio web. Per rendere attivo il meccanismo di autenticazione personalizzato, si deve aggiungere un riferimento a questa classe nella sezione <security>del nodo Microsoft.Web.Service2. Salvare e compilare, quindi richiamare la pagina seguente. Quello che si vede nella pagina è il “contratto SOAP” WSDL esposto dal servizio web, sarà poi utilizzato per creare i metodi per interrogare il servizio. Copiare la cartella ASService sul disco e renderla una cartella virtuale in IIS. TPSIT 5 434 um 276 di Client Implementare un client, capace di comunicare con il server e gestire l’email, dove una classe gestisce il protocollo POP3, POP3.CS, dove ci sono tutti i metodi che si occupano della creazione del canale di comunicazione, dell’invio dei comandi al server e della lettura dei risultati che dovranno poi essere gestiti in modo opportuno dal client. In Visual Studio, scegliere la voce File/Nuovo/Progetto (CTRL+N), nella finestra Nuovo Progetto selezionare Altri linguaggi/Progetti Visual C# e Windows/Applicazione per Windows Form come modello. Nel form principale si devono inserire tutti gli elementi che compongono l’interfaccia utente del client. Per la barra di navigazione di sinistra è stata scelta una ListView. Nella parte centrale alta posizionare un DataGrid mentre in quella bassa un Panel. Il nostro client, oltre che dal form principale, sarà composto da altri elementi tra cui il DataSet tipizzato per la memorizzazione dei messaggi, tutti gli elementi sono raggruppati sotto un unico progetto. TPSIT 5 434 um 277 di La logica del S/W è implementata nel form principale. File FORM1.CS. Il metodo che permette di scaricare la posta sul client è DownloadMails, dove è istanziata la classe POP3. È effettuata la connessione al server e sono inviate le credenziali. Se la connessione ha esito positivo, il server è raggiungibile e le credenziali inviate sono valide, quindi si possono inviare i comandi supportati dal protocollo. Quello che interessa è scaricare i messaggi sul PC in modo da poterli leggere anche in modalità disconnessa. Dovendoli scaricare tutti, per prima cosa si deve conoscere quanti messaggi ci sono. Il comando da inviare è STAT. La risposta, qualora ci fossero dei messaggi, sarà ad esempio la seguente. +OK 7 5678 Dove. +OK indica che il comando inviato è corretto e non ha causato errori. 7 indica il numero dei messaggi presenti. 5678 rappresenta la dimensione totale dei messaggi. Per facilitare l’estrazione del numero dei messaggi dalla risposta al comando STAT, nella classe POP3 è stata implementata una proprietà che ritorna direttamente il numero dei messaggi, MailNumber che fa riferimento al metodo ParseSTAT. int totmail = pop3.MailNumber; TPSIT 5 434 um 278 di Dato il numero, si deve inviare il comando RETR per ogni messaggio presente sul server: Quello che si riceve dal comando RETR è una stringa contenente l’intero messaggio, header compresi. Da questa stringa si devono estrarre le informazioni che permettono di rendere più presentabile il messaggio nonché di memorizzarlo sul disco. Questo problema si risolve con un parser cui passare la stringa ricevuta dal server di posta, il risultato sarà quello di memorizzare in una serie di variabili i campi seguenti. FROM il mittente del messaggio. TO il destinatario. SUBJECT oggetto del messaggio. BODY corpo del messaggio. DATE data del messaggio. MESSAGEID ID del messaggio. Il codice del parser è contenuto nel file PARSEEMAILMESSAGE.CS. Il problema, relativo alla presentazione e memorizzazione dei dati, vista la semplicità delle informazioni da memorizzare si risolve con un DataSet tipizzato, è una rappresentazione dei dati residente in memoria. Sebbene un DataSet è utilizzato in accoppiata ad un DB per la gestione dei dati in modalità disconnessa, nulla impedisce di usarlo come entità a sé stante. Per aggiungere un DataSet al progetto è sufficiente cliccare con il tasto destro del mouse sull’icona relativa al progetto e selezionare la voce Aggiungi/Aggiungi nuovo elemento… (CTRL+MAIUSC+A). Nell’elenco degli elementi che possono essere aggiunti, selezionare il Dataset, chiamandolo dsMail e fare clic su Aggiungi. Visual Studio offre uno strumento visuale per realizzare i DataSet. Aprire il file DSMAIL.XSD e dalla casella degli strumenti aggiungere gli elementi che interessano, il primo sarà un Element di nome Mail che rappresenta la tabella, come se si operasse su un DB, aggiungere all’elemento appena inserito i vari campi che interessano. TPSIT 5 434 um 279 di Ora che si ha il parser e il contenitore per i messaggi scaricati, creare una nuova istanza del DataSet. dsMail dsmail = new dsMail(); Creare una nuova DataRow, in altre parole una nuova riga nel DataSet, valorizzandone i campi con il risultato del parser e aggiungerli al DataSet. Dato che il ciclo sarà ripetuto per tutti i messaggi presenti, ogni riga del DataSet conterrà un singolo messaggio scaricato dal server. Ora si deve implementare nel client le funzionalità che consentono di ricevere il file delle definizioni e di usarlo. Il primo passo sarà quello di prendere la classe Proxy relativa alla gestione del server anti SPAM, ANTISPAMMANAGERSERVICES.CS e includerla nel progetto. Tale classe, infatti, contiene il metodo GetSpamList che ritorna l’elenco delle definizioni. Dato che il download delle definizioni richiede un accesso sicuro al server anti SPAM, includere nel progetto anche la classe per la gestione del token di autorizzazione AUTHMANAGER.CS. Nel client, il metodo che si occupa di ricevere la lista delle definizioni e di salvarla sul disco si chiama DownloadSpamList. Dopo aver creato il token con le credenziali dell’utente, costruire il messaggio SOAP da inviare al server anti SPAM con la richiesta del file con le definizioni. Il metodo GetSpamList della classe proxy ritorna una stringa che si trasforma in XML con XmlDocument.LoadXml e si salva il risultato sul disco. Il client dovrà “filtrare” l’email ricevute e anteporre a quelle che ritiene SPAM una dicitura che le distingua dalle altre mail. In particolare, a differenza degli altri campi, il Subject dell’email è filtrato prima di essere inserito nel DataSet di appoggio. Ora si può scaricare la posta e salvarla sul disco, si può scaricare e usare il file con le definizioni di SPAM ma non si può ancora segnalare nuovo SPAM. La classe proxy ANTISPAMMANAGERSERVICES.CS contiene il metodo SegnalaMail che accetta come input una stringa, l’indirizzo da segnalare. Questo metodo richiamerà il corrispettivo del server anti SPAM che si occuperà dell’inserimento dell’indirizzo nel DB. Per recuperare l’indirizzo da segnalare dal DataSet popolato con i dati dell’email ricevute occorre abilitare l’evento CurrentCellChanged del DataGrid che mostra l’elenco dell’email, passare lo stesso DataGrid alla metodo GetCurrentBindedObject che si occuperà di estrarre dal DataSet associato i dati che interessano. Si usa il GetCurrentBindedObject invece di recuperare i dati direttamente dal DataGrid, perché conviene sempre prelevare i dati dal DataSet associato in quanto il DataGrid potrebbe nascondere alcune delle colonne del DataSet. Ritornando al codice dell’evento CurrentCellChanged, si può vedere che, oltre a TPSIT 5 434 um 280 di valorizzare il contenuto di alcune TextBox utili a visualizzare il contenuto dell’mail, si valorizza anche un campo di tipo stringa denominato mail_From. tbxFrom.Text = drv["From"].ToString(); mail_From = tbxFrom.Text; Che sarà esattamente quello che si passa al metodo SegnalaMail della classe Proxy. Il client ora ha tutte le funzionalità che servono per scaricare i messaggi di posta, filtrarli e, eventualmente, segnalare nuovo SPAM. Scegliendo il sistema di memorizzazione dei dati, si è preferito un DataSet ad un DB. A differenza di un DB però, l’istanza di un DataSet “vive” finché è mantenuto un riferimento a essa. Dopo di che, il GC (Garbage Collector) libererà la memoria e i dati andranno persi. Occorre quindi a “salvare” il DataSet per ritrovarlo integro a ogni riavvio dell’applicazione. Si sfrutta una funzionalità fornita dal .Net Framework: la serializzazione. Usare il BinaryFormatter che risulta essere semplice da usare e veloce nelle operazioni di serializzazione e deserializzazione. Come prima cosa quindi creare un’istanza del Formatter che si vuole utilizzare. Dovendo salvare i dati sul disco, predisporre uno StreamWriter chiamato dat che scriva il dato nella cartella Mails contenuta nella stessa cartella di esecuzione dell’applicazione. Nella cartella Mails si trova ora un nuovo file chiamato MAILS.DAT che conterrà la rappresentazione binaria del DataSet. Per recuperarlo si deve avviare il processo inverso, chiamato deserializzazione. Lo stesso principio è stato utilizzato per la persistenza dei dati relativi alla casella di posta da controllare e ai dati di login al server anti SPAM solo che, al posto di serializzare un DataSet, è stato serializzato un normale oggetto. File SPAMFILTER.CS Il metodo statico Filter si occupa di valutare l’email in arrivo basandosi sul file XML contenente le definizioni. Il primo step è quello di “leggere” il file XML delle definizioni, l’oggetto XmlTextReader fornisce un rapido accesso al contenuto di tale file. L’applicazione del filtro consiste nel valutare se il mittente dell’email, mailFrom, è contenuto nell’elenco delle definizioni. Qualora il riscontro dovesse avere esito positivo, la funzione passa alla lettura del rating e antepone al Subject originale la dicitura seguente. return "[***SPAM*** " + rating +" ] " + mailSubject; In questo modo, tutte l’email ritenute SPAM saranno contrassegnate e saranno facilmente distinguibili da quelle che non lo sono. Alla prima esecuzione del client di posta è chiesto d’impostare i dati di accesso sia al server POP3 sia al server anti SPAM. Per recuperare questo parametro fare riferimento all’ISP che fornisce la casella di posta elettronica. UserName e Password sono quelle che si usano per leggere la posta. Nella scheda Settaggi server antispam bisogna inserire i dati di accesso al servizio, di default UserName = User e Password = User. Una volta inseriti questi dati sarà possibile utilizzare il client. Sulla barra di sinistra è presente una serie d’icone, ognuna delle quali avvia un comando specifico, ogni comando si attiva con un doppio clic sull’icona relativa. Per scaricare l’email quindi, sarà sufficiente fare doppio clic sull’icona Scarica Mail. TPSIT 5 434 um 281 di L’email scaricate con questo client non saranno cancellate dal server. TPSIT 5 434 um 282 di JAVA Per implementare un filtro bayesiano occore costruire un dizionario che contenga una serie di parole, con il numero di occorrenze delle stesse passate per la fase di allenamento. Nel progetto questa fase è simulata da due file. 1. File NORMALE.MBOX che contiene posta legittima. accesso=1.0 account=9.0 acquisizione=5.0 commerciale=3.0 comunicabile=0.0 comunicato=1.0 TPSIT 5 434 um 283 di compagnia=17.0 File JUNK.MBOX che contiene una serie di messaggi spazzatura. lots=1.0 lou=5.0 love=5.0 viagra=9.0 cialis=9.0 levitra=9.0 sesso=7.0 low=3.0 File DIZIONARIO.JAVA package bayes; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; public class Dizionario { public final static String TOKENS = "!#* \t\n,:"; Map dizionario = new HashMap(); String filename; // crea un dizionario di termini e loro occorrenze // @param filename file che contiene la casella da analizzare public Dizionario( String filename) { this.filename = filename; } È definita la classe Dizionario che dispone di tre attributi: una costante che indica quali sono i separatori di parola, una Map che conterrà le parole e le occorrenze e una stringa per ospitare il nome del file da caricare. La classe ha un costruttore che permette di specificare il nome del file da leggere. // importa la casella di testo public void importa() throws IOException { FileReader fr = null; try { fr = new FileReader( filename ); BufferedReader br = new BufferedReader( fr ); while( true ) { String line = br.readLine(); if (line==null) break; StringTokenizer st = new StringTokenizer( line, TOKENS ); while( st.hasMoreTokens() ) { increment( st.nextToken() ); } } } finally { TPSIT 5 434 um 284 di if (fr == null) fr.close(); } System.out.println("Importato il dizionario " + filename + " per un totale di " + dizionario.size() + " token" ); } La seconda fase prevede la lettura del file di testo, utilizzando un oggetto FileReader associato ad un BufferedReader, in questo modo è letta una riga alla volta e spezzata in token attraverso un oggetto StringTokenizer. Questo è costruito con la costante TOKENS definita al passo precedente. In questo modo è considerata una parola unica tutto quanto non separato dai caratteri sopra indicati; per ciascuna parola così ottenuta è chiamato il metodo increment che si occupa di aumentare il conteggio di questa parola, se non esiste. // incrementa il conteggio di un token void increment( String token ) { Float count = (Float)dizionario.get(token); if (count == null) { count = new Float(1); } else { count = new Float( count.floatValue() + 1 ); } dizionario.put( token, count ); } Il metodo increment verifica se nel dizionario è già presente la parola. In questo caso ne ottiene il numero di occorrenze che aumenta di 1, per poi reinserire i dati aggiornati nel dizionario. Nel caso la parola non esista, è inserita nel dizionario con contatore a 1. // ritorna la probabilità di un token float get( String token ) { Float value = (Float)dizionario.get(token); if (value == null) return 0.0f; else return value.floatValue(); } La classe è poi integrata con due funzioni aggiuntive che permettono d’investigare i dati del dizionario da un’altra classe. Il metodo get ritorna il numero di occorrenze di una parola passata come parametro. Se questa non esiste nel dizionario è ritornato 0.0f. Il dato è restituito come tipo primitivo float per agevolare le operazioni successive. // numero di parole nel dizionario int size() return dizionario.size(); TPSIT 5 434 um 285 di File FILTROBAYESIANO.JAVA Una volta in possesso di dizionari, è possibile creare il filtro di posta che è implementato con una singola classe. package bayes; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.StringTokenizer; import java.util.TreeMap; public class FiltroBayesiano { Dizionario postaAccettata; Dizionario postaRifiutata; // crea un nuovo filtro, utilizzando i dizionari di posta // accettata e rifiutata indicati public FiltroBayesiano( Dizionario postaAccettata,Dizionario postaRifiutata ) { this.postaAccettata = postaAccettata; this.postaRifiutata = postaRifiutata; } } Il codice prevede un singolo costruttore che si aspetta i due dizionari di riferimento, il metodo che esegue il filtro del contenuto di un file non fa altro che caricare il file. // filtra un messaggio contenuto in un file public float filtra( File file ) throws IOException { FileReader fr = new FileReader(file); char[] buffer = new char[ (int) file.length() ]; fr.read( buffer ); fr.close(); return filtra( new String(buffer) ); } Il file è caricato in un unico blocco di caratteri, memorizzati in un array di char. Da questo è costruita una stringa che è passata al metodo filtra(string). Questo contiene la logica di valutazione del messaggio che è composta da tre passaggi. 1. Estrazione di tutte le parole presenti nel messaggio, in modo similare a quanto fatto per la costruzione dei dizionari e calcolo delle probabilità dei singoli token. 2. Estrazione delle parole che hanno probabilità più lontana dal punto medio, identificato da una probabilità dello 0.5. 3. Calcolo della probabilità combinata di tutti i termini che produce la probabilità che il messaggio sia posta indesiderata. // filtra un messaggio, indicando se potrebbe essere posta spazzatura public float filtra( String messaggio ) { Map tokenProb = new HashMap(); StringTokenizer st = new StringTokenizer( messaggio, Dizionario.TOKENS ); while( st.hasMoreTokens() ) { TPSIT 5 434 um 286 di String token = st.nextToken(); tokenProb.put( token, calcolaProbabilita(token)); } System.out.println( tokenProb ); float[] pb = estraiProbabilita( tokenProb ); return probabilitaCombinata( pb ); } Il primo passo è espletato tramite l’oggetto di tipo StringTokenizer. L’approccio è simile alla classe Dizionario ma nella mappa, per ogni parola, non è presente il numero di occorrenze ma la probabilità calcolata con il metodo calcolaProbabilità. Questo metodo mette in relazione le occorrenze della parola nei dizionari di posta accettata e rifiutata, calcolando la probabilità che la mail che contiene questa parola sia indesiderata. Se una parola non è presente nei dizionari assume un valore di 0.4. // calcola la probabilità di un singolo token Float calcolaProbabilita( String token ) { float result = 0.4f; float g = postaAccettata.get(token) * 2; float b = postaRifiutata.get(token); if ( !((g+b) < 5) ) { float temp1 = b / postaRifiutata.size(); temp1 = Math.min( temp1, 1 ); float temp2 = g / postaAccettata.size(); temp2 = Math.min( temp2, 1 ); float temp = temp1 / (temp1+temp2); result = Math.min( 0.99f, temp ); result = Math.max( 0.1f, result ); } return new Float(result); } Una volta ottenuto il dizionario dei token del messaggio da valutare, sono considerati i 15 elementi con maggior discostamento dal punto medio 0.5: il discostamento deve considerare il segno. Ad esempio, un valore 0.8 si discosta di 0.3 dal punto medio (0.8 – 0.5 = +0.3), mentre un valore 0.3 si discosta di -0.2 (0.3 – 0.5 = –0.2). Per ottenere l’elenco dei token ordinati per discostamento, è costruita una mappa ordinata. Nella mappa la chiave è il punteggio, mentre il valore è la probabilità. Da questa mappa sono estratti i primi 15 elementi e ritornati sotto forma di array di float. // estrae le 15 probabilità più lontane dal valore medio 0.5f float[] estraiProbabilita( Map dizionario ) { final int size = 15; float[] pb = new float[ size ]; // crea una mappa con le probabilità e i punteggi relativi // cioè la distanza dal valore medio Map punteggi = new TreeMap(); for( Iterator iter = dizionario.keySet().iterator(); iter.hasNext(); ) { TPSIT 5 434 um 287 di String key = (String)iter.next(); Float currentObject = (Float)dizionario.get(key); float current = currentObject.floatValue(); float punteggio = Math.abs( current - 0.5f ); punteggi.put( new Float( punteggio ), currentObject ); } // la mappa è ordinata per chiave, dunque estraendo i // primi 15 elemeni si anno le probabilità più lontane dal valore medio int i = 0; for( Iterator iter = punteggi.keySet().iterator(); iter.hasNext(); ) { Float key = (Float)iter.next(); Float currentObject = (Float)punteggi.get(key); pb[ i++ ] = currentObject.floatValue(); if (i>=size) { break; } } return pb; } Una volta in possesso di questo array di probabilità, è possibile calcolare la probabilità combinata. In pratica la probabilità che tutti i casi si verifichino, la formula relativa è la seguente. abc + (1 - a)(1 - b)(1 - c) Dove a, b e c sono valori di probabilità. Ovviamente la formula si espande fino a contemplare tutte e 15 le probabilità estratte al passo precedente. La probabilità combinata è implementata nel metodo seguente. // calcola la probabilità combinata di un insieme di valori di numerosità arbitraria float probabilitaCombinata( float[] pb ) { float result = 0; // calcola il prodotto delle probabilitˆ float prodotto = pb[0]; for( int i=1; i<pb.length; i++ ) { prodotto *= pb[i]; } // calcola il prodotto di 1-x result = (1 - pb[0]); for( int i=1; i<pb.length; i++ ) { result *= (1 - pb[i]); } return prodotto / ( prodotto + result ); } L’array di float passato come parametro potrebbe essere di qualsiasi lunghezza. Per prima cosa è calcolato il prodotto delle probabilità, in seguito è calcolato il prodotto di tutte le probabilità (1 – x). Il valore ritornato dal metodo filtra è un float che può assumere un valore vicino allo 0. TPSIT 5 434 um 288 di In questo caso il messaggio è accettato. Se il valore è invece superiore a 0.9, il messaggio è molto probabilmente posta indesiderata. Nella classe FiltroBayesiano sono presenti metodi di utilità per eseguire queste valutazioni e stampare un messaggio a video. Il metodo main contiene del codice di prova che esegue il filtro a fronte di messaggi spazzatura CATTIVO.TXT o legittimi BUONO.TXT. A questo punto è possibile costruire i due dizionari necessari all’operatività di un filtro bayesiano. public static void main(String[] args) throws IOException { Dizionario dizionarioJunk = new Dizionario("junk.mbox"); dizionarioJunk.importa(); Dizionario dizionarioNormale = new Dizionario("normale.mbox"); dizionarioNormale.importa(); FiltroBayesiano filtro = new FiltroBayesiano(dizionarioNormale, dizionarioJunk); filtro.stampaEsitoFile("cattivo.txt"); filtro.stampaEsitoFile("cattivo1.txt"); filtro.stampaEsitoFile("buono.txt"); filtro.stampaEsitoFile("buono1.txt"); } L’output che si ottiene è il seguente. Importato il dizionario I:/junk.mbox per un totale di 19 token Importato il dizionario I:/normale.mbox per un totale di 7 token >>> Il messaggio è posta accettata con una probabilità di 0.0 TPSIT 5 434 um 289 di SNMP INTRODUZIONE È il protocollo standard per il network management su reti IP, opera al livello 7 del modello ISO/OSI, controlla qualunque tipo di device che lo supporti, compresi router, PC e stampanti. L’architettura di cui il protocollo SNMP fa parte, è definita con il nome NMF (Network Management Framework) e consente la gestione dei device gestibili attraverso l’utilizzo di agenti ossia moduli S/W che risiedono su ciascuno di essi. Ciascun agente non fa altro che comunicare con almeno una stazione di gestione centralizzata NMS (Network Management Station): il manager, attraverso la quale è possibile leggere e/o scrivere informazioni e raccogliere eventuali segnalazioni, trap, inviatele da essi. Le informazioni che si possono gestire per un’apparecchiatura, mediante il protocollo SNMP, sono dette MO (Management Objects) e sono contenute all’interno di file chiamati MIB (Management Information Base) secondo la struttura definita nella SMI (Structure Management Information) che è costituita da un insieme di strutture comuni e da uno schema identificativo usato per far riferimento alle variabili del MIB. Componenti dell’architettura. SNMP: protocollo per la comunicazione tra agenti e sistema centrale. NMS: sistema centrale. Agente: sistema S/W residente su ogni device. MIB: formato per la descrizione delle caratteristiche di ogni device. Trap: allarmi, ossia particolari pacchetti inviati dagli agenti all’NMS per la notifica di certe situazioni. Progettare un sistema in grado di monitorare un certo numero di periferiche permettendo, nel contempo, di ricevere opportuni allarmi nel caso di malfunzionamento. Il S/W deve analizzare il traffico che passa su una scheda di rete, oppure determinare l’occupazione della memoria o il regime d’uso dei dischi. L’architettura è costituita dai seguenti componenti. Un sistema centrale in grado di raccogliere informazioni sullo stato di ogni device da monitorare e capace d’interpretare i messaggi che sono inoltrati da essi. Un linguaggio comune in grado di fare da ponte tra le periferiche e il sistema centrale. TPSIT 5 434 um 290 di Problema: il sistema chiederà le informazioni sullo stato alle periferiche oppure saranno le periferiche a inviare autonomamente le informazioni al sistema. Affinché il sistema funzioni in maniera bidirezionale, è necessario che ogni device sappia in che modo interpretare le richieste ricevute dalla centrale e, allo stesso modo, sappia come avvertirla relativamente agli eventi che la riguardano. La bidirezionalità è, ovviamente, una caratteristica necessaria in questi sistemi perché se per qualche ragione il device non riuscisse a comunicare con la stazione centrale per assenza di collegamento o per altre ragioni, quest’ultima deve poter tentare in qualche modo di contattare il device a determinate scadenze di tempo. Una volta stabilito che le due componenti debbano comunicare tramite un protocollo comune, sarà necessario implementare un S/W che gestisca questo genere di comunicazione: questo tipo di S/W prende il nome di agente. Ogni device, diverso da tipo a tipo, deve esporre le proprie caratteristiche che devono, peraltro, essere messe a conoscenza di ogni agente e, ancora, del sistema centrale. Occorre quindi anche un DB che contenga la descrizione delle caratteristiche esposte da ogni periferica. FORMATO MIB È un’astrazione di un generico DB, se per un device non si ha disponibile il MIB è paragonabile ad un device che non esiste poiché non si sa come interpretare le informazioni da richiedere o ricevute da un agent. Un MIB è un file di testo, scritto secondo una notazione particolare che permette di descrivere tutte le variabili alle quali si può accedere via SNMP e relative a quel determinato device. Tutti i MIB fanno parte di una struttura gerarchica simile a quella utilizzata dai DNS per i nomi dei domini, costituita da una radice senza nome e all’interno della quale ogni nodo contiene un identificatore univoco, tipi di dati e modalità di accesso per ciascuna variabile in essi contenuta. Al di sotto della radice di quest’albero, sono inseriti i MIB relativi alle diverse organizzazioni standard e quelli che non risultano standard, solitamente posizionati all’interno del ramo experimental. Le modalità di lettura di una determinata variabile sono analoghe a quanto è fatto per lo spazio dei nomi DNS. Si parte dalla radice, indicata con un punto e si scorre l’intera gerarchia sino al raggiungimento dell’informazione voluta. In questa maniera, ogni foglia dell’albero può essere identificata da una sequenza di nomi, Object Name o, analogamente, di numeri OID (Object IDentifier) separati da un (.). Ad esempio, .iso.org.dod.internet.mgmt.mib-2.system e .1.3.6.1.2.1.1 identificano la stessa cosa. Ovviamente, ciascun identificativo dev’essere necessariamente unico e ciò implica che all’interno di un qualunque sotto albero non possono esistere più oggetti aventi il medesimo OID. Se si parte dalla radice della struttura, scorrendola si nota che esiste il ramo dod (6) (US DEPARTEMENT OF DEFENSE), dal quale deriva il nodo internet (OID = 1.3.6.1). Al di sotto di esso si trovano diversi nodi tra cui i seguenti. management: questo sotto albero contiene le definizioni delle MIB standard. experimental: utilizzato per identificare oggetti che sono in fase sperimentale. private: utilizzato per identificare gli oggetti creati dai vari vendor come Cisco, IBM per la gestione delle variabile che sono specifiche di un loro prodotto. In conclusione, un agent deve possedere, per ogni device che intende gestire, il relativo MIB. TPSIT 5 434 um 291 di COMUNITÀ L’insieme degli apparati di rete che possono essere gestiti attraverso l’SNMP appartengono necessariamente ad una comunità. La comunità rappresenta un identificativo attraverso il quale si cerca di garantire un minimo di sicurezza quando avvengono i colloqui, via SNMP, tra NMS ed agenti. Questo vuol dire che un agent SNMP risponde solamente alle richieste d’informazioni effettuate da un NMS appartenente alla sua stessa comunità. Esistono tre tipi di comunità. 1. Monitor: permette di effettuare solamente interrogazioni agli agent quindi esclusivamente operazioni di lettura. 2. Control: permette, attraverso gli agent SNMP, di effettuare delle operazioni di lettura/scrittura sul device. 3. Trap: permette ad un agent d’inviare un opportuno messaggio (trap) SNMP alla management station per notificarle una determinata situazione. Bisogna tener presente che i nomi che definiscono una comunità sono formati da 32 caratteri e sono case sensitive. Esistono dei nomi predefiniti per i diversi tipi di comunità e molto spesso sono public per i casi di sola lettura e private per quelle in lettura/scrittura. Per aumentare il grado di sicurezza, naturalmente, è opportuno modificare queste impostazioni prima possibile. La maniera più valida per imparare a conoscere SNMP ma soprattutto per vedere se la configurazione delle macchine è a posto, è quella di provare praticamente a leggere alcune informazioni, interrogando direttamente il proprio PC o, meglio ancora, un’altra macchina in rete. Per far questo, sono necessari due passi. 1. Configurazione del protocollo SNMP. 2. Utility per lo scorrimento delle informazioni e per l’invio/la lettura delle trap. TPSIT 5 434 um 292 di In Windows ma la procedura è analoga anche su altri SO, occorre avviare i seguenti servizi: Start/Tutti i programmi/Strumenti di amministrazione/Servizi e verificare se il servizio SNMP è avviato con i parametri di default. Ora non serve che un’applicazione in grado d’interrogare, via SNMP, il PC di destinazione alla ricerca delle variabili dei MIB supportati. Esempio, leggere la variabile sysDescr, il cui percorso è identificato come system.1 o, .1.3.6.1.2.1.1.1.1 da PC configurato per la community di default public. TPSIT 5 434 um 293 di WMI (WINDOWS MANAGEMENT INSTRUMENTATION) È un componente del SO Windows ed è l’implementazione Microsoft di WBEM (WebBased Enterprise Management) che è un’iniziativa industriale per lo sviluppo di una tecnologia standard di accesso alle informazioni di gestione in un ambiente aziendale. WMI può essere utilizzato per automatizzare attività amministrative, quali la modifica del registro di sistema, in linguaggi di script che dispongono di un modulo di gestione per Windows e che supportano la gestione di oggetti ActiveX. WMI include un archivio di oggetti conforme agli standard CIM (Common Information Model) e il servizio CIMOM (CIM Object Manager) che gestisce l’acquisizione, la manipolazione di oggetti nell’archivio e la raccolta delle informazioni fornite dai provider WMI, il CIM offre, in definitiva le seguenti possibilità. Gestire gli oggetti come classi astratte, in questo modo, si può operare su una qualunque periferica o su di un processo qualsiasi attraverso esse. Una serie di proprietà e metodi che consentono la gestione delle suddette classi. namespace per gestire le classi di oggetti. In particolare, lo scopo di un namespace è quello di raggruppare tutte le classi e le istanze che riguardano un determinato sistema da monitorare. Si può anche definirlo come una porzione del CIM Schema. In particolare, il CIMV2 namespace che costituisce quello di default per WMI, raggruppa tutte le classi e le istanze che riguardano il sistema Windows locale. II provider WMI costituisce un concetto importante e il compito è quello di fungere da intermediario tra i componenti del SO e le applicazioni. Un esempio è proprio quello del provider SNMP che fornisce dati ed eventi dalle periferiche. In sostanza, quando il CIMOM riceve una richiesta da un’applicazione, management application, per dati che non sono disponibili nel CIM oppure per notifiche di eventi non supportati dal CIMOM stesso, inoltra tali richieste direttamente al provider WMI interessato. Start/Pannello di controllo/Installazione applicazioni Nella finestra Installazione applicazioni selezionare Installazione componenti di Windows TPSIT 5 434 um 294 di Selezionare la voce Provider WMI SNMP e confermare con OK. Selezionare la voce SNMP (Simple Network Management Protocol) e OK. I provider WMI altro non sono che delle DLL localizzate all’interno della cartella seguente: %SYSTEMROOT%\SYSTEM32\WBEM. All’interno di questa cartella ci sono file di tipo testo con estensione MOF contenenti le definizioni necessarie a registrare un provider WMI e, quindi, il set di classi che gli appartengono, all’interno del CIM repository. File NCPROV.MOF // Copyright (c) 1997-2001 Microsoft Corporation, All Rights Reserved TPSIT 5 434 um 295 di #pragma namespace("\\\\.\\root\\cimv2") class MSFT_NCProvEvent : __ExtrinsicEvent { string Namespace; string ProviderName; uint32 Result; }; class MSFT_NCProvNewQuery : MSFT_NCProvEvent { string QueryLanguage; string Query; uint32 ID; }; class MSFT_NCProvCancelQuery : MSFT_NCProvEvent { uint32 ID; }; class MSFT_NCProvAccessCheck : MSFT_NCProvEvent { string QueryLanguage; string Query; uint8 Sid[]; }; class MSFT_NCProvClientConnected : MSFT_NCProvEvent { boolean Inproc; }; class MSFT_WMI_GenericNonCOMEvent : __ExtrinsicEvent { string PropertyNames[]; string PropertyValues[]; string ProviderName; uint32 ProcessId; }; #pragma deleteinstance("MSFT_WMI_NonCOMEventProvider.Name=\"Standard NonCOM Event Provider\"",NOFAIL) Instance of __Win32Provider as $NonCOMProvider { Name = "Standard Non-COM Event Provider"; HostingModel = "Decoupled:NonCOM"; }; Instance of __EventProviderRegistration { Provider = $NonCOMProvider; EventQueryList = {"select * from MSFT_WMI_GenericNonCOMEvent", "select * from MSFT_NcProvEvent"}; }; #pragma namespace("\\\\.\\root\\cimv2") instance of __namespace{ name="ms_409";}; #pragma namespace("\\\\.\\root\\cimv2\\ms_409") #pragma deleteinstance("MSFT_WMI_NonCOMEventProvider.Name=\"Standard NonCOM Event Provider\"",NOFAIL) File WINMGMT.EXE TPSIT 5 434 um 296 di Tester di strumentazione gestione Windows, denominato anche WBEMTest, è un’utilità di uso generale per visualizzare o modificare classi, istanze o metodi dello schema CIM durante lo sviluppo di provider di strumentazione gestione Windows e di applicazioni WMI. Un consumer è un’applicazione che sfrutta i dati offerti da un provider WMI e rappresenta una management application. In particolare, esso interagisce con le WMI COM API, un set di oggetti COM utilizzabili da diversi linguaggi di programmazione. I provider WMI sono installati assieme al SO ma la lista di quelli messi a disposizione può essere allungata attraverso la definizione di propri. SNMP Provider funge da ponte tra i sistemi di monitoring e i device che devono essere controllati, consentendo la lettura e la scrittura di MIB SNMP e la rimappatura, in eventi WMI, di trap SNMP. Ovviamente affinché ciò sia possibile, è anche vero che ci debba essere un gruppo di classi, all’interno di WMI che consenta di rappresentare questo tipo d’informazioni. Per ottenere questo risultato e, quindi, interagire con una periferica della quale si possieda il proprio MIB, è necessario inserire tale DB nello schema di WMI affinché le sue caratteristiche siano visibili come oggetti astratti WMI. Un’utility che consente di effettuare questa operazione è SNMP Information Module Compiler SMI2SMIR.EXE che, opportunamente utilizzata, permette di mappare i MIB voluti nel formato equivalente CIM, consentendo d’interagire con un determinato device, sfruttando la sua Management Information Base ma attraverso WMI. In particolare, tali informazioni sono destinate all’interno di SMIR (SNMP Module Information Repository). smi2smir /a <Nome file MIB> Attraverso questo comando, s’inseriscono i dati di un MIB all’interno dell’SMIR per poterli utilizzare all’occorrenza. Ci sono anche consumer built-in che consentono di perfezionare il funzionamento di questa architettura. TPSIT 5 434 um 297 di Infatti, WMI dispone di diverse management application in grado di compiere determinate azioni a seguito del verificarsi di alcuni eventi. SMTP Event Consumer: invia una mail al verificarsi di un determinato evento. Command Line Event Consumer: avvia un processo al verificarsi di un determinato evento. Log File Event Consumer: scrive una stringa all’interno di un file di log al verificarsi di un determinato evento. Per poter accedere a oggetti contenuti all’interno del CIM Repository, sono necessarie tre informazioni. 1. Il namespace e il nome della macchina alla quale ci si riferisce ad esempio root\CIMV2. 2. La classe alla quale ci si riferisce ad esempio Win32_Service. 3. L’istanza alla quale ci si riferisce ad esempio Win32_Service.Name="SNMP". Il primo passo può essere portato a termine attraverso due possibili modi. Creare un oggetto SWBemLocator e utilizzando il metodo ConnectServer per indicare il namespace che interessa. Microsoft Visual Basic 6 Il metodo ConnectServer ritorna un oggetto di tipo SwbemServices. A questo si è agganciato il namespace, ora si deve interrogare il CIM Repository alla ricerca delle informazioni, per far questo, utilizzare WQL. Questa query WQL non fa altro che cercare, all’interno della classe Win32_Service, tutti i servizi down, riportando una collezione di elementi, denominata ServiceSet e costituita dallo Stato e dal DisplayName dei servizi trovati. Conoscendo namespace e classi da interrogare si possono cercare informazioni come “tutte le partizioni del disco rigido con meno del 10% di spazio” oppure “tutti i servizi che sono in modalità Avvio Automatico”. Il progetto è costituito da un solo form e da moduli che raccolgono funzioni utili all’applicazione stessa. DISKS Raccoglie le procedure e le funzioni utili al controllo dello spazio disco libero su tutte le partizioni del proprio disco. PING Implementa una semplice funzione che consente di “pingare” un host remoto. PORTE Implementa una procedura che, sfruttando l’SNMP Provider, consente il controllo delle porte TCP/IP e UDP aperte. All’avvio, l’evento Load del form principale compie una serie d’operazioni. Le più importanti riguardano il controllo sulla prima esecuzione dell’applicazione e l’impostazione delle variabili che rispecchiano le scelte precedentemente fatte dall’utente. Se l’applicazione è stata avviata per la prima volta, è richiamata una serie di procedure che si preoccupano di creare, all’interno del registro, una struttura che memorizza diverse impostazioni utili al corretto funzionamento dello stesso. La chiave che fa da root a queste informazioni è denominata MasterAgent sotto HKEY_CURRENT_USER\Software\VB and VBA ProgramSettings e al suo interno si trovano le seguenti sotto chiavi. Drives: per il controllo dello spazio disco sulle partizioni. Eventi: per il controllo di nuovi eventi nell’Event Viewer. TPSIT 5 434 um 298 di Install: per verificare se si tratta di primo avvio dell’applicazione. Interfacce: per il controllo d’interfacce di rete. Processi: per il controllo sulla creazione o eliminazione di processi. Servizi: per il controllo di servizi che vanno giù. Fatta eccezione per la sotto chiave Drives ed Install, le altre controllano se l’utente ha o meno attivato quel determinato tipo di controllo e lo attivano all’avvio. La chiave Drives, invece, conserva anche altre informazioni tra le quali la soglia di allarme sullo spazio disco rimasto libero, espresso come valore percentuale. Una volta determinate le impostazioni lette all’interno del registro, attraverso un’apposita procedura denominata ReadFromREG, l’evento Load del form principale inizia ad impostare le variabili e i controlli all’interno del form. Successivamente, valorizza un’array denominato WQLQuery che non fa altro che memorizzare le varie stringhe WQL che serviranno per i controlli, imposta i flag relativi all’attivazione delle procedure, lanciandole se necessario e visualizza il form. Essa può essere suddivisa in tre parti principali. 1. Una lista di pulsanti alla sinistra. 2. Una serie di pannelli sovrapposti nella parte centrale. 3. Una serie di LED nella parte inferiore sinistra. Ogni pulsante attiva un determinato pannello attraverso il metodo ZOrder che consente di visualizzare eventuali allarmi scattati, impostare o meno il controllo ed eliminare i log creati. Codice per controllare lo stato dell’interfaccia di rete Le procedure implementate sfruttano l’SNMP Provider per ottenere le informazioni che servono. La prima notazione risiede nel namespace specificato, ossia \ROOT\SNMP\LOCALHOST. È ovvio che, se si fosse caricato all’interno della struttura esposta da WMI il MIB di un qualunque altro device, si sarebbe specificato, al posto di localhost, il device considerato. Gli oggetti di tipo SwbemNamedValueSet sono importanti perché rappresentano delle collezioni di oggetti SWbemNamedValue attraverso i quali è possibile inviare ai provider che si stanno sfruttando, informazioni aggiuntive. In questo caso si sta sfruttando l’SNMP Provider e considerato che SNMP protegge i pacchetti inviati e ricevuti attraverso il nome della community di riferimento, è indispensabile specificare quest’informazione, impostandolo come ultimo parametro all’interno della chiamata ExecNotificationQueryAsync. Si è considerato come nome di community configurata all’interno del servizio SNMP della macchina locale la stringa Pippo. Ricordare che senza quest’informazione, il provider SNMP non è in grado di ritornare alcuna informazione e che la query WQL fallirà senza ritornare alcun dato. Codice per il controllo dei servizi È implementato grazie a due procedure: ServiceStatus e ServiceSink_OnObjectReady. L’utilizzo delle istruzioni seguenti. objWmiServices.Security_.ImpersonationLevel=3 objWmiServices.Security_.Privileges.AddAsString ("SeSecurityPrivilege") Consentono di definire le modalità di accesso, per motivi di sicurezza, alle informazioni che si stanno cercando di ottenere. Istruzioni come queste sono necessarie per impedire errori del tipo “Accesso negato”. La seconda particolarità è il modo con cui è avviata la query WQL. TPSIT 5 434 um 299 di SELECT * FROM __InstanceModificationEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_Service' AND (PreviousInstance.State <> TargetInstance.State) AND TargetInstance.State='Stopped' La classe __InstanceModificationEvent consente di tener traccia di eventuali modifiche avvenute all’interno di altre, per esempio si sta chiedendo di essere avvertiti quando avviene un cambiamento all’interno di dell’istanza Win32_Service. La clausola WITHIN costituisce l’intervallo di polling, mentre TargetInstance permette di specificare l’istanza della classe da controllare. La query nella stringa prima dell’AND, controlla qualunque cambiamento nell’istanza Win32_Service, impegnando le risorse di sistema. Tuttavia, per restringere il campo d’azione e, quindi, sacrificare meno risorse, si può migliorare la query attraverso la seconda parte della stringa che chiede di monitorare ogni cambiamento avvenuto all’interno di Win32_Service ma considerando solo i casi in cui ci sia un cambio di stato di un qualunque servizio e lo stato attuale sia pari a Stopped. Le due proprietà PreviousInstance e TargetInstance della __InstanceModificationEvent rappresentano lo stato precedente e attuale dell’istanza della classe, ora bisogna essere avvertiti quando la query impostata è soddisfatta, si usa l’oggetto: SWBemSink attraverso il quale è possibile definire le procedure che riceveranno tali notifiche. Attraverso WMI sono riconosciute le seguenti procedure predefinite: OnCompleted, OnObjectPut, OnObjectReady e OnProgress. Di queste è gestita solo la OnObjectReady che è chiamata in causa non appena l’evento, definito dalla query WQL, è pronto. Lanciare la query asincrona sfruttando il metodo ExecNotificationQueryAsync di objWmiServices specificando, però, come primo parametro, un oggetto SWBemSink attraverso i cui eventi si gestiranno gli allarmi. Nel momento in cui la query è soddisfatta ossia, un servizio è fermato, scatta l’evento ServiceSink_OnObjectReady che consentirà di gestire l’allarme. In particolare, il codice implementato agisce sul primo parametro passato alla procedura ossia StatusEvent che, nel caso specifico della classe Win32_Service, in corrispondenza dello stop del servizio Centro Sicurezza PC. Attraverso l’utilizzo della funzione Split, quindi, non si fa altro che spezzare nelle varie righe l’intero testo e prelevare solo le parti che interessano. Codice per il controllo dei processi e degli eventi Consente di controllare sia la creazione sia la distruzione di qualunque processo. La principale differenza con la precedente è l’utilizzo di due oggetti SWBemSink, ProcessCreateSink e ProcessDeleteSink e, di conseguenza, di due query WQL. Codice per il controllo dei dischi Le variabili per la gestione degli allarmi sullo spazio disco delle partizioni del sistema controllato sono inizializzate all’avvio dell’applicazione e servono a impostare correttamente i parametri di allarme sui dischi. La variabile ThresholdDiskFlag consente di tener traccia di cambiamenti alle soglie di allarme dei dischi permettendo di richiedere all’utente il salvataggio all’interno del registro. Le procedure ReadFromREG e CaricaDrives, consentono di leggere le impostazioni dal registro e preparare l’UI con tutti i parametri corretti, numero di partizioni, soglie di allarme. La procedura CaricaDrives ricava tutte le lettere di unità dei soli dischi rigidi oltre alla dimensione e allo spazio disco di ognuna di esse. Una volta ottenute queste informazioni, se le impostazioni dell’utente prevedono il TPSIT 5 434 um 300 di controllo dello spazio disco, è lanciata la procedura ControlloSpazioDisco, ad intervalli prestabiliti. TPSIT 5 434 um 301 di VOICE OVER IP SIP (SESSION Initiation Protocol) La società Skype, con sede in Lussemburgo, è stata fondata nel 2003, nel 2005 è stata acquisita da eBay, nel 2009 è stata acquistata da Silver Lake. Il prodotto denominato Skype è un S/W di comunicazione gratuito e liberamente scaricabile che permette di utilizzare i servizi di VoIP per la telefonia e per la videoconferenza. Nel 2011 è arrivato l’accordo con Microsoft che si è impegnata a mantenere gli investimenti per lo sviluppo del prodotto anche su piattaforme non Microsoft: il prodotto è disponibile anche per Linux e per Mac. Skype ha progettato un insieme di API per controllare il S/W Skype da una propria applicazione: SkypeKit. Invece, Skype Public API è un insieme di API che possono essere utilizzate per connettere il proprio H/W e il proprio S/W a Skype. Skype4COM è un ActiveX che espone le API di Skype in termini di oggetti, proprietà, comandi, eventi e notifiche e può essere utilizzato nei seguenti linguaggi, Visual Studio, Delphi, PHP, JavaScript e VBScript. In questo modo, invece di dover utilizzare direttamente la tecnologia COM, per connettere un’applicazione .NET, si può utilizzare questo ActiveX che è un “wrapper” alle Skype API. Esempio, aprire, in VBScript, una chiamata all’utente “echo123” e verificare che Skype sia installato e configurato correttamente. // creare un oggetto Skype: Set oSkype = WScript.CreateObject("Skype4COM.Skype", "Skype_") // effettuare una chiamata utilizzando l’oggetto Skype: Set oCall = oSkype.PlaceCall("echo123") // sospendere una chiamata: oCall.Hold // recuperare una chiamata sospesa: oCall.Resume // terminare una chiamata: oCall.Finish L’operazione preliminare che è necessaria, prima di poter utilizzare Skype, è l’installazione del S/W VoIP sul PC degli utenti e la registrazione di ogni utente al servizio gratuito di Skype. Comandi Per ogni attività è necessario inviare un comando in formato testuale a Skype: per aprire e chiudere una chiamata o una chat, per ottenere informazioni su un altro utente e per disconnettersi da Skype. A sua volta, Skype restituirà un’altra stringa di testo con le informazioni richieste o con altre informazioni di supporto, come il numero identificativo della chiamata. Questo <id> è importantissimo per riuscire a inviare dei comandi corretti a Skype nel corso di una chiamata. Il comando Skype è formato dalle seguenti parti. Una dichiarazione del comando formata da un simbolo di cancelletto (#) e da alcuni TPSIT 5 434 um 302 di caratteri in sequenza, a propria scelta, per esempio (#cmd). Una parola che indica un’azione: GET, SET, CALL, PING e OPEN. Altre parole che integrano il comando ed eventuali parametri, nome utente da chiamare, numero identificativo o <id> della chiamata. Per chiamare un utente di Skype si utilizza uno dei seguenti comandi. #cmd CALL echo123 #cmd CALL +393331234567 Nel primo caso si apre una chiamata vocale, eventualmente anche video. Nel secondo caso si chiama un numero di un cellulare, con prefisso internazionale, può avvenire solamente nel caso in cui si è abbonati a Skype. In caso contrario, chiamando un numero di telefono reale si ottiene un errore e si può solo chiamare utenti Skype, con il loro nome utente. All’avvio della chiamata, si riceve una sequenza di messaggi simile alla seguente. (Comando) #abc CALL echo123 #abc CALL 162221 STATUS UNPLACED CALL 162221 STATUS UNPLACED CALL 162221 STATUS ROUTING CALL 162221 STATUS RINGING CALL 162221 STATUS INPROGRESS CALL 162221 DURATION 0 CALL 162221 VIDEO_RECEIVE_STATUS AVAILABLE CALL 162221 DURATION 1 CALL 162221 DURATION 2 CALL 162221 DURATION 3 ... (Comando) #cmd SET CALL 162221 STATUS FINISHED #cmd CALL 162221 STATUS FINISHED CALL 162221 DURATION 12 CALL 162221 STATUS FINISHED I messaggi con il testo DURATION <n> indicano la durata della chiamata in corso e il parametro <n> è un numero che indica il numero di secondi attuale. Per chiudere la chiamata in corso, si deve utilizzare il comando seguente. #cmd SET CALL <id> STATUS FINISHED <id> è il parametro numerico che si è ricevuto quando è stata aperta la chiamata. Si deve utilizzare lo stesso numero anche per chiudere la chiamata in corso. Un comando che si può inviare per verificare se si è connessi al servizio Skype è il seguente. #cmd PING Skype risponde con il comando seguente. #cmd PONG Confermando la ricezione del comando. Per verificare la versione di Skype attualmente installata, si utilizza il comando seguente. TPSIT 5 434 um 303 di #cmd GET SKYPEVERSION Si ottiene in risposta il messaggio seguente. #cmd SKYPEVERSION 5.1.0.112 Per verificare lo stato come utente Skype, si utilizza il comando seguente. #cmd GET USERSTATUS Si riceve un risultato, dove <stato> è lo stato Skype corrente. #cmd USERSTATUS <stato> Il valore di <stato> può essere uno dei seguenti: ONLINE (in linea), AWAY (assente), DND (occupato), INVISIBLE (invisibile) oppure OFFLINE (non in linea). Per cambiare stato, si utilizza il comando seguente. #cmd SET USERSTATUS <stato> Per verificare lo stato di un altro utente Skype, si utilizza il comando seguente. #cmd GET USER pippo ONLINESTATUS PROGRAMMAZIONE Si utilizzano le API di Skype dall’interno di un’applicazione Visual Basic per vedere quali sono le possibilità di utilizzo che sono messe a disposizione. L’applicazione deve gestire una chiamata in audio/video sul servizio Skype, senza dover andare appositamente ad aprire la finestra di Skype. Dopo aver creato il tipo di applicazione e dopo aver aggiunto il progetto SkypeControl alla soluzione, inserire un nuovo form di nome ChiamaOperatore. Il form principale contiene solamente un pulsante per l’apertura del form secondario. La soluzione è formata da due progetti. SkypeControl, crea una libreria di classi che funziona da wrapper delle funzionalità offerte da Skype. VB_Skype è l’applicazione che utilizza la DLL, apre direttamente una chiamata Skype, TPSIT 5 434 um 304 di senza che l’utente debba preoccuparsi di aprire la finestra principale di Skype, cercare l’utente, in pratica il supporto tecnico del S/W ed effettuare la chiamata. L’utente da chiamare potrà essere impostato in modo predefinito e la chiamata potrà essere effettuata in automatico: basterà premere un pulsante nell’applicazione. File MAIN.VB Public Class Main Private Sub cmdChiamaOperatore_Click(sender As System.Object, e As System.EventArgs) Handles cmdChiamaOperatore.Click ChiamaOperatore.Show() End Sub End Class File CHIAMAOPERATORE.VB Il form contiene quattro controlli: una PictureBox, due Button e una TextBox. La casella di testo serve per visualizzare un log delle operazioni svolte all’utente. All’apertura del form si deve creare una connessione all’applicazione di Skype, per gestire le sue funzionalità direttamente dal form. Il gestore dell’evento Load contiene due istruzioni simili tra loro che creano un gestore degli eventi che è agganciato rispettivamente agli eventi pubblici SkypeAttach e SkypeResponse, esposti dall’oggetto aSkype. SkypeAttach permette di aprire la connessione a Skype dall’applicazione, mentre SkypeResponse permette di ottenere uno o più messaggi da Skype. Il tipo di messaggi che si ricevono da Skype sono di tutti i tipi. Infatti, nella finestra di log sono elencati dei messaggi che rispecchiano tutto quello che avviene a Skype, compresa la modifica dell’aspetto della sua finestra. WINDOWSTATE MINIMIZED. WINDOWSTATE MAXIMIZED. WINDOWSTATE NORMAL. Quello che conta è che, dopo aver eseguito le due istruzioni inserite nel gestore dell’evento Load, si può gestire Skype direttamente dall’applicazione. Al momento dell’avvio della connessione a Skype, si ottengono due messaggi consecutivi. 1. PendingAuthorizaion. 2. Success indica che tutto è andato per il verso giusto. Nel codice del gestore dell’evento SkypeResponse: le prime due istruzioni prendono il messaggio ricevuto e lo accodano alla casella di “log”. Nel blocco If ... End If si verifica se il messaggio contiene contemporaneamente le stringhe CALL e STATUS INPROGRESS: se la risposta è positiva, significa che si è chiesto a Skype di aprire una chiamata ad un utente Skype o ad un numero telefonico. In tal caso si eliminano entrambe le stringhe dal messaggio originario, compreso lo spazio iniziale o finale e si memorizza la stringa rimanente che è costituita dalla stringa composta solo da cifre numeriche, nella variabile id. A questo punto si è preso il numero di riferimento della chiamata in corso e lo si deve memorizzare per poterlo utilizzare al momento opportuno: per chiudere la conversazione in corso. Gestore dell’evento Click del pulsante cmdChiama. La prima istruzione chiama il metodo Conect dell’oggetto aSkype, per agganciare l’applicazione di Skype, subito dopo si definisce il comando per chiamare un utente. Chiamando il metodo Command dell’oggetto aSkype e passando a questo metodo la stringa di comando, si ottiene l’avvio effettivo della chiamata vocale e/o video. Gestore dell’evento Click del pulsante cmdRiaggancia. Per chiudere la conversazione in corso, si deve inviare un altro comando testuale. TPSIT 5 434 um 305 di SET CALL <id> STATUS FINISHED Nel comando s’inserisce il valore della variabile <id> salvata nel momento di apertura della chiamata, una volta inviato il comando di termine conversazione, si può disconnettersi da Skype, utilizzando il metodo Disconnect dell’oggetto aSkype. ' importare il namespace SkypeControl per utilizzare le funzionalità offerte dalla DLL creata ' con il progetto Visual C# Imports SkypeControl Partial Public Class ChiamaOperatore ' inserisce un ritorno a capo nel testo che è visualizzato nella casella di testo Private nl As String = Environment.NewLine ' serve a memorizzare l’identificativo della chiamata attuale Private id As String = "" ' comando da inviare a Skype Dim comando As String = "" ' la classe SkypeProxy è definita in SkypeControl nel file SkypeProxy.cs Dim aSkype As SkypeProxy = New SkypeProxy() Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load ' connessione all’applicazione di Skype AddHandler aSkype.SkypeAttach, AddressOf aSkype_SkypeAttach AddHandler aSkype.SkypeResponse, AddressOf aSkype_SkypeResponse End Sub Public Sub aSkype_SkypeAttach(theSender As Object, theEventArgs As SkypeAttachEventArgs) ' stato della connessione TextBox1.AppendText(String.Format("Attach: {0}", theEventArgs.AttachStatus) & nl) End Sub Public Sub aSkype_SkypeResponse(theSender As Object, theEventArgs As SkypeResponseEventArgs) Dim risposta As String = theEventArgs.Response TextBox1.AppendText(String.Format("Response: {0}", risposta) & nl) If risposta.Contains("CALL") And risposta.Contains("STATUS INPROGRESS") Then risposta = risposta.Replace("CALL ", ""). Replace(" STATUS INPROGRESS", "") id = risposta End If End Sub Private Sub cmdChiama_Click(sender As System.Object, e As System.EventArgs) Handles cmdChiama.Click aSkype.Conect() comando = "#cmd CALL echo123" TextBox1.AppendText(comando & nl) aSkype.Command(comando) End Sub Private Sub cmdRiaggancia_Click(sender As System.Object, e As System.EventArgs) Handles cmdRiaggancia.Click comando = String.Format("#cmd SET CALL {0} STATUS FINISHED", id) TPSIT 5 434 um 306 di TextBox1.AppendText(comando & nl) aSkype.Command(comando) aSkype.Disconnect() TextBox1.AppendText("Disconnesso" & nl) End Sub End Class JAVA Considerando i diversi dispositivi che compongono la struttura di una chiamata VoIP, Java ha sviluppato diverse tipologie di API chiamate JAIN (Java APIs for Integrated Networks) sono sviluppati da JCP (Java Community Process) e sono classificati in progetti JSR (Java Specification Request), i progetti sono i seguenti. JSR 180 Sip per J2ME (Java Micro Edition), per lo sviluppo di applicazioni mobile. JSR 32 Jain-Sip per sviluppo su piattaforma J2SE (Java2 Platform, Standard Edition). JSR 116 Sip-Servlet, per piattaforma J2EE (Java Enterprise Edition). In realtà, a queste API complete, c’è anche una libreria aggiuntiva Jain-Sip Lite con delle classi di più alto livello che servono per astrarre dalle difficoltà e dai dettagli implementativi e consentono di sviluppare applicazioni con estrema facilità e velocità. Permette di sviluppare applicazioni di tipo UA (User Agent), in pratica terminali S/W che si connettono a reti SIP. Possono essere viste come involucro, wrapper, per accedere alle librerie Jain-Sip che sono complete e richiedono un programmatore che conosca il protocollo SIP. Per avere, invece, maggior controllo su cosa accade e per guidare il protocollo SIP, si deve fare riferimento alle altre API. SIP per J2ME definisce un set d’interfacce per dispositivi cellulari e PDA (Personal Digital Assistant) che trattano la comunicazione SIP con factory comuni alle SocketConnection e HttpConnection per semplificarne l’uso, in questo modo, infatti, si ha la gestione integrata degli header, response automatizzate e supporto ai dialoghi SIP. Sip-Servlet è un insieme di API basate sulle esistenti Servlet e funziona in maniera analoga, come le Servlet, devono girare in un container adatto, Servlet Container, ad esempio Tomcat. Le Sip-Servlet hanno anche l’obiettivo di rendere standard alcuni aspetti del container: l’associazione delle richieste SIP con le Servlet, il modello di sicurezza, il Servlet Deployment Descriptor, documento che accompagna la Servlet e che contiene le TPSIT 5 434 um 307 di informazioni per il container su come dev’essere gestita la Servlet e il formato del file di distribuzione analogo al JAR, simile al formato WAR (Web Archives Repository) utilizzato dalle Servlet HTTP. In questo modo si può rispondere alle richieste SIP con interfacce semplici per realizzare sia UA sia proxy. I dettagli di basso livello del protocollo, invece, sono demandate al Servlet Container. È, infatti, il container che si occuperà di gestire le ritrasmissioni dei messaggi in caso di funzionamento come proxy, di scegliere la risposta migliore tra più risposte a seguito di richieste concorrenti, generazioni di numeri di sequenza, identificativi di chiamata e gestione degli header. La differenza con le altre API è l’uso necessario del Servlet Container e del proxy SIP. Jain-Sip è il primo progetto proposto per la standardizzazione Java del protocollo SIP. Le interfacce sviluppate consentono la realizzazione di tutti gli attori coinvolti in una rete SIP: UAC (User Agent Client), UAS (User Agent Server), proxy, registrar server e redirect server. Si basano su eventi e hanno vari oggetti factory per la rapida creazione di request, response e header. Eclipse IDE for Java Un’applicazione che voglia realizzare il VoIP dovrebbe basarsi sul modello a eventi. In questo modo si definiscono un Listener e un Provider che scambiano, appunto, eventi. In particolare il Listener si troverà in ascolto degli eventi generati dal Provider. Gli eventi incapsulano Request e Response e il SipListener rappresenta colui che ascolta e consuma l’evento. L’evento è un’entità astratta: quello che c’è dentro l’evento è il messaggio SIP. Il SipProvider è il fornitore dell’evento che riceve il messaggio dalla rete e lo passa all’applicazione sempre sotto forma di evento. Questo modello serve per capire come sono strutturati le interfacce delle API. Indipendentemente dal tipo di API che si usano, infatti, gli obiettivi e i servizi offerti da Java sono: fornire metodi per formattare il messaggio in formato SIP; inviare e ricevere i messaggi; scompattare i messaggi in arrivo; gestire la transazione con timeout, stati e ciclo di vita. Resta all’applicazione il compito d’implementare il SipListener per interagire con lo stack SIP; di registrarsi con il SipProvider per tutti i messaggi e gestire la transazione in modalità sincrona o asincrona; accedere agli oggetti dello stack e ricevere i messaggi dallo stack sotto forma di eventi. Il flusso di un’applicazione dovrebbe essere come quello in figura nella quale sono coinvolte le classi realizzate in Jain-Sip. TPSIT 5 434 um 308 di L’interfaccia SipStack serve per gestire i Provider SIP, può registrare molti listener, è instanziato dalla SipFactory con un insieme di parametri, definisce le proprietà di ritrasmissione, le informazioni sui router ed eventuali nuovi metodi realizzati. In particolare, coordinando la ritrasmissione, solleva il programmatore da una notevole complessità di gestione. I parametri da passare per il corretto funzionamento del SipStack sono il suo indirizzo IP, il nome, il path del router per l’instradamento dei messaggi prima che il dialogo sia iniziato e il filtro per la ritrasmissione. L’interfaccia SipProvider ha invece il compito di ricevere gli eventi e notificarli al SipListener registrato e gestisce le transazioni con scambio di request e response. L’intrerfaccia SipListener è creata e associata ad un unico SipStack, e tutti i SipProvider associati al SipStack hanno il medesimo SipListener; gestisce i timeout della transazione e ritrasmette gli eventi. Queste interfacce si trovano nel package generale javax.sip di Jain-Sip che definisce l’architettura generale, le transazioni e gli eventi. Gli altri package sono relativi al messaggio SIP. Il package address contiene un wrapper per le URI, definendo interfacce per URI di tipo Sip e Tel. Gli indirizzi di un utente, infatti, possono essere ad esempio sip:[email protected], simili a quelli di posta elettronica, oppure come numero di telefono tel:123456. Il package message definisce le interfacce per i messaggi request e response. I messaggi request contengono il tipo di richiesta e l’URI; quelli di response il codice dello stato della transazione. Il body di un messaggio può contenere anche la descrizione della sessione in formato String o Object, come specificato nel SDP (Session Description Protocol). I tipi di messaggio della request seguono il protocollo SIP e, a livello di codice, sono delle costanti. Il package header, naturalmente, contiene tutti gli header supportati e loro possibili estensioni. Sono simili agli header HTTP sia per sintassi sia per significato. In questo package si è deciso di sviluppare ogni possibile header invece di uno solo generico che gestisse tutte le informazioni, per rendere più esplicita la gestione dei molteplici header definiti negli standard. TPSIT 5 434 um 309 di Per poter scegliere, di volta in volta, tra le diverse alternative, ci sono quattro classi che implementano il pattern factory. 1. AddressFactory. 2. HeaderFactory. 3. MessageFactory 4. SipFactory. Per creare indirizzi URI sip e tel, gli header, i messaggi con request e response e gli oggetti Stack. I vantaggi di usare questa suddivisione sono la facilità di aggiungere nuovi metodi e header. Un’applicazione che voglia far comunicare due utenti si chiama 3PCC (Third Party Call Control), il dialogo è un’associazione P2P, punto punto, tra due utenti che stabiliscono una connessione SIP e rappresenta, in maniera astratta, il contesto nel quale interpretare i messaggi SIP. Si può osservare che tra i due utenti si stabilisce una connessione RTP (Real-time Transport Protocol). Nel codice c’è l’inizializzazione dello stack, usando la SipFactory e impostando i parametri dello stack. try { Properties properties = new Properties(); properties.setProperty("javax.sip.IP_ADDRESS","129.8.9.78"); // inizializzazione altre proprietà } try { sipStack = sipFactory.createSipStack(properties); } catch(SipException e) { System.exit(-1); } L’inizializzazione della request specificando, ad esempio, l’URI e il tipo di messaggio, INVITE. try { SipURI requestURI = addressFactory.createSipURI (toUser, toSipAddress); TPSIT 5 434 um 310 di // creazione altri header Request request = messageFactory.createRequest (requestURI, Request.INVITE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); } L’invio del messaggio di request usando una ClientTransaction. try { // creazione ClientTransaction ClientTransaction inviteTid = sipProvider.getNewClientTransaction(request); // invio request sipProvider.sendRequest(inviteTid,request); } La gestione dei messaggi come eventi usando una ServerTransaction. try { public void processRequest(RequestEvent requestEvent) { Request request = requestReceivedEvent.getRequest(); ServerTransaction st = requestEvent.getTransaction(); // gestione specifica della request } La voce non può essere trattata come i dati e allora serve un protocollo che garantisca una certa priorità ai pacchetti vocali. Per realizzare il protocollo RTP, Java si appoggia a JMF (Java Media Framework) che fornisce un’architettura unificata per la cattura, emissione e manipolazione di dati multimediali. JMF permette la riproduzione e trasmissione di stream RTP attraverso l’uso di classi e interfacce definite in javax.media.rtp, javax.media.rtp.event e javax.media.rtp.rtcp che sfruttano il modello a eventi. SIP si occupa di trovare e stabilire una connessione tra gli utenti, specificando le caratteristiche della comunicazione grazie al SDP presente nel body del messaggio. RTP permette d’inviare lo stream dei pacchetti della voce e RTPC ne controlla la qualità. JMF ha il compito di definire la sorgente dalla quale prelevare la voce, convertirla da formato analogico a digitale, per poterla avere come flusso di byte e stabilire il protocollo RTP verso il destinatario. JMF necessita di tutti i dati relativi all’indirizzo dell’utente al quale inviare lo stream della voce che sono contenuti nel body del messaggio SIP, conformi al SDP. Sviluppare un’applicazione vuol dire creare un oggetto che permetta, prima di stabilire una connessione SIP tra utenti e prendere i dati relativi ad un tipo di streaming usando JainSip e poi chiamare JMF per iniziare il protocollo RTP creando così la sequenza dei pacchetti voce su Internet. Si deve quindi costruire un oggetto con le API JMF cha cattura la voce, la codifica in formato digitale opportuno, campionandola ad esempio alla frquenza di 44 KHz e la associa a una lista di dispositivi audio. Vector audioDevices=CaptureDeviceManager.getDeviceList(new AudioFormat(AudioFormat.LINEAR, 44100, 16, 2)); A questo punto si apre il canale di stream per la voce usando i parametri SDP come TPSIT 5 434 um 311 di indirizzo e porta del destinatario. public void openVoiceStream (String sdpData) throws MediaException {… SessionDescription sessionDescription=sdpFactory.createSessionDescription(sdpData); Connection sessionConnection = sessionDescription.getConnection(); // avvio oggetti per inviare e ricevere lo stream voce } Per scambiare il flusso voce si usano le classi JMF PushBufferStream e PullBufferStream per sfruttare le capacità offerte dal Buffer, PushBufferDataSource per catturare il flusso dati e RTPManager e SendStream per l’invio. Quindi le API JMF si usano per creare un’applicazione client/server che apre una connessione e invia uno stream. Emulare un telefono, è un’applet Java che stabilisce connessioni con altri utenti e scambia flussi di pacchetti voce. Il package principale è denominato, applet.phone. Un livello al di sotto della gerarchia principale ci saranno due cartelle, UA e MEDIA che costituiranno il nucleo dell’applicazione. All’interno di ua si trovano le classi per la gestione del telefono e in media quelle per il flusso dei pacchetti voce. Per stabilire una connessione con un utente, l’applicazione usa il protocollo SIP. La classe principale sarà MessengerManager che ha lo scopo di regolare tutte le chiamate e il loro stato. Entrando nei dettagli del costruttore della classe, è invocato il gestore delle chiamate, inizializzata la lista dei contatti e impostato l’indirizzo SIP URI dell’utente. Il gestore delle chiamate: CallManager che si trova nel package applet.phone.call, ha il compito di regolare i vari tipi di chiamata. Infatti, in questo caso, si tratta di una chiamata audio ma è possibile anche semplicemente avviare una chat testuale. Se si vuole ampliare lo sviluppo anche alla video chiamata, si dovranno aggiungere nel CallManager la gestione e l’oggetto specifico. // classe MessangerManager public MessengerManager(Configuration configuration,NISTMessengerGUI appletHandle) { MediaManager.detectSupportedCodecs(); contactList = new Vector(); callManager = new CallManager(); // create a new instance of the sip Listener messageListener = new MessageListener(this, configuration,appletHandle); messageListener.start(); String userURI = messageListener.getConfiguration().userURI; try { // create the SIP URI for the user URI String user = userURI.substring(0,userURI.indexOf("@")); String host = userURI.substring(userURI.indexOf("@") + 1, userURI.length()); userSipURI = MessageListener.addressFactory.createSipURI(user, host); } catch (ParseException ex) { System.out.println(userURI + " is not a legal SIP uri! " + ex); } } TPSIT 5 434 um 312 di Da sottolineare anche la creazione del MessageListener che invia e si pone in ascolto dei messaggi SIP. Come tutti i protocolli che si basano sui messaggi sincroni tra utenti, anche SIP si basa sull’invio di richieste e risposte, per esempio questo avviene quando un utente manda un messaggio, INVITE, al secondo utente al quale inizia a squillare il telefono. Appena l’utente chiamato decide di rispondere manderà un OK al chiamante che risponderà a sua volta con un ACK, a questo punto può iniziare la sessione voce. A livello di codice questa sequenza si traduce in una serie di chiamate per creare e ricevere i messaggi SIP e poi il controllo passa al gestore degli eventi multimediali. La prima parte da considerare è quindi l’invio dei messaggi SIP tra gli utenti. Per mandare la richiesta di INVITE si usa il metodo SendInvite che prende in ingresso le informazioni sul destinatario della telefonata calee-URI e sul tipo di sessione multimediale da scambiare, sdpBody, ed esegue la creazione della richiesta impostando header e body del messaggio e gestendo la transazione con l’utente. // classe MessengerManager private void sendInvite(String calleeURI, String sdpBody) { // creazione messaggio richiesta INVITE Request invite = createRequest(Request.INVITE,contactURI, userSipURI); // creazione header e body del messaggio acceptHeader=MessageListener.headerFactory.createAcceptHeader("audio","x-gsm"); // gestione transazione ClientTransaction inviteTransaction = null; inviteTransaction = messageListener.sipProvider.getNewClientTransaction(invite); // invio richiesta inviteTransaction.sendRequest(); } TPSIT 5 434 um 313 di L’oggetto ClientTransaction appartiene alle API standard contenute nella libreria Jain-Sip. Da questo punto in poi si occuperanno loro di gestire la richiesta. Quando il chiamato riceve la chiamata e vuole rispondere, devono essere invocati i metodi per la gestione della sessione multimediale, il principale è processInviteOK che è contenuto nella classe MessageProcessor ed è invocato dal MessageListener. // classe MessageProcessor public void processInviteOK(ClientTransaction clientTransaction,Response inviteOK) { CallManager callManager=messageListener.sipMeetingManager.getCallManager(); // ricerca della chiamata Audio AudioCall call = callManager.findAudioCall(callee); // invio ACK try { TPSIT 5 434 um 314 di Request ack=(Request)clientTransaction.getDialog().createRequest(Request.ACK); clientTransaction.getDialog().sendAck(ack); if (type.equals("application") && subType.equals("sdp")) { // inizio sessione multimediale MediaManager mediaManager =call.getMediaManager(); call.setVoiceMesaging(false); mediaManager.prepareMediaSession(new String(inviteOK.getRawContent())); mediaManager.startMediaSession(true); } } } // gestione degli stati della chiamata call.setStatus(AudioCall.IN_A_CALL); messageListener.sipMeetingManager.notifyObserversNewCallStatus(call); } Come per la gestione dei messaggi esiste il metodo MessageManager, così esistono i gestori per le chiamate CallManager e per la sessione multimediale MediaManager. L’interfaccia Call contiene gli elementi che devono essere implementati nella classe, proprio come fa l’oggetto AudioCall. In altri messaggi del protocollo, un ruolo di spicco assume anche il body Sdp che serve per far capire agli utenti le informazioni multimediali da scambiare, come il tipo di codifica supportata e su quale porta viaggerà la voce. Dopo aver permesso agli utenti di scambiarsi informazioni sulla modalità con la quale scambiarsi i pacchetti voce, i due utenti possono finalmente iniziare la conversazione. Per avviare uno stream con i pacchetti voce sono stati scelti i protocolli RTP e RTCP che, insieme, riescono a gestire contenuti multimediali impostando velocità di trasmissione e qualità del servizio, la chiamata che avvia questo processo è startMediaSession ed è gestita dal MediaManager. Le classi che realizzano il protocollo sono in un package a parte applet.phone.media. L’oggetto MediaManager si occupa di gestire la sessione multimediale aprendo e chiudendo gli oggetti receiver e transmitter che gestiscono il flusso voce. La chiamata per avviare questi oggetti avviene con il metodo startMediaSession. // classe MediaManager public void startMediaSession(boolean transmitFirst) { if (!started) { startReceiving(); startTransmitting(); } } In questo modo si arriva a instanziare i vari oggetti, come Transmit che serve per realizzare il protocollo RTP sfruttando JMF che a sua volta, usa gli oggetti JMF come RTPManager, MediaLocator, SessionAddress e Socket per il flusso relativo ai protocolli RTP e RTCP; per gestire il flusso voce serve a catturarla e poi trasmetterla. Per il primo compito, la classe Trasmit usa il metodo createProcessor che prende la sorgente dati che si desidera utilizzare ed è gia predisposto per audio, video, o entrambi per il secondo, usa createTrasmitter. // classe Transmit private String createProcessor() TPSIT 5 434 um 315 di {DataSource audioDS=null; DataSource videoDS=null; DataSource mergeDS=null; StateListener stateListener=new StateListener(); // creazione del DataSource if (audioLocator != null) { // creazione DataSource tipo audio audioDS= javax.media.Manager.createDataSource(audioLocator); } if(videoDS!=null && audioDS!=null) { try { // creazione merge audio-video mergeDS = javax.media.Manager.createMergingDataSource(new DataSource [] {audioDS, videoDS}); } catch (Exception e) { System.out.println("Non si connettte ad audio o device di cattura video"); } // configurazione del processor boolean result = stateListener.waitForState(processor, Processor.Configured); // tracce del processor TrackControl [] tracks = processor.getTrackControls(); // impostazione dei parametri delle tracce conformi a RTP // output data source del processor dataOutput = processor.getDataOutput(); } Anche gli oggetti di tipo TrackControl fanno parte di JMF e sono gestiti dal framework. Il metodo createTransmitter si appoggia a RTPManager per creare sessioni per ogni traccia del processor e gestisce il flusso della voce a livello di buffer. // classe Transmit private String createTransmitter(String localIpAddress) {PushBufferDataSource pbds =(PushBufferDataSource)dataOutput; PushBufferStream pbss[] = pbds.getStreams(); rtpMgrs = new RTPManager[pbss.length]; SessionAddress localAddr, destAddr; InetAddress ipAddr; SendStream sendStream; SourceDescription srcDesList[]; … for (int i = 0; i < pbss.length; i++) { try { rtpMgrs[i] = RTPManager.newInstance(); // impostazione parametri connessione con host remoto per RTP while(!connected) { socketRTPTransmit = new Socket(ipAddr,destPort); connected=true; } // connessione con host remoto per RTCP while(!connected) { socketRTCPTransmit = new Socket(ipAddr, rtcpDestPort); connected=true; TPSIT 5 434 um 316 di } rtpMgrs[i].initialize(new TCPSendAdapter(socketRTPTransmit,socketRTCPTransmit)); } // inizio trasmissione con host remoto sendStream = rtpMgrs[i].createSendStream(dataOutput, i); sendStream.start(); } catch (Exception e) { e.printStackTrace(); } Una volta aperti i socket, avviene l’invio dei dati voce per mezzo dell’oggetto SendStream anch’esso appartenente a JMF. RTPManager gestisce i protocolli RTP e RTCP e c’è anche la gestione di TCP e UDP a seconda delle caratteristiche del protocollo di trasporto che si desidera impostare. In modo del tutto analogo al trasmettitore lavora l’oggetto Receiver che si occupa di ricevere lo stream voce. JMF viene incontro anche alla modalità di cattura delle sorgenti voce. Infatti, nella classe MediaManager, il metodo detectSupportedCodecs serve proprio ad ottenere una lista di tutti i dispositivi audio e video disponibili con le loro caratteristiche. TPSIT 5 434 um 317 di TV OVER IP INTRODUZIONE È la trasmissione di audio e video su protocollo TCP/IP, per proiettare contenuti multimediali su Internet. Tv Over IP significa visualizzare sul proprio televisore di casa una trasmissione televisiva, scegliendola quando e come si vuole da una televisione basata su Internet. Protocollo MMS (Microsoft Media Services) È un protocollo a livello applicazione, utilizzato per lo streaming audio/video. Usa il protocollo UDP ma non è raro il suo utilizzo sopra TCP. La porta MMS di default è 1755. Protocollo RTSP (Real Time Streaming Protocol) È stato sviluppato da RealNetworks, Netscape Communications e Columbia University. Ottimizza il flusso di dati, è stato progettato per essere simile a HTTP. Il protocollo RTSP non si occupa direttamente della trasmissione dello streaming. La maggior parte dei server utilizza RTP per il trasporto del flusso multimediale ma alcuni produttori utilizzano protocolli di trasporto proprietari. La porta RSTP di default è 554. Protocollo RTP È un protocollo del livello applicazioni che permette la distribuzione di servizi che hanno bisogno di trasferimento in tempo reale, come l'interattività audio e video. WMS (WINDOWS MEDIA SERVICES) È un servizio installabile su Windows, il cui complemento è il Windows Media Player. Per visualizzare contenuti video provenienti da un server di streaming WMS si usa Windows Media Player, basta fare clic su File/Apri URL… (CTRL+U) e digitare l’indirizzo seguente per connettersi al server in questione e poter visualizzare il file. mms://indirizzodelserver/indirizzodipubblicazione/<opzionale indirizzo del video> Un client che apre un file tramite mms:// prova prima a utilizzare RTSP su UDP e se fallisce, prova RTSP su TCP. Dopo aver fallito con RTSP, prova MMS su UDP, quindi MMS su TCP. Se MMS fallisce prova una versione modificata di HTTP su TCP. Qual è il vantaggio di utilizzare un servizio di streaming rispetto a consentire ad un utente di scaricare un file per visualizzarlo in un secondo momento? Prima di tutto un server di streaming consente la diretta, inoltre nel caso in cui si volessero mettere a disposizione contenuti registrati in precedenza, l’utente sarebbe sempre costretto a visualizzarli sul sito. In pratica, un video o un file MP3 (Motion Picture Expert Group-1/2 Audio Layer 3) non può essere copiato, può essere solo riprodotto. È possibile anche proteggere i contenuti con una password e fornire l’accesso solo agli utenti registrati al servizio. Infine, un server di streaming consente di gestire la pubblicità, il palinsesto e la banda. TPSIT 5 434 um 318 di TPSIT 5 434 um 319 di WME (WINDOWS MEDIA ENCODER) WME consente di catturare audio e video da una fonte esterna, comprimere i dati in tempo reale e inviarli ad un WMS che si occuperà di renderli disponibili a tutti i client che ne vorranno fare uso. Per esempio, se al PC è collegata una telecamera, WME catturerà il segnale e lo invierà verso il WMS. A questo punto un qualunque utente dotato di Windows Media Player potrà connettersi al servizio e visualizzare in tempo reale i dati trasmessi dalla telecamera. Due tipi di trasmissione. 1. Live streaming: diretta. 2. TV On Demand: trasmissione preregistrata. Singolo filmato S’intende quello che comunemente è indicato come servizio, per esempio un filmato singolo di un telegiornale. Trasmissione televisiva S’intende un insieme di servizi, per esempio un telegiornale. Emittente televisiva S’intende un soggetto che propone una serie di trasmissioni televisive organizzate in palinsesti, per esempio la RAI (RAdiotelevisione Italiana). S’indica una trasmissione televisiva come PP (Publishing Points), ciascuno sarà composto da uno o più filmati, i PP potranno trasmettere due modalità. 1. Broadcasting: s’intende che il video sarà trasmesso sequenzialmente e tutti i client che si connetteranno per guardarlo lo visualizzeranno in maniera sincrona, in altre parole ciascun client vedrà scorrere le stesse immagini nello stesso tempo, utilizzata nelle dirette. 2. Unicasting: consente a ciascun client di avviare la ripresa del video in modalità asincrona, scegliendo quando e come avviare il video indipendentemente dagli altri, utilizzata per la TV On Demand. Ciascun PP può avere delle caratteristiche, per esempio una sigla iniziale e una sigla finale che devono essere sempre visualizzate qualunque sia il servizio che è richiesto dall’utente. Oppure pubblicità agganciata ad un particolare servizio sotto la forma di banner. Di tutte queste operazioni si occupa il WMS. PROGRAMMAZIONE È disponibile il WME SDK che permette d’interagire con gli strumenti dell’encoder. using System.Windows.Forms; using WMEncoderLib; public partial class Form1 : Form { WMEncoder Encoder; IWMEncSourceGroupCollection SrcGrpColl; IWMEncSourceGroup SrcGrp; IWMEncSource SrcAud; IWMEncVideoSource SrcVid; IWMEncProfileCollection ProColl; IWMEncProfile Pro; IWMEncPushDistribution PushDist; TPSIT 5 434 um 320 di string strServerName,strPubPoint,strPubTemplate,MyNSCFile,MyNSCURL,MyASXFile; public Form1() { InitializeComponent(); } private void Form1_Load_1(object sender, System.EventArgs e) { // crea l'oggetto WMencoder Encoder = new WMEncoder(); } private void Button1_Click(System.Object sender, System.EventArgs e) { if (OpenFileDialog1.ShowDialog() == Windows.Forms.DialogResult.OK) ListBox1.Items.Add(OpenFileDialog1.FileName); } private void Button2_Click(System.Object sender, System.EventArgs e) { SrcGrpColl = Encoder.SourceGroupCollection; // crea un gruppo chiamato SG_1. SrcGrp = SrcGrpColl.Add("SG_1"); // specifica la sorgente // commentata la linea che recupera una sorgente audio //SrcAud = SrcGrp.AddSource(WMENC_SOURCE_TYPEWMENC_AUDIO) SrcVid = SrcGrp.AddSource(WMENC_SOURCE_TYPE.WMENC_VIDEO); // specifica il path del file sorgente, lo recuperiamo dalla listbox //SrcAud.SetInput("c:\audio.wav") SrcVid.SetInput(ListBox1.Items(0).ToString()); // se vogliamo prendere come sorgente un device //SrcAud.SetInput("device://default_audio_device") //SrcVid.SetInput("device://default_video_device") // Stabilice il profilo da utilizzare per la codifica. ProColl = Encoder.ProfileCollection; Pro = ProColl.Item(2); SrcGrp.Profile = Pro; // definisce l'encoding in broadcast PushDist = Encoder.Broadcast; // definisce il nome del PP e la porta strServerName = "localhost:8888"; strPubPoint = "publicationpoint"; // rimuove il PP quando la trasmissione è finita PushDist.AutoRemovePublishingPoint = true; PushDist.ServerName = strServerName; PushDist.PublishingPoint = strPubPoint; Encoder.PrepareToEncode(true); // start encoding. Encoder.Start(); MsgBox("Click OK to stop broadcasting."); } } TPSIT 5 434 um 321 di EMULE INTRODUZIONE La tecnologia che si nasconde dietro al file sharing, P2P, è raffinata quanto semplice. La storia di eMule inizia il 13 maggio 2002, quando Hendrik Breitkreuz, un programmatore tedesco, conosciuto come Merkur, insoddisfatto del client originale per la rete eDonkey2000, inizia a lavorare al progetto. eMule consente di connettersi sia alla rete eDonkey sia alla rete Kad. Fra le caratteristiche che lo hanno portato ad essere uno dei client P2P maggiormente utilizzati, vi sono lo scambio diretto fra le sorgenti localizzate nei PC client, recupero dei download interrotti o corrotti, l’utilizzo di un sistema a crediti che incoraggia la condivisione e l’upload, la trasmissione in formato compresso ZLIB, per risparmiare larghezza di banda. eMule è stato sviluppato e continua ad esserlo, in Visual C++ è rilasciato sotto licenza GPL (GNU General Public License), ed è disponibile, tramite progetti paralleli da esso derivati, anche sotto altre piattaforme, per esempio esiste il client xMule per Linux. Sempre più lunga anche la lista di altri progetti, detti Mod che apportano modifiche all’originale: eMule Extreme, eMule plus, aMule. In inglese Donkey significa “asino”, quindi Merkur scelse un nome simile: Mule, significa “mulo”, la e iniziale di eDonkey ed eMule indica il termine inglese electronic, letteralmente elettronico. ARCHITETTURA Una rete eMule è costituita da centinaia di macchine che agiscono da server eMule e milioni di PC nelle case degli utenti che svolgono il ruolo di client eMule. I client devono come primo passo connettersi ad un server, per ottenere accesso alla rete eMule. La connessione al server rimarrà attiva per tutto il tempo in cui il client resterà connesso alla rete eMule. Un server eMule esegue compiti d’indicizzazione dei contenuti e non comunica mai con altri server eMule. Ogni client invece possiede una lista di server eMule, oppure può essere aggiornato con TPSIT 5 434 um 322 di un nuovo elenco, ottenibile sia sui siti specializzati, oppure richiedendolo mediante apposito comando ad un server cui esso possa connettersi. Ogni client inoltre possiede un proprio elenco di file che mette a disposizione della comunità eMule, vale a dire la lista dei file condivisi. La connessione di un client ad un server eMule avviene utilizzando il protocollo TCP, tramite esso si ottiene l’elenco dei file disponibili e dei client sui quali questi sono presenti. Dopo aver effettuato l’accesso, mediante altre centinaia di connessioni TCP, un client comunica con i suoi pari, per effettuare l’upload o il download di file. Tale processo è regolato mediante code di accesso ad ogni determinato file. Ogni coda è formata dai client che hanno richiesto lo stesso file e che mano a mano che uno di essi completa il download permette l’avanzamento di un altro client. Per migliorare le prestazioni e velocizzare il download, ogni client non effettua il download di un intero file da un singolo altro client. Ogni file è invece suddiviso in parti, ognuna delle quali può essere richiesta e scaricata da un client diverso. In questo modo possono essere sfruttate centinaia di sorgenti differenti e contemporaneamente il client può fornire le parti che ha già scaricato mettendole a disposizione di altri client. Il protocollo eMule prevede, inoltre, un sistema di crediti che avvantaggia i client che mettono a disposizione un maggiore numero di file, incoraggiando la condivisione. Ogni server eMule mantiene un DB, con le informazioni relative ai client e ai file condivisi ma non memorizza alcun file, agendo solo da indicizzatore. Architettura di alto livello di una rete di server e client Emule per la condivisione di file. L’elenco dei server utilizzati da eMule è contenuto in un file SERVER.MET, presente nella cartella di avvio dell’applicazione. L’elenco può essere aggiornato sia fornendo l’URL di un SERVER.MET presente in Internet, oppure è previsto dal protocollo eMule un comando OP_SERVERLIST (32H) che permette di effettuare una richiesta al server eMule cui un client è connesso per richiedere TPSIT 5 434 um 323 di una lista dei server aggiornata. PROGRAMMAZIONE Formato dei messaggi Ogni messaggio ha un’intestazione lunga 6 byte, suddivisi in tre parti. Il primo byte indica il protocollo utilizzato ed ha il valore E3H per eDonkey, oppure C5H per il protocollo eMule. I successivi 4 byte indicano la lunghezza del messaggio in byte, escluso l’header stesso, quindi un messaggio formato solo dall’intestazione ha come dimensione il valore zero. Il terzo campo dell’intestazione, in pratica l’ultimo byte, è l’identificatore univoco del messaggio stesso, un ID che indica il comando. Classe che rappresenta un’intestazione di messaggio. public class MessageHeader { public Protocol.ProtocolType protocolID; public uint packetSize; public byte Command; public MessageHeader(byte command):base() { protocolID = Protocol.ProtocolType.eDonkey; Command = command; packetSize = 6; TPSIT 5 434 um 324 di } public void Serialize(BinaryWriter writer) { writer.Write((byte)protocolID); writer.Write(packetSize); writer.Write(Command); } } Il metodo Serialize serve per scrivere l’intestazione su uno stream da spedire in rete. Per appendere informazioni aggiuntive ai messaggi eMule, sono utilizzate delle strutture chiamate TAGS e strutturate secondo una sequenza Tipo-Lunghezza-Valore, per questo sono detti anche TAG TLV. Esistono diversi TAG differenti, documentati nel protocollo di eMule liberamente scaricabile dalla rete. Ogni TAG è formato da 4 campi, non tutti però sono sempre inclusi nel corpo del messaggio. Tipo Nome Valore Special 1 byte, indica il tipo del TAG. Può essere formato da una stringa di lunghezza variabile o da un intero di 1 byte. Può essere un intero di 4 byte, un numero floating di 4 byte, oppure una stringa di lunghezza variabile. È un intero di 1 byte che identifica un TAG speciale. TAG con valori interi sono detti integer TAGS, si hanno string TAGS e float TAGS. Il tipo di un string TAG è 2, di un integer TAG è 3 e di un float TAG è 4. Quando i TAG sono codificati per essere spediti in rete, è seguito l’ordine seguente: prima il tipo, poi il nome e infine il valore. I nomi dati ai TAG non hanno un significato particolare per il protocollo, sono solo assegnati per un più semplice ed immediato riferimento nella descrizione dei messaggi. Per leggere un TAG occorre implemetare una classe TagReader. Analogamente si deve implementare la classe per scrivere un TAG. Comandi e OpCode Ogni comando eMule è identificato da uno specifico codice esadecimale, detto OpCode. Ogni comando inoltre prevede una serie di parametri da specificare. La connessione avviene inviando un comando con OpCode OP_LOGINREQUEST, pari al valore 01H. Per evitare di specificare ogni OpCode di volta in volta e rendere più facilmente gestibili i comandi s’implementano delle enumerazioni. Per esempio, per i comandi da inviare al server un’enumerazione ServerCommand con relativi OpCode. public enum ServerCommand : byte { LoginRequest = 0x01, ServerMessage = 0x38, IDChange = 0x40, GetServerList = 0x14, OfferFiles = 0x15, SearchRequest = 0x16, ServerList = 0x32, SearchResult = 0x33, TPSIT 5 434 um 325 di GetSources = 0x19, QueryMoreResults = 0x21, FoundSources = 0x42, CallBackRequest = 0x1C, ServerState = 0x34, ServerIdent = 0x41, CallBackRequested = 0x35, } Allo stesso modo si possono implementare enumerazioni per i comandi client o per i valori dei campi di un TAG. Per un server eMule per esempio si possono specificare e ottenere le seguenti informazioni. public enum ServerTag : byte { Name = 0x01, Description = 0x0B, Ping = 0x0C, Priority = 0x0E, Fails = 0x0D, DynIP = 0x85, LastPing = 0x86, MaxUsers = 0x87, } Connessione ad un server Per connettersi ad un server eMule è necessario inviare l’apposito comando di login, con OpCode LoginRequest e stabilire una connessione TCP. Per rappresentare una connessione al server si deve progettare una classe ServerConnection, derivata da una generica Connection che utilizzerà i socket per creare una comunicazione TCP ad un indirizzo IP su una data porta. La classe Connection possiede i seguenti campi. public class Connection { protected Socket m_socket; protected IPEndPoint m_EPremote; } Al metodo CreateConnection si passeranno l’indirizzo IP e la porta verso cui effettuare la connessione. Il metodo Connect invierà una richiesta asincrona di connessione, indicando il metodo OnConnected come metodo che si occuperà di gestire la risposta del server una volta ricevuta una risposta. All’interno del metodo OnConnected è innanzitutto impostato l’istante dell’avvenuta connessione e azzerati i tentativi falliti. Un’altra classe, Server, si occuperà d’inviare e ricevere i messaggi dai server eMule. Il metodo SendLoginRequest invia il messaggio di login suddetto, costruendo un oggetto ServerLoginRequest che si occupa di riempire e formattare un buffer contenente la richiesta di login e spedendo poi il messaggio mediante il metodo SendPacket. La classe ServerLoginRequest serve a formattare il messaggio di login, costruendone l’intestazione, mediante l’utilizzo degli appropriati TAG. Il messaggio di login, come specificato nella documentazione del protocollo eMule, deve TPSIT 5 434 um 326 di contenere innanzitutto un’intestazione e poi una serie di altre informazioni come l’ID utente, versione del client, nome del client. L’intestazione è costruita mediante la classe MessageHeader al cui costruttore è passato il valore ServerCommand.LoginRequest. La lunghezza del messaggio varia quindi in base alla configurazione dell’utente, per esempio dal suo nickname. Al ricevimento di un messaggio di risposta, dopo aver letto e processati i byte del messaggio. A connessione avvenuta, il metodo ProcessIncomingPacket verifica il comando specificato nell’intestazione del messaggio e intraprende l’azione opportuna. Nel caso del messaggio di login, sarà ricevuto come risposta un messaggio ServerMessage. Tale messaggio può avere lunghezza ed è spedito da un server in diverse occasioni, non solo dopo una richiesta di login. Un singolo ServerMessage può contenere diverse righe di testo separate da caratteri new line, (‘\r’,’\n’ o entrambe). Messaggi che iniziano con il testo ”server version”, ”warning”, ”error” e ”[emDynIP:” hanno un significato per il client e possono essere trattati in maniera speciale. Gli altri messaggi possono semplicemente essere mostrati all’utente. Nel metodo ProcessIncomingPacket, esiste un case per elaborare un ServerMessage, all’interno del quale è invocato il metodo SetMessage della classe Server. Tale metodo semplicemente interpreta i byte ricevuti ed estrae il testo del messaggio. Oltre all’intestazione il messaggio, infatti, conterrà due byte che specificano la lunghezza della stringa successiva e appunto una stringa di testo, il messaggio può poi essere mostrato all’utente, mediante un’interfaccia grafica simile a quella di eMule. ID Basta leggere un intero per ottenere l’ID e poi verificare se il server ha specificato altri flag. Attualmente è possibile che il server spedisca infatti altri 4 byte e se il valore convertito in intero è uno indica che è supportata la compressione. In risposta alla richiesta di login, un server che accetta la connessione invierà anche un messaggio chiamato ID Change che assegna un ID al client in base a determinati parametri. TPSIT 5 434 um 327 di Il client ID è un identificatore a 4 byte ed è valido solo per il ciclo di vita di una sessione di connessione client-server, i client ID possono essere distinti in alti e bassi. Un ID basso è in genere assegnato da un server quando questi si accorge che il client non può accettare connessioni in ingresso. Avere un ID basso diminuisce le potenzialità di utilizzo della rete eMule, diminuendo notevolemente le capacità di download, in quanto il server dovrà agire da intermediario nello scambio dei file. L’ID è calcolato in base all’indirizzo IP, in quanto è questo che influenza la capacità da parte di altri client di connettersi direttamente ad un’altra macchina su una data porta. Se il server cui ci si connette non riesce ad aprire una connessione TCP verso il client, esso assegnerà un ID basso. Ciò accade generalmente se si utilizza un firewall per bloccare determinate porte. Un ID basso si avrà anche se ci si connette attraverso NAT (Network Address Translation) o proxy o se il server è troppo occupato. Un ID è calcolato secondo la seguente formula. Se l’IP del client è X.Y.Z.W, l’ID sarà pari al valore seguente. X + 28*Y + 216*Z + 224*W Se il valore risultante è inferiore a 16777216 (0x1000000) si tratta di un ID basso. Per controllare più agevolmente lo stato dell’ID, si controllano le frecce in diagonale poste sul piccolo globo azzurro posizionato nella barra di stato del client, in basso a destra. La freccia inferiore sinistra indica il collegamento al server, quella superiore destra indica il collegamento alla rete Kad. La freccia rossa indica: non connesso. La freccia verde indica: connesso con ID alto. La freccia gialla indica: connesso con ID basso, firewalled per la rete Kad. Da non confondersi con le frecce poste in verticale, rosse o verdi che indicano un’attività/inattività di download/upload. I crediti sono uno dei modificatori per il calcolo del punteggio nella coda di upload. In eMule un client accumula crediti verso un altro client in base alla quantità di dati scambiati reciprocamente e variano da 1 a 10 calcolati con le seguenti formule. Dove. C rappresenta i crediti. Bi indica i byte inviati. Br indica i byte ricevuti. MBi i MB inviati. eMule assegnerà i crediti scegliendo il risultato minore tra le due precedenti formule. Qualche mod di eMule fa scaricare pochi KB e poi blocca l’invio del file, cercando di guadagnare gli stessi crediti rispetto ai client “leali” che fanno scaricare l’intera parte. Per questo motivo è stato modificato il calcolo dei crediti da riconoscere agli altri utenti per i primi 9 MB ricevuti, non permettendo questa ingiustizia. La priorità di upload di un file condiviso descrive il livello d’importanza di condivisione, può essere impostato manualmente oppure in automatico da eMule. TPSIT 5 434 um 328 di Il variare del valore associato va a modificare il punteggio nella coda di upload. Queste sono le proprietà e i valori utilizzati da eMule. Release Alta Normale Bassa Molto Bassa 1.8 0.9 0.7 0.6 0.2 I download in eMule funzionano grazie ad un meccanismo di code. Ogni richiesta di upload è posta in una “lista d’attesa” propria di ogni eMule alla quale è assegnato un punteggio calcolato nel modo seguente. punteggio = tempo di attesa * priorità del file * crediti Il tempo di attesa, espresso in secondi, indica da quanto tempo un client è entrato in coda per il download di un file. Esempio, di due client che si trovano in coda fra di loro da 20 minuti, senza crediti fra loro: il primo ha il file in priorità release, l’altro ha lo stesso file ma in priorità bassa, naturalmente hanno tutti e due il file incompleto. 1. Punteggio del client con il file in priorità release che richiede il file dell’altro client in priorità bassa: 1200 * 0.6 * 1 = 720. 2. Punteggio del client con il file in priorità bassa che richiede il file dell’altro client in priorità release: 1200 * 1.8 * 1 = 2160. Si può notare che più si rimane connessi, più il punteggio aumenta. Comunque è meglio lasciar gestire le priorità a eMule in automatico e usare la priorità release per file più unici che rari. Durante il download di un file eMule può essere in coda solamente per un file alla volta, se un client possiede più file tra quelli scaricati eMule utilizza quindi le fonti A4AF (Asked For Another File), in altre parole Contattato Per Un Altro File per entrare nella coda di un file quando il download del precedente è terminato. eMule assegna le fonti A4AF ad un file in base alla sua priorità. Vi sono 9 livelli distinti di priorità determinati dalla priorità della categoria e del file. Priorità finale 1 2 3 4 5 6 7 8 9 Priorità della categoria Alta Alta Alta Normale Normale Normale Bassa Bassa Bassa Priorità del file Alta Normale Bassa Alta Normale Bassa Alta Normale Bassa Ricerca dei file Per avviare la ricerca di un file nella rete eMule basta utilizzare il comando SearchRequest cui il server risponderà con un comando SearchResult contenente l’elenco degli eventuali risultati. Nel comando di ricerca è possibile specificare i diversi parametri da utilizzare, come la TPSIT 5 434 um 329 di dimensione massima o minima dei file, il tipo di contenuto da ricercare e l’estensione. La risposta del server è se tutto va bene un comando SearchResult, in cui è specificato il numero n di risultati e quindi di seguito n elementi in un determinato formato. Il metodo SearchResult mostra come è possibile interpretare il messaggio dei risultati, il costruttore legge il numero di risultati nRes e poi esegue un ciclo for per ricavare i dati dei file trovati. Il metodo ExtractResult legge la parte seguente del messaggio interpretando i singoli TAG. Per ogni risultato ricava dunque il nome, la dimensione, il numero di sorgenti, l’ID del file o altri parametri come il bitrate o l’artista se si tratta per esempio di un MP3. TPSIT 5 434 um 330 di TOR (THE ONION ROUTER) INTRODUZIONE È un S/W open source, il cui acronimo significa “instradamento a cipolla”, il motivo di un nome così originale dipende dall’algoritmo su cui è basato il funzionamento della rete. I nodi, relay, si occupano di smistare i messaggi da e verso gli altri nodi della rete. I messaggi non seguono un percorso logico o diretto dalla sorgente alla destinazione ma percorrono la rete in modo disordinato e tortuoso, cancellando periodicamente le tracce. Grazie al sofisticato algoritmo adottato dalla rete nessun osservatore situato in un singolo punto può capire da dove proviene e dove va ogni singolo messaggio, in pratica ogni nodo conosce solo il nodo da cui ha ricevuto il pacchetto e a quale nodo deve inoltrarlo. Per ogni singolo hop da un relay all’altro è usato un insieme di chiavi diverse per la cifratura, così da evitare che qualche nodo possa tracciare le connessioni. Quando la rete è riuscita a stabilire un percorso tra una sorgente e una destinazione continua ad usare questo “circuito” per tutte le comunicazioni che avvengono tra i due nodi, per 10 minuti. Dopo questo periodo di tempo è creato automaticamente un nuovo circuito. L’installazione e la configurazione del client è semplice e richiede pochi passaggi. Una volta che il S/W si è collegato alla rete bisogna configurare tutti i S/W che devono collegarsi a Internet per l’uso di un server proxy basato sul protocollo Socks, con indirizzo 127.0.0.1 e porta TCP 9051. TPSIT 5 434 um 331 di Nella sua versione di base il client è un eseguibile a riga di comando che si limita ad avviarsi e a collegarsi, restando in attesa di connessioni sulla porta 9051. TPSIT 5 434 um 332 di Il progetto TOR ha sviluppato anche un’UI per la configurazione e gestione del client, chiamata Vidalia, questo curioso nome deriva da una varietà di cipolle rosse dolci, apprezzate in America, originarie di Vidalia, in Georgia. L’uso dell’interfaccia grafica permette di tenere sotto controllo lo stato della rete, di visualizzare e modificare le principali impostazioni e persino di visualizzare la rete TOR su una mappa del pianeta, per vedere dove si trovano tutti i nodi che ne fanno parte. TPSIT 5 434 um 333 di BT (BITTORRENT) INTRODUZIONE È un protocollo di tipo P2P finalizzato alla distribuzione e condivisione di file nella rete. In realtà, non è un vero e proprio protocollo P2P, visto che l’architettura di BT prevede la presenza di un server. L’omonimo client originale e il protocollo sono stati sviluppati da Bram Cohen, un programmatore di San Francisco, nel 2002. Un file con estensione TORRENT è un piccolo file che può essere pubblicato su un qualunque sito web. Ne esistono migliaia che offrono funzioni di ricerca per genere, per tipo, per categoria, si chiamano Torrent Finder, i principali sono i seguenti. Btjunkie.org Bittorrent.com Torrentreactor.net Isohunt.com Meganova.org Mininova.org ThePirateBay.org Torrentportal.com Torrentspy.com Torrentz.com Il punto di partenza è trovare un file TORRENT da prelevare, tramite esso il client potrà sapere di quali e quanti pacchetti è composto il file originale, incluse le informazioni necessarie a confermare l’integrità e la “bontà” dello stesso. Naturalmente il file TORRENT contiene anche l’indirizzo URL del server che conosce le sorgenti dei vari frammenti del file originale. Il server è chiamato Tracker ed è esso che si occupa di coordinare le varie richieste dei client che richiederanno di scaricare il file. Il tracker fornisce informazioni sui file TORRENT che gestisce, per esempio quanti client posseggono una copia del file, intera o parziale o quante volte esso è stato scaricato. Una volta che un client richiede un file e inizia a scaricarne un frammento, esso può diffonderlo agli altri pretendenti, grazie alla collaborazione del tracker. Per il tracker non transita neppure un byte dei file che si stanno scambiando, è su ciò che si basa la difesa della legalità di questi server: i contenuti sono scambiati direttamente da P2P. TPSIT 5 434 um 334 di In questo modo ogni file è diffuso in maniera estremamente rapida ed efficiente, perché tutti i client e non solo i server contribuiscono alla diffusione stessa e inoltre è anche eliminato alla base il problema dovuto al fatto che molti utenti scaricano il file ma poi, una volta completato si disconnettono impedendo ad altri di poterne ottenere una copia. Il file Seed, seme, è una copia completa online del file che può essere ospitato su un qualunque sito web purchè connesso in banda larga. Nel momento in cui un client invia una richiesta di download del file, il trasferimento non parte fino a che non sia trovato il file Seed. Questo sistema incrementa la velocità di download, soprattutto quando ci sono molti utenti che stanno scaricando uno stesso file. Un client che ottiene una copia completa del file da scaricare diverrà anch’esso Seeder. Anche client che non hanno il file completo possono condividere la loro parte, tali client sono chiamati Peer e contribuiscono alle prestazioni del protocollo come i Seed. Chi scarica un file e si disconnette non permettendo la diffusione dello stesso è Leech. Maggiore è il numero di Seed e Peer, maggiore sarà la velocità di download. Allo stesso modo se ci sono moltissimi Peer e pochi Seed, inizialmente si avrà una maggiore velocità, mentre man mano che si procede meno client avranno il file completo e minore sarà la velocità di download per riuscire a completare il file. Le specifiche di BT sono state rese pubblicamente fruibili e grazie a ciò molti programmatori le hanno potute implementare liberamente. PROGRAMMAZIONE I progettisiti di Mono per gestire il protocollo Torrent hanno realizzato la libreria MONOTORRENT.DLL, un set completo di API, grazie al quale, è possibile creare un client senza dover scrivere nulla riguardante il protocollo stesso o che abbia a che fare con la comunicazione di rete di basso livello. Al progetto aggiungere il riferimento all’assembly MONOTORRENT.DLL. La classe da cui partire si chiama ClientEngine, quella del progetto MyTorrentController con un campo private di tale tipo m_clientEngine che permetterà di trattare caricamento e download dei file TORRENT. File MYTORRENTCONTROLLER.CS Mostra il costruttore che si occupa d’inizializzare tutti i campi privati della classe e la relativa inizializzazione. Il dizionario m_itemTorrents conterrà tutti i file TORRENT trattati in ogni dato momento. Il metodo Init si occupa di creare l’istanza di ClientEngine, passandogli come parametro un oggetto EngineSettings, contentente tutte le impostazioni viste nel costruttore. TPSIT 5 434 um 335 di Dopo di che, se non esistono, crea le cartelle che conterrenno i download, leggendo dalle stesse eventuali file TORRENT in sospeso. Il primo evento sottoscritto è l’evento StatsUpdate, al verificarsi del quale si procederà all’aggiornamento di tutte le statistiche e informazioni sui file TORRENT: velocità, dimensione, byte scaricati, avanzamento. Il primo passo per iniziare il download di un file TORRENT è procurarsi il suo URL, oppure avere scaricato il file TORRENT. A questo punto è verificato se il file non è già nella cartella dei TORRENT e lo si salva. Quindi è invocato il metodo Torrent.Load. Dopo aver creato graficamente un elemento della ListView, in maniera da dare un riscontro all’utente finale, inizia il trattamento vero e proprio del TORRENT, registrandolo nel ClientEnginee creando un nuovo oggetto TorrentManager a esso dedicato. Il TorrentManager è aggiunto al dizionario m_itemTorrents, facendolo corrispondere all’item della ListView che funge da chiave per ricavarlo in ogni momento. Il metodo Start inizia il download dei file TORRENT in lista. Tale metodo scorre il dizionario e invoca su ogni TorrentManager trovato il metodo Start della classe stessa. Di ogni file TORRENT è possibile conoscere lo stato in download, in pausa, la percentuale di avanzamento, il numero di Seed e Leech, la velocità di download e upload, la quantità di byte già scaricati e, quindi, quella rimanente, i byte caricati e, mediante un calcolo basato sulla dimensione, tempo trascorso e byte scaricati, nonché la velocità di download attuale. Per creare un TORRENT a partire da un file esistente si usa la classe TorrentCreator. Basta poi trovare un sito su cui caricare il TORRENT creato, in pratica un Tracker che dice a ogni utente interessato a chi e come connettersi per scaricarlo. TPSIT 5 434 um 336 di TPSIT 5 434 um 337 di JAVA Vuze, noto come Azureus fino alla versione 2.0, è un client BT scritto in Java sotto licenza GNU GPL. Nella cartella dove è installato Vuze si trova il file AZUREUS2.JAR. Copiarlo e aggiungerlo al progetto Java: si ha accesso a tutte le classi di Vuze. La prima cosa da fare è accedere alla classe com.aelitis.azureus.core.AzureusCore, costituisce il core, in altre parole il nucleo di Vuze/Azureus. Per utilizzare qualsiasi funzione collegata alla libreria di Vuze, quindi, bisogna ottenere un’istanza di questa classe. L’istanza è costruita passando per la classe seguente e il suo metodo statico create. AzureusCore core = AzureusCoreFactory.create(); Una volta creata l’istanza di AzureusCore bisogna avviarla con il suo metodo start. core.start(); Ora è possibile usare il core di Vuze/Azureus per scaricare un torrent. Si deve chiamare in gioco il componente global manager che corrisponde alla classe org.gudy.azureus2.core3.global.GlobalManager. Un’istanza può essere recuperata dal core, nel modo seguente. GlobalManager globalManager = core.getGlobalManager(); Con il GlobalManager è possibile mettere in download un TORRENT. Il metodo da invocare si chiama addDownloadManager e vuole due argomenti sotto forma di stringa: il percorso del file TORRENT e quello della cartella in cui posizionare i file scaricati. globalManager.addDownloadManager("test.torrent","F:\\MiaDirectory"); Si possono aggiungere più download, chiamando più volte addDownloadManager con differenti argomenti. Le operazioni di download possono quindi essere avviate invocando il codice seguente. globalManager.startAllDownloads(); Nella maggioranza dei casi, è importante conoscere lo stato di ciascun download in corso. È possibile monitorare ciascuno dei download passando per la classe seguente org.gudy.azureus2.core3.download.DownloadManager. Il metodo addDownloadManager restituisce un’istanza della classe, utile per monitorare il download appena aggiunto al global manager. DownloadManager manager = globalManager.addDownloadManager("test.torrent", "F:\\MiaDirectory"); Al download manager ottenuto si può aggiungere un listener. manager.addListener(listener); Il listener fornito dev’essere un oggetto che implementi l’interfaccia seguente. TPSIT 5 434 um 338 di org.gudy.azureus2.core3.download.DownloadManagerListener. Questa richiede i seguenti metodi. public void stateChanged(DownloadManager manager,int state) public void downloadComplete(DownloadManager manager) public void completionChanged(DownloadManager manager, boolean bCompleted) public void filePriorityChanged(DownloadManager manager, DiskManagerFileInfo info) public void positionChanged(DownloadManager manager, int oldPosition, int newPosition) Il metodo stateChanged è richiamato quando lo stato del download cambia. Il download manager fornito è quello corrispondente al download in corso, mentre il nuovo stato dell’operazione è simboleggiato dall’intero state. Il valore sarà sicuramente una delle costanti di DownloadManager il cui nome comincia per STATE, ad esempio DownloadManager.STATE_DOWNLOADING che indica che il download è in corso. Il metodo downloadComplete è richiamato per segnalare che il download è completato. Al termine della sessione di utilizzo del core di Vuze/Azureus, bisogna arrestare quest’ultimo invocandone il metodo stop. core.stop(); Scaricare da riga di comando un TORRENT alla volta, il nome del file TORRENT dev’essere fornito come argomento, mentre la cartella corrente sarà usata come locazione per il download dei file del TORRENT. File SIMPLEDOWNLOADER.JAVA Nel main dell’applicazione è controllata la presenza del parametro che corrisponde al percorso del file TORRENT da scaricare. La cartella corrente è risolta interrogando la proprietà di sistema user.dir. Si è inserito nella classe uno speciale metodo chiamato waitDownloadCompleted che serve per mettere in attesa chi lo chiama fin quando il download non sarà completato. L’attesa è coordinata servendosi delle tecniche di sincronizzazione di Java basate sui metodi wait e notify. Nella definizione del metodo stateChanged si resta in attesa che il download cominci. Quando lo stato diventa DownloadManager.STATE_DOWNLOADING, si lancia un thread secondario, il cui compito è monitorare il progresso dell’operazione. TPSIT 5 434 um 339 di Nel metodo downloadComplete si ferma il thread di controllo e si risveglia chi si era messo in attesa dell’evento. package sd; import java.io.File; import org.gudy.azureus2.core3.download.DownloadManager; import org.gudy.azureus2.core3.global.GlobalManager; import com.aelitis.azureus.core.AzureusCore; import com.aelitis.azureus.core.AzureusCoreException; import com.aelitis.azureus.core.AzureusCoreFactory; public class SimpleDownloader { public static void main(String[] args) { if (args.length != 1) { System.out.println(); System.out.println("Utilizzo:"); System.out.println(); System.out.println("SimpleDownloader torrentDaScaricare.torrent"); System.out.println(); System.exit(1); } File file = new File(args[0]); String filePath = file.getAbsolutePath(); String dirPath = System.getProperty("user.dir"); if (!file.exists()) { System.out.println("Impossibile trovare " + filePath); System.exit(1); } try { AzureusCore core = AzureusCoreFactory.create(); core.start(); GlobalManager globalManager = core.getGlobalManager(); DownloadManager downloadManager = globalManager.addDownloadManager(filePath, dirPath); SimpleDownloadListener listener = new SimpleDownloadListener(); downloadManager.addListener(listener); globalManager.startAllDownloads(); listener.waitDownloadCompleted(); core.stop(); } catch (AzureusCoreException e) { System.out.println("Errore nel core di Vuze/Azureus scaricare"); e.printStackTrace(); System.exit(1); } } } File SIMPLEDOWNLOADCHECKER.JAVA È un metodo che cicla fin quando non è interrotto, facendo pause di cinque secondi tra un ciclo e il successivo. Ad ogni ciclo controlla lo stato di avanzamento del download. Per farlo interagisce con il DownloadManager assegnato al costruttore della classe. Attraverso il metodo getStats estrapola dal download manager un oggetto di tipo TPSIT 5 434 um 340 di org.gudy.azureus2.core3.download.DownloadManagerStats. Questo genere di oggetti riporta informazioni statistiche sul download in corso. Con getCompleted se ne estrae il valore di progresso, espresso come intero da 0 a 1000. È sufficiente dividere questo valore per 10 per avere una percentuale di completamento del download. package sd; import org.gudy.azureus2.core3.download.DownloadManager; import org.gudy.azureus2.core3.download.DownloadManagerStats; class SimpleDownloadChecker implements Runnable { // il manager da controllare private DownloadManager manager; public SimpleDownloadChecker(DownloadManager manager) { this.manager = manager; } @Override public void run() { int lastValue = -1; while (!Thread.interrupted()) { DownloadManagerStats stats = manager.getStats(); int completed = stats.getCompleted(); if (completed != lastValue) { lastValue = completed; float percent = completed / 10F; System.out.println("Scaricato: " + percent + "%"); } try { Thread.sleep(5000); } catch (InterruptedException e) { break; } } } } File SIMPLEDOWNLOADLISTENER.JAVA package sd; import org.gudy.azureus2.core3.download.DownloadManager; import org.gudy.azureus2.core3.download.DownloadManagerStats; class SimpleDownloadChecker implements Runnable { // il manager da controllare private DownloadManager manager; public SimpleDownloadChecker(DownloadManager manager) { this.manager = manager; } @Override public void run() { int lastValue = -1; while (!Thread.interrupted()) { DownloadManagerStats stats = manager.getStats(); int completed = stats.getCompleted(); if (completed != lastValue) { lastValue = completed; TPSIT 5 434 um 341 di float percent = completed / 10F; System.out.println("Scaricato: " + percent + "%"); } try { Thread.sleep(5000); } catch (InterruptedException e) { break; } } } } Avviare la classe principale SimpleDownloader, passandole come argomento il percorso del TORRENT da scaricare PROVA.TORRENT. Fare clic su Run/Run Configurations… Fare clic su Run. Da console. I:\>java -cp Azureus2.jar;SimpleDownloader.jar sd.SimpleDowloader prova.torrent Eclipse IDE for Java Progettare un client BT, l’utente dovrà fornire all’applicazione soltanto due informazioni: qual è il file TORRENT con i dettagli dei contenuti da scaricare e quale la cartella in cui sono salvati i dati scaricati. TPSIT 5 434 um 342 di package bt; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JTextField; import javax.swing.UIManager; import javax.swing.filechooser.FileFilter; import org.gudy.azureus2.core3.disk.DiskManagerFileInfo; import org.gudy.azureus2.core3.download.DownloadManager; import org.gudy.azureus2.core3.download.DownloadManagerListener; import org.gudy.azureus2.core3.download.DownloadManagerStats; import org.gudy.azureus2.core3.global.GlobalManager; import org.gudy.azureus2.core3.security.SESecurityManager; import com.aelitis.azureus.core.AzureusCore; import com.aelitis.azureus.core.AzureusCoreFactory; Classe BTDownLoader. Per gestire i download BT, si ha bisogno di un’istanza della classe AzureusCore. Una volta ottenuta questa istanza, la si utilizza per creare un DownloadManager, in altre parole un componente utile per avviare e gestire il download e la condivisione di un TORRENT. Una volta avviato il download è possibile ciclicamente interrogare il DownloadManager, per chiedergli come stanno andando le operazioni in corso. TPSIT 5 434 um 343 di Si fa avviando un thread parallelo che si occuperà di estrarre continuamente dal DownloadManager le informazioni di stato aggiornate. Per far ciò si devono aggiungere tre proprietà alla classe BTDownLoader. Per ricevere eventi dal DownloadManager si deve usare un DownloadManagerListener, mentre per far girare del codice nel thread secondario occorre un oggetto Runnable. // interfaccia grafica per il download dei torrent public class BTDownLoader extends JFrame implements DownloadManagerListener, Runnable { private static final long serialVersionUID = 1L; // la casella di testo per specificare il percorso del file torrent private JTextField torrentFilePathField = new JTextField(""); // la casella di testo per specificare il percorso della directory in cui // scaricare i contenuti del torrent private JTextField downloadDirectoryPathField = new JTextField(""); // il bottone per la selezione guidata del percorso del file torrent private JButton torrentFilePathBrowseButton = new JButton("Sfoglia"); // il bottone per la selezione guidata del percorso della directory di download private JButton downloadDirectoryPathBrowseButton = new JButton("Sfoglia"); // il bottone per l’avvio del download / upload del torrent private JButton startButton = new JButton("Avvia"); // il bottone per l’arresto del download / upload del torrent private JButton stopButton = new JButton("Arresta"); // la barra di progresso usata per mostrare l’avanzamento del download private JProgressBar downloadProgressBar = new JProgressBar(); // l’etichetta usata per mostrare una descrizione dello stato del torrent private JLabel statusLabel = new JLabel("Seleziona un file .torrent per cominciare", JLabel.CENTER); // l’etichetta che riporta la velocità di download private JLabel downloadRateLabel = new JLabel("Download: 0 KB/s", JLabel.CENTER); // l’etichetta che riporta la velocità di upload private JLabel uploadRateLabel = new JLabel("Upload: 0 KB/s", JLabel.CENTER); // un riferimento al core di Azureus private AzureusCore core = AzureusCoreFactory.create();; // il download manager per il torrent in download / upload private DownloadManager downloadManager = null; // il thread che durante il download / upload controlla e mostra le statistiche private Thread statsThread = null; // costruttore privato (inibisce l’uso dall’esterno della classe) private BTDownLoader() { setTitle("BitTorrent Downloader"); // costruisce l’interfaccia grafica JComponent contentPane = buildContentPane(); setContentPane(contentPane); // applica le dimensioni ottimali pack(); // posizione al centro dello schermo. Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frame = getSize(); int x = (screen.width - frame.width) / 2; int y = (screen.height - frame.height) / 2; TPSIT 5 434 um 344 di setLocation(x, y); // alla chiusura della finestra termina l’applicazione addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { SESecurityManager.exitVM(0); } }); } L’UI è costituita da una finestra di dimensioni ridotte, all’interno della quale l’utente avrà due aree di testo per specificare i percorsi del file TORRENT e della cartella di download. A fianco delle aree di testo ci sono due pulsanti Sfoglia che permettono all’utente di selezionare un percorso su disco. L’interfaccia è poi dotata di due pulsanti Avvia e Arresta, utili rispettivamente per avviare e interrompere il download e la condivisione dei contenuti del torrent, in questa maniera l’utente potrà anche mettere in pausa il download, per poi riprenderlo successivamente. Al termine c’è la barra di progressione, utile per mostrare all’utente la percentuale di completamento raggiunta durante il download e alcune label per notificare lo stato del torrent e per informare l’utente sulle velocità di download e upload dei dati. Il costruttore della classe assembla l’UI chiamando il metodo buildContentPane. La finestra è compattata alle sue dimensioni ideali e messa al centro dello schermo. Quando si chiude la finestra, un WindowAdapter cattura l’evento e lo gestisce invocando il metodo exitVM della classe org.gudy.azureus2.core3.security.SESecurityManager. Per uscire dall’applicazione si deve passare per la classe SESecurityManager. // costruisce il pannello con i contenuti della finestra // @return Il pannello con i contenuti della finestra private JComponent buildContentPane() { JComponent pathsPane = buildPathsPane(); JComponent actionsPane = buildActionsPane(); JComponent statusPane = buildStatusPane(); JComponent ratesPane = buildRatesPane(); JPanel contentPane = new JPanel(); contentPane.setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.insets = new Insets(3, 3, 3, 3); c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.CENTER; c.weightx = 1; c.weighty = 0; c.gridx = 0; c.gridy = 0; contentPane.add(pathsPane, c); c.gridy = 1; contentPane.add(actionsPane, c); c.gridy = 2; contentPane.add(statusPane, c); c.gridy = 3; contentPane.add(ratesPane, c); return contentPane; } TPSIT 5 434 um 345 di Il pannello principale della finestra è suddiviso in quattro sotto aree: pathsPane, actionsPane, statusPane e ratesPane. Nel pannello pathsPane s’inseriscono i widget utili per indicare i percorsi del file TORRENT e della cartella per il download, insieme con i corrispettivi pulsanti Sfoglia che sono collegati a dei listener che rimandano ai metodi browseTorrentFilePath e browseDownloadDirectoryPath. // costruisce il pannello per la selezione dei percorsi // @return Il pannello per la selezione dei percorsi private JComponent buildPathsPane() { torrentFilePathField.setPreferredSize( new Dimension(300, torrentFilePathField.getPreferredSize().height)); downloadDirectoryPathField.setPreferredSize( new Dimension(300, downloadDirectoryPathField.getPreferredSize().height)); JLabel torrentFilePathLabel = new JLabel("File .torrent:"); JLabel downloadDirectoryPathLabel = new JLabel("Scarica in:"); torrentFilePathBrowseButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { browseTorrentFilePath(); } }); downloadDirectoryPathBrowseButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { browseDownloadDirectoryPath(); } }); JPanel pathsPanel = new JPanel(); pathsPanel.setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.insets = new Insets(3, 3, 3, 3); c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.EAST; c.weightx = 0; c.weighty = 0; c.gridx = 0; c.gridy = 0; pathsPanel.add(torrentFilePathLabel, c); c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.CENTER; c.weightx = 1; c.gridx = 1; pathsPanel.add(torrentFilePathField, c); c.fill = GridBagConstraints.NONE; c.weightx = 0; c.gridx = 2; pathsPanel.add(torrentFilePathBrowseButton, c); c.fill = GridBagConstraints.NONE; TPSIT 5 434 um 346 di c.anchor = GridBagConstraints.EAST; c.weightx = 0; c.weighty = 0; c.gridx = 0; c.gridy = 1; pathsPanel.add(downloadDirectoryPathLabel, c); c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.CENTER; c.weightx = 1; c.gridx = 1; pathsPanel.add(downloadDirectoryPathField, c); c.fill = GridBagConstraints.NONE; c.weightx = 0; c.gridx = 2; pathsPanel.add(downloadDirectoryPathBrowseButton, c); return pathsPanel; } Nel pannello actionsPane ci sono i pulsanti Avvia e Arresta che sono associati ai metodi startTorrent e stopTorrent. // costruisce il pannello con i bottoni per le azioni di start e stop // @return Il pannello con i bottoni per le azioni di start e stop private JComponent buildActionsPane() { stopButton.setEnabled(false); startButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { startTorrent(); } }); stopButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { stopTorrent(); } }); JPanel actionsPane = new JPanel(); actionsPane.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 3)); actionsPane.add(startButton); actionsPane.add(stopButton); return actionsPane; } Nel pannello statusPane ci sono, la label di stato e la barra di progressione che indicano rispettivamente l’operazione corrente e la percentuale di completamento della stessa. // costruisce il pannello con le informazioni di stato sul download / upload del torrent // @return Il pannello con le informazioni di stato sul download / upload del torrent private JComponent buildStatusPane() { downloadProgressBar.setStringPainted(false); downloadProgressBar.setMinimum(0); TPSIT 5 434 um 347 di downloadProgressBar.setMaximum(1000); downloadProgressBar.setValue(0); JPanel statusPane = new JPanel(); statusPane.setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.insets = new Insets(3, 3, 3, 3); c.anchor = GridBagConstraints.CENTER; c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1; c.weighty = 0; c.gridx = 0; c.gridy = 0; statusPane.add(downloadProgressBar, c); c.gridy = 1; statusPane.add(statusLabel, c); return statusPane; } L’ultimo pannello è ratesPane che mostra le velocità correnti di download e upload. // costruisce il pannello con le informazioni sulle velocità di upload e download dei dati. // @return Il pannello con le informazioni sulle velocità di upload e download dei dati private JComponent buildRatesPane() { JPanel ratesPane = new JPanel(); ratesPane.setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.insets = new Insets(3, 3, 3, 3); c.anchor = GridBagConstraints.CENTER; c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1; c.weighty = 0; c.gridx = 0; c.gridy = 0; ratesPane.add(downloadRateLabel, c); c.gridx = 1; ratesPane.add(uploadRateLabel, c); return ratesPane; } L’UI è dotata di due pulsanti Sfoglia, utili per selezionare graficamente file e cartelle senza dover digitare manualmente per intero il loro percorso. Il primo di questi due pulsanti, quello a fianco della casella di testo con il percorso del file TORRENT da aprire, utilizza un oggetto Swing javax.swing.JFileChooser, al quale è abbinato un filtro utile per mostrare solo i file con estensione TORRENT, lasciando così nascosti quelli di altro tipo. A selezione confermata, il percorso del file scelto dall’utente è scritto nella casella di testo corrispondente. Il metodo controlla se l’utente ha già specificato una cartella di download, se non lo ha fatto, l’applicazione propone automaticamente di scaricare i dati nella stessa cartella che ospita il file TORRENT appena selezionato. // sfoglia il file system per selezionare il file torrent. TPSIT 5 434 um 348 di private void browseTorrentFilePath() { JFileChooser fc = new JFileChooser(System.getProperty("user.home")); fc.setFileFilter(new FileFilter() { @Override public String getDescription() { return "File .torrent"; } @Override public boolean accept(File f) { // se cartella, va bene sempre if (f.isDirectory()) { return true; } // se file, va bene solo se ha estensione .torrent if (f.getName().toLowerCase().endsWith(".torrent")) { return true; } else { return false; } } }); int c = fc.showOpenDialog(this); if (c == JFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); torrentFilePathField.setText(file.getAbsolutePath()); torrentFilePathField.setCaretPosition(0); String aux = downloadDirectoryPathField.getText().trim(); if (aux.length() == 0) { File dir = file.getParentFile(); if (dir != null) { downloadDirectoryPathField.setText(dir.getAbsolutePath()); downloadDirectoryPathField.setCaretPosition(0); } } } } Il metodo associato al secondo tasto Sfoglia, in altre parole quello utile proprio per specificare o modificare il percorso della directory di download. // sfoglia il file system per selezionare la directory di download private void browseDownloadDirectoryPath() { JFileChooser fc = new JFileChooser(System.getProperty("user.home")); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int c = fc.showOpenDialog(this); if (c == JFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); downloadDirectoryPathField.setText(file.getAbsolutePath()); downloadDirectoryPathField.setCaretPosition(0); } } TPSIT 5 434 um 349 di Il metodo startTorrent avvia le operazioni di download e condivisione dei dati. Prima di avviare il download, controlla che i dati forniti dall’utente siano validi. Sono pertanto esaminati i percorsi del file .TORRENT e della cartella di download, appurando preventivamente la loro esistenza e la loro correttezza. Passati i controlli, la GUI è aggiornata e il core di Vuze/Azureus avviato, se non è già attivo, il metodo infatti potrebbe essere richiamato più di una volta. Tramite il core è possibile generare il DownloadManager e avviare il download e la condivisione. Per ultimo si avvia anche il thread che controllerà le statistiche e aggiornerà la percentuale di progresso e le velocità di download e upload. // avvia o riprende il download / upload del torrent private void startTorrent() { // controlla il file torrent String torrentFilePath = torrentFilePathField.getText().trim(); if (torrentFilePath.length() == 0) { JOptionPane.showMessageDialog(this, "Specificare il file .torrent"); return; } File torrentFile = new File(torrentFilePath); if (!torrentFile.exists() || !torrentFile.isFile()) { JOptionPane.showMessageDialog(this, "File .torrent non valido"); return; } // controlla la cartella di download String downloadDirectoryPath = downloadDirectoryPathField.getText().trim(); if (downloadDirectoryPath.length() == 0) { JOptionPane.showMessageDialog(this, "Specificare la directory di download"); return; } File downloadDirectory = new File(downloadDirectoryPath); if (!downloadDirectory.exists() || !downloadDirectory.isDirectory()) { JOptionPane.showMessageDialog(this, "Directory di download non valida"); return; } // normalizza i percorsi torrentFilePath = torrentFile.getAbsolutePath(); downloadDirectoryPath = downloadDirectory.getAbsolutePath(); // cambia lo stato della GUI. torrentFilePathField.setEnabled(false); torrentFilePathBrowseButton.setEnabled(false); downloadDirectoryPathField.setEnabled(false); downloadDirectoryPathBrowseButton.setEnabled(false); startButton.setEnabled(false); stopButton.setEnabled(true); downloadProgressBar.setStringPainted(true); downloadProgressBar.setValue(0); downloadRateLabel.setText("Download: 0 KB/s"); uploadRateLabel.setText("Upload: 0 KB/s"); // se necessario, avvia il core di Azureus if (!core.isStarted()) { TPSIT 5 434 um 350 di core.start(); } GlobalManager globalManager = core.getGlobalManager(); downloadManager = globalManager.addDownloadManager(torrentFilePath, downloadDirectoryPath); downloadManager.addListener(this); globalManager.startAllDownloads(); // avvia il thread delle statistiche statsThread = new Thread(this); statsThread.start(); } Per arrestare il torrent si usa il metodo stopTorrent che rimuove il download dalla pila di Vuze/Azureus e aggiorna la GUI di conseguenza. Il core è mantenuto attivo, in modo che sia possibile riusarlo al riavvio successivo, il download, infatti, può essere ripreso azionando di nuovo il bottone Avvia. // arresta il download / upload del torrent private void stopTorrent() { // ferma il thread delle statistiche statsThread.interrupt(); // ferma e rimuove il download GlobalManager globalManager = core.getGlobalManager(); globalManager.stopAllDownloads(); try { globalManager.removeDownloadManager(downloadManager); } catch (Exception e) { e.printStackTrace(); } // cambia lo stato della GUI torrentFilePathField.setEnabled(true); torrentFilePathBrowseButton.setEnabled(true); downloadDirectoryPathField.setEnabled(true); downloadDirectoryPathBrowseButton.setEnabled(true); startButton.setEnabled(true); stopButton.setEnabled(false); downloadProgressBar.setValue(0); downloadProgressBar.setStringPainted(false); downloadRateLabel.setText("Download: 0 KB/s"); uploadRateLabel.setText("Upload: 0 KB/s"); statusLabel.setText("Torrent arrestato"); } Implementazione dei metodi in grado d’intercettare gli eventi del TORRENT. Si possono seguire due vie per estrapolare altrettante distinte categorie d’informazioni. I cambi di stato si possono intercettare dando implementazione ai metodi richiesti dall’interfaccia DownloadManagerListener. Le informazioni sulla percentuale di completamento e sulle velocità di download e upload dei dati, invece, si possono recuperare ciclicamente con il metodo statsThread. @Override public void completionChanged(DownloadManager manager, boolean bCompleted) { TPSIT 5 434 um 351 di } @Override public void downloadComplete(DownloadManager manager) { } @Override public void filePriorityChanged(DownloadManager download, DiskManagerFileInfo file) { } @Override public void positionChanged(DownloadManager download, int oldPosition, int newPosition) { } Il metodo stateChanged intercetta e annota i cambi di stato del TORRENT, così da poter illustrare all’utente cosa sta accadendo all’interno del S/W. @Override public void stateChanged(DownloadManager manager, int state) { switch (state) { case DownloadManager.STATE_ALLOCATING: statusLabel.setText("Allocazione in corso..."); break; case DownloadManager.STATE_CHECKING: statusLabel.setText("Verifica in corso..."); break; case DownloadManager.STATE_CLOSED: statusLabel.setText("Torrent chiuso"); break; case DownloadManager.STATE_DOWNLOADING: statusLabel.setText("Download in corso..."); break; case DownloadManager.STATE_ERROR: statusLabel.setText("Errore: " + manager.getErrorDetails()); break; case DownloadManager.STATE_INITIALIZED: statusLabel.setText("Inizializzato..."); break; case DownloadManager.STATE_INITIALIZING: statusLabel.setText("Inizializzazione in corso..."); break; case DownloadManager.STATE_QUEUED: statusLabel.setText("In attesa..."); break; case DownloadManager.STATE_SEEDING: statusLabel.setText("Download completato. Condivisione in corso..."); break; case DownloadManager.STATE_STOPPED: statusLabel.setText("Arrestato"); break; case DownloadManager.STATE_STOPPING: statusLabel.setText("Arresto in corso..."); break; } TPSIT 5 434 um 352 di } Il metodo run, richiesto dall’interfaccia Runnable, è in esecuzione su thread secondario mentre il download e la condivisione del TORRENT sono attivi. La routine rimane attiva fin quando il thread non è interrotto, evento che avviene nel metodo stopTorrent. Finché attivo, il thread interroga continuamente il DownloadManager corrente, estraendone il livello corrente di completamento dell’operazione, valore da 0 a 1000 e le velocità di download e upload dei dati. Ad ogni ciclo il thread esegue una pausa di 1000 millisecondi, un secondo, in modo da aggiornare le statistiche. @Override public void run() { while (!Thread.interrupted()) { DownloadManagerStats stats = downloadManager.getStats(); int completed = stats.getCompleted(); long dataReceiveRate = stats.getDataReceiveRate(); long dataSendRate = stats.getDataSendRate(); downloadProgressBar.setValue(completed); downloadRateLabel.setText("Download: " + (dataReceiveRate / 1024) + " KB/s"); uploadRateLabel.setText("Upload: " + (dataSendRate / 1024) + " KB/s"); try { Thread.sleep(1000); } catch (InterruptedException e) { break; } } } Il metodo main compie tre differenti operazioni. Per prima cosa forza l’utilizzo del look & feel di sistema, ove disponibile, in modo da far apparire l’applicazione più familiare per l’utente che la utilizza. In Swing c’è la possibilità di modificare l’aspetto e il comportamento dei widget grafici come i pulsanti, le caselle di testo, attraverso la classe javax.swing.UIManager e il suo metodo setLookAndFeel. La VM ingloba diversi temi ma uno è speciale, definito di sistema e richiamabile con il metodo getSystemLookAndFeelClassName di UIManager. Questo tema cerca la maggiore integrazione possibile con il SO sottostante, usando l’aspetto e le dimensioni standard che i vari widget hanno nel sistema ospite. Subito dopo imposta una cartella di lavoro per le API di Vuze/Azureus. La piattaforma ha bisogno di spazio su disco per memorizzare informazioni temporanee. La cartella utilizzata da Vuze può essere stabilita annotandone il percorso sulla proprietà di sistema azureus.config.path. La routine usa l’accortezza di generare una cartella temporanea diversa a ogni avvio del S/W, poiché il suo nome è stabilito ogni volta in maniera differente, usando l’orologio di sistema. Questa accortezza permette l’esecuzione simultanea di più istanze del S/W senza che le stesse vadano in conflitto tra di loro, per esempio si possono scaricare più torrent insieme, semplicemente avviando più di una volta l’applicazione. public static void main(String[] args) { TPSIT 5 434 um 353 di // applica il look & feel di sistema (se disponibile) try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { } // fa lavorare Azureus in una cartella temporanea final String workdir = System.getProperty("java.io.tmpdir") + File.separator + "bt." + System.currentTimeMillis(); System.setProperty("azureus.config.path", workdir); // avvia la GUI BTDownLoader gui = new BTDownLoader(); gui.setVisible(true); } } TPSIT 5 434 um 354 di IRC (INTERNET RELAY CHAT) INTRODUZIONE Sviluppato nel 1988 è un sistema di chat che si basa su un server centrale e su diversi client che si connettono a questo server. A loro volta i diversi server entrano in comunicazione fra loro formando una specie di circuito condiviso dagli stessi utenti connessi però a server diversi. Ogni utente sceglie un nickname con il quale è identificato univocamente su tutti i server. Esistono due tipi di moderazione. 1. OP Gli OP di un canale sono i proprietari o comunque quelli che gestiscono il canale, cambiano il TOPIC, l’argomento, settano limiti e decidono se una persona può rimanere nel canale o non. 2. VOICE I VOICE sono le uniche persone che possono parlare, oltre agli OP, quando il canale è settato in un modalità “moderata”. PircBot è un framework open source per sviluppare in Java dei bot IRC, il S/W “gestirà” un canale quando non si è connessi. Nel package org.jibble.pircbot si trova la definizione delle classi base del framework. La classe base da cui partire è PircBot, per far partire il primo bot e collegarlo al server IRC, in un canale, il codice è il seguente. import org.jibble.pircbot.*; public class HelloWorldBot extends PircBot { public HelloWorldBot(){ this.setName("Ciao, mondo bot!");} } Con il metodo setName s’impone il nickname che il bot utilizzerà sul server IRC. Il prossimo passo sarà collegare questo bot ad un server, farlo entrare in un canale e chiudere la connessione. import org.jibble.pircbot.*; public class BotExample { public static void main(String a[]){ HelloWorldBot hwb=new HelloWorldBot(); hwb.connect("irc.azzurra.org"); hwb.joinChannel("#javastaff"); hwb.sendMessage("#javastaff","Ciao a tutti"); hwb.disconnect(); } } Con il metodo connect si dice al bot a quale server si deve connettere. Una volta che la connessione è avvenuta si fa entrare il bot nel canale #javastaff. Il simbolo # è un prefisso che identifica i canali su IRC, ad esempio #java, #italia, #roma. Appena il bot entra nel canale si manda un messaggio pubblico su #javastaff con il TPSIT 5 434 um 355 di metodo sendMessage. Infine, ci si disconnette dal server e termina l’esecuzione. La cosa più interessante in un bot è la possibilità d’interagire con le persone che sono presenti nel canale. Per fare ciò si devono poter controllare i messaggi del canale dove il bot si trova e parsarli in maniera corretta. Il formato con cui i messaggi passano sul canale e i messaggi è il seguente. 1113949468186:[email protected] PRIVMSG #javastaff :ciao DeLiRiUm 1113949480624:[email protected] PRIVMSG #javastaff :che fai? Un messaggio di questo genere non è adatto a essere comprensibile e andrebbe “parsato” e “scomposto” per ottenere solo le informazioni che servono. Non è necessario riprogrammare il parser usando PircBot, di fatto esiste già il metodo OnMessage nella classe base che implementa un parser a tutti gli effetti. Attraverso il metodo OnMessage è facile usare dei trigger per riprogrammare i comportamenti del bot. public void onMessage(String channel, String sender,String login, String hostname, String message) { if (message.equals("Africa")) sendMessage(channel, "Celestino"); } In questo modo il bot quando intercetta la parola Africa all’interno di un messaggio, scriverà come reazione nel canale la parola Celestino, usando la primitiva sendMessage definita nella classe PircBot. Eclipse IDE for Java Per testare questo progetto si deve avere un collegamento a Internet. Creazione del bot TPSIT 5 434 um 356 di Dopo aver incluso le librerie, creare la classe che estende PircBot e nel main dell’applicazione inizializzare il bot. package bot; import org.jibble.pircbot.*; import java.util.*; import java.io.*; public class Delirium extends PircBot{ static Delirium bot; Vector canali,utenti,utentiIdentificati; File f; public Delirium() { utenti=new Vector(); caricaUtenti(); f=new File("regole.txt"); utentiIdentificati=new Vector(); } public static void main(String[] args) throws Exception { bot = new Delirium(); bot.setVerbose(true); bot.setName("DeLiRiUm"); bot.connect("localhost"); bot.channelJoin(); } Connessione al server Collegare il bot al server IRC passato come parametro al metodo connect. Effettuata la connessione si entra in tutti i canali che sono presenti in un testo. public void channelJoin() { canali=new Vector(); try{ BufferedReader br=new BufferedReader(new FileReader("I:/canali.txt")); String linea=br.readLine(); while(linea!=null) { bot.joinChannel("#"+linea); linea=br.readLine(); canali.add(linea); } br.close(); } catch(Exception e) { } } La prima parola Implementare il metodo onMessage, decidere un trigger, in risposta al quale il bot risponde nel canale. In questo caso si permette al bot di essere attivato dal trigger !REGOLE per poter inviare un file con le regole del canale a chi le ha richieste. public void onMessage(String channel, String sender,String login, String hostname, TPSIT 5 434 um 357 di String message) { StringTokenizer st=new StringTokenizer(message," "); String action=st.nextToken(); if (action.equals("!OP")) { opAction(st,sender,channel); } else if(action.equals("!DEOP")) { deopAction(st,sender,channel); } else if (action.equals("!REGISTER")) { sendMessage(channel,sender+" queste cose si fanno in query :)"); } else if (action.equals("!REGOLE")) { dccSendFile(f,sender,120000); } else if (action.equals("!GOOGLE")) { searchAction(st,sender,channel); } else if (action.equals("Africa")) { sendMessage(channel,"Celestino"); } else if (action.equals("!JAVA")) { javaAction(st,sender,channel); } else if (action.equals("!DIE")) { dieAction(st,sender,channel); } } Una volta definito come sarà rappresentato l’utente si deve decidere un metodo per far sì che gli utenti possano registrarsi. Per fare ciò l’utente dovrà scrivere in query, messaggio privato, al bot. Ridefinire il metodo onPrivateMessage, creando prima di tutto uno StringTokenizer basato sullo spazio, per ottenere così tutte le singole parole presenti nel messaggio che è passato. public void onPrivateMessage(String sender, String login, String hostname, String message) { StringTokenizer st=new StringTokenizer(message," "); String action=st.nextToken(); // INVIO EMAIL if (action.equals("!EMAIL")) { emailAction(st,sender); } // REGISTRAZIONE UTENTE else if(action.equals("!REGISTER")) { registerAction(st,sender); } else if(action.equals("!IDENTIFY")) { identifyAction(st,sender); } } TPSIT 5 434 um 358 di Caricare gli utenti Caricare una lista di utenti che si sono precedentemente registrati, si legge il vettore degli utenti direttamente da un file, salvati nella precedente sessione. public void caricaUtenti() { try { FileInputStream in = new FileInputStream("I:/user.ser"); ObjectInputStream s = new ObjectInputStream(in); utenti =(Vector) s.readObject(); s.close(); } catch(Exception e) { System.out.println(e.toString()); e.printStackTrace(); } } Gestione file Utilizzando IRC è possibile effettuare uno scambio diretto di file che è chiamato DCC (Direct Client to Client), utilizzato anche per parlare. Chiaramente anche il bot può utilizzare queste funzionalità, essendo lui stesso un client che si collega al server IRC. Esiste un metodo definito all’interno della classe PircBot che permette di avere la notifica del file in arrivo è onIncomingFileTransfer, dovrà essere implementato nel bot, per poter accettare i file in arrivo. Per l’invio si utilizza dccSendFile, indicando l’utente, il file e il timeout. public void onIncomingFileTransfer(DccFileTransfer transfer) { File file = transfer.getFile(); transfer.receive(file, true); } Il file accettato e ricevuto sarà copiato nella cartella dove è in esecuzione il bot, in questo modo si può anche creare un repository per tutti gli utenti in un canale. In generale la tecnica per inviare file è simile a quella della ricezione solo che si deve indicare una persona alla quale inviare il file. Gestione canali Il metodo joinChannel fa entrare il bot nel canale che è passato come parametro. Se si volesse far entrare automaticamente in una serie di canali, definiti in un file di testo, non si deve fare altro che scrivere un metodo che, chiamato dopo la connessione al server, legge da un file i nomi dei canali e richiede, uno per uno, di entrare nei canali. public void channelJoin() { canali=new Vector(); try{ BufferedReader br=new BufferedReader(new FileReader("I:/canali.txt")); String linea=br.readLine(); while(linea!=null) { bot.joinChannel("#"+linea); linea=br.readLine(); TPSIT 5 434 um 359 di canali.add(linea); } br.close(); } catch(Exception e) { } } Così tutti i canali che avranno l’onore di avere come ospite il bot saranno facilmente gestibili tramite questo file di testo. Altre funzionalità Si deve permettere al bot di svolgere le normali funzioni di utente IRC. Si può comandarlo per rendere altri utenti OP del canale o levare l’OP del canale a qualcuno. Nei canali IRC dove non si ha una gestione tramite bot o tramite particolari funzionalità di registrazione del canale sull’IRCD ci sono elevate probabilità di perderlo a causa di persone che cercano di rubarlo. Per gestire queste funzionalità base ci sono semplici metodi, già definiti in PircBot che devono essere richiamati, quindi basta inserire dei trigger nel metodo onMessage. Richiamando il metodo opAction e il metodo deopAction si comunica al server di rendere moderatore l’utente indicato. Tutti gli utenti possono registrarsi e identificarsi, quindi potrebbero rubare il canale. Infatti, si può pensare a una registrazione lato web oppure ad un solo utente, il primo che può registrare pian piano altri utenti. Questo dipende dalle esigenze del canale che si desidera gestire. public void opAction(StringTokenizer st,String sender,String channel) { String who=st.nextToken(); if (utentiIdentificati.contains(sender)) op(channel,who); } public void deopAction(StringTokenizer st,String sender,String channel) { String who=st.nextToken(); if (utentiIdentificati.contains(sender)) deOp(channel,who); } public void dieAction(StringTokenizer st,String sender,String channel) { if (utentiIdentificati.contains(sender)) { salvaUtenti(); disconnect(); } } public void identifyAction(StringTokenizer st,String sender) { try{ String pass=st.nextToken(); for (int i=0;i<utenti.size();i++) { User temp=(User)utenti.elementAt(i); if ((sender.equals(temp.getUsername()))&&(pass.equals(temp.getPassword()))) { if (!utentiIdentificati.contains(sender)) utentiIdentificati.add(sender); TPSIT 5 434 um 360 di sendMessage(sender,"Utente identificato"); return; } } sendMessage(sender,"Errore nell’identificazione"); } catch(Exception e) { } } Con il metodo salvaUtenti si registra l’utente aggiungendolo al vettore degli utenti, è richiamato prima della disconnessione del bot dal server IRC, per salvare sul file il vettore di utenti aggiornati. public void salvaUtenti() { try { FileOutputStream out = new FileOutputStream("I:/user.ser"); ObjectOutputStream s = new ObjectOutputStream(out); s.writeObject(utenti); s.close(); } catch(Exception e) { System.out.println(e.toString()); } } Ricerche con Google Sviluppare un’estensione che s’interfacci al motore di ricerca Google, per poter fare le ricerche anche quando si è in chat con altre persone. Si usano le Google WebAPI, ovvero delle API che permettono di effettuare ricerche su Google grazie ad un WS che è esposto. Una volta registrati sul sito http://www.google.com/apis/ è inviata via email una chiave da utilizzare nell’applicazione che serve per limitare l’accesso al WS, offrendo un massimo di 1000 ricerche al giorno. Quindi prima d’interfacciare Google con il bot costruire una classe WebSearch per effettuare la vera e propria ricerca, come parametro si passano le parole inviate dall’utente nel canale. Si comunicano i risultati tramite la primitiva sendMessage. Oltre a istanziare GoogleSearch bisogna settare la chiave che è stata inviata via email con il metodo setKey. Poi si deve settare un proxy e relativa porta, attraverso il quale si desidera effettuare la ricerca e infine con il metodo setQueryString si setta la stringa che sarà ricercata su Google. File WEBSEARCH.JAVA package bot; import com.google.soap.search.GoogleSearch; import com.google.soap.search.GoogleSearchResult; import com.google.soap.search.GoogleSearchResultElement; import com.google.soap.search.GoogleSearchFault; import java.util.*; public class WebSearch{ TPSIT 5 434 um 361 di int tipo; GoogleSearch search; Vector toReturn; public WebSearch(int tipo) { this.tipo=tipo; search = new GoogleSearch(); // search.setProxyHost("192.168.1.1"); // search.setProxyPort(3128); search.setKey("DACAMBIARECONLACHIAVECHEVIENEINVIATA"); toReturn=new Vector(); } public Vector search(String toSearch) { try { if (tipo==0) { search.setQueryString(toSearch); GoogleSearchResult result = search.doSearch(); GoogleSearchResultElement[] re = result.getResultElements(); for ( int i = 0; i < re.length; i++ ) { toReturn.add(re[i].getURL()); } return toReturn; } else return searchAdvanced(toSearch); } catch(Exception e) { return toReturn; } } public Vector searchAdvanced(String toSearch){ String toSearch1="site:www.javaworld.com "+toSearch; String toSearch2="site:www.javalobby.org "+toSearch; String toSearch3="site:www.alphaworks.ibm.com "+toSearch; tipo=0; Vector temp=search(toSearch1); temp=search(toSearch2); temp=search(toSearch3); return temp; } } Definire il metodo searchAction richiamando questa classe appena implementata. Praticamente si ha un vettore come risultato, quindi con questo metodo basterà eseguire un ciclo for e stampare nel canale tutti i risultati. public void searchAction(StringTokenizer st,String sender,String channel) { try{ String toSearch=st.nextToken(); while(st.hasMoreTokens()) toSearch=toSearch+" "+st.nextToken(); WebSearch ws=new WebSearch(0); Vector result=ws.search(toSearch); TPSIT 5 434 um 362 di sendMessage(channel,sender+" ecco i risultati della tua ricerca"); for (int i=0;i<result.size();i++) { String temp=(String)result.elementAt(i); sendMessage(channel,temp); } } catch(Exception e) { } } Riconoscere gli utenti Uno dei principali motivi per cui si crea un bot su dei canali IRC è per gestire il canale, anche quando non sono presenti i veri moderatori del canale. Allora bisogna implementare un vero e proprio sistema d’identificazione che permetterà soltanto a determinati utenti di utilizzare il bot. Occorre una classe dove implementare i metodi get e set per ogni variabile, inoltre, bisogna implementare l’interfaccia Serializable, per serializzare un vettore di utenti e ricaricarlo all’avvio. File USER.JAVA package bot; import java.util.*; import java.io.*; public class User implements Serializable{ String username,password,email,dir; int level; public User(String username,String password,String email,int level,String dir) { this.username=username; this.password=password; this.email=email; this.level=level; this.dir=dir; } public String getMail() { return email; } public void setMail(String email) { this.email=email; } public String getUsername() { return username; } public void setUsername(String username) { this.username=username; } public String getPassword() { return password; } public void setPassword(String password) { this.password=password; } public int getLevelAut() { TPSIT 5 434 um 363 di return level; } public void setLevelAut(int level) { this.level=level; } public void setDir(String dir) { this.dir=dir; } public String getDir() { return dir; } } File EMAILSENDER.JAVA package bot; import java.util.*; import javax.mail.*; import javax.mail.internet.*; public class EmailSender { String smtp,destinatario,mittente,oggetto,corpo; public EmailSender(String smtp,String destinatario, String mittente,String oggetto, String corpo) { this.smtp=smtp; this.destinatario=destinatario; this.mittente=mittente; this.oggetto=oggetto; this.corpo=corpo; } public String send() { try{ Properties props = System.getProperties(); props.put("mail.smtp.host", smtp); Session session = Session.getDefaultInstance(props, null); Message msg = new MimeMessage(session); msg.setFrom(new InternetAddress(mittente)); msg.setRecipients(Message.RecipientType.TO,InternetAddress.parse(destinatario, false)); msg.setSubject(oggetto); msg.setText(corpo); msg.setHeader("X-Mailer", "Delirium Irc Bot"); msg.setSentDate(new Date()); Transport.send(msg); return "Mess inviato"; } catch(Exception e) { return e.toString(); } } } TPSIT 5 434 um 364 di TPSIT 5 434 um 365 di SECOND LIFE INTRODUZIONE È un mondo virtuale 3D, progettato nel 2003 dalla società americana Linden Lab fondata nel 1999 da Philip Rosedale la sua sede è a San Francisco, interamente costruito e posseduto dai suoi residenti, raggiungibile all’indirizzo http://www.secondlife.com, costruito con dei “mattoncini” in cui vivono gli alter ego, gli “avatar”. “Un nuovo mondo, una seconda vita!” Il sistema fornisce ai propri utenti, i “residenti”, gli strumenti per aggiungere e creare nel mondo nuovi contenuti grafici: oggetti, fondali, fisionomie dei personaggi e audiovisivi. Second LIFE gira su più di 2000 server basati su processori INTEL (INTegrated ELectronics) o AMD (Advanced Micro Devices), utilizzano come SO la distribuzione Linux Debian e come DB MySQL. L’architettura è di tipo client server, il client è stato rilasciato dalla Linden Lab in licenza GPL, il server, Simulator o SIM, è di proprietà della Linden Lab. Il protocollo di comunicazione tra i server e i client costituisce la SecondLifeGrid che mette in comunicazione i simulator tra loro, anche la Grid è di proprietà della Linden Lab. La peculiarità del mondo di Second LIFE è quella di lasciare agli utenti la libertà di usufruire dei diritti d’autore sugli oggetti che essi creano che possono essere venduti e scambiati tra i residenti utilizzando una moneta virtuale, il Linden Dollar che può essere convertito in veri dollari statunitensi e anche in euro. Avatar È un’immagine scelta per rappresentare l’utenza nelle comunità virtuali, luoghi di aggregazione, discussione o di gioco online. La parola che è in lingua sanscrita, è originaria della tradizione induista, nella quale ha il significato d’incarnazione, di assunzione di un corpo fisico da parte di un dio, Avatar è “Colui che discende” per traslazione metaforica, nel gergo di Internet s’intende che una persona reale che scelga di mostrarsi agli altri, lo faccia attraverso una propria rappresentazione, un’incarnazione: un avatar appunto. Tale immagine che può variare per tema e per grandezza può raffigurare un personaggio di fantasia, della realtà, o anche temi più vari, come vignette comiche, testi e altro. TPSIT 5 434 um 366 di Titolo originale Paese Anno Durata Genere Regia Sceneggiatura Casa di produzione Fotografia Montaggio Musiche Scenografia Interpreti e personaggi Sam Worthington: Zoë Saldaña: Sigourney Weaver: Giovanni Ribisi: Michelle Rodríguez: Avatar Stati Uniti 2009 162 min Fantascienza, avventura, drammatico James Cameron James Cameron 20th Century Fox, Giant Studios, Lightstorm Entertainment Mauro Fiore James Cameron, John Refoua, Stephen E. Rivkin James Horner Rick Carter, Martin Laing, Robert Stromberg Jake Sully Neytiri Dr. Grace Augustine Parker Selfridge Trudy Chacón Tutto ciò che che si vede nel mondo è costruito a partire da componenti elementari, i prim e reso dinamico utilizzando il linguaggio di programmazione LSL. Se si vuole creare una vita virtuale, si deve scaricare e installare sul PC il client che si trova all’indirizzo http://http://secureweb11.secondlife.com/community/downloads.php e iscriversi, fornire le “seconde” generalità nome e cognome, la password, quindi premere sul tasto Connect.... In Second LIFE ciò che si vede è l’avatar che si può personalizzare nell’aspetto e si può muovere utilizzando le frecce direzionali della tastiera. LSL (Linden Scripting Language) Il client contiene al suo interno tutto ciò che serve, se si fa clic sul pulsante Build, in basso, il mouse si trasforma in “bacchetta magica”; se si fa un clic sul terreno di fronte, appare un cubo di legno e il relativo dialogo di progettazione; se si fa clic sulla linguetta Content, ci si posiziona nella zona in cui porre gli script. Fare clic sul pulsante New Script... il sistema crea lo script che si può analizzare se si fa clic sulla relativa icona. LSL è un linguaggio per la programmazione di una macchina a stati finiti guidata dagli TPSIT 5 434 um 367 di eventi, la sintassi è C-like. Di ogni oggetto, infatti, si deve programmare la sequenza degli stati che l’oggetto potrà assumere e gli eventi che saranno scaturiti dal/nell’oggetto. LSL offre una libreria di oltre 300 funzioni che consentono di manipolare in ogni modo gli oggetti del mondo. Il client consente da solo di sviluppare codice ma è meglio utilizzare un editor, per esempio LSLEditor che fornisce un intellisense tipo Visual Studio, colorazione del codice e un debugger, si trova all’indirizzo http://www.lsleditor.org/. È definito un solo stato, default che è lo stato predefinito e che dev’essere sempre presente in uno script LSL. Sono definiti due gestori degli eventi. state_entry Il primo evento è scatenato quando si entra nello stato default, ovvero quando lo script è compilato; in questo caso, dato che non sono definiti ulteriori stati, il codice del metodo è eseguito solo la prima volta, normalmente si utilizza questo metodo per inizializzare piuttosto che per modificare l’aspetto dell’oggetto stesso. touch_start Restando nello stato default, il metodo è eseguito ogni qualvolta è toccato l’oggetto; per reagire al tocco si utilizza una delle funzioni di libreria. Tutte le funzioni di libreria cominciano per ll (Linden Library); la funzione llSay è utilizzata per comunicare con l’esterno, detto in altre parole per eseguire una azione di chat. Il primo parametro è il canale su cui è inviato il messaggio che si passa al metodo come secondo parametro. Ci sono ben 4294967294 canali disponibili nell’intervallo che va da 2147483648 a 2147483647; il canale 0 è l’unico canale utilizzabile per mandare messaggi tra avatar e corrisponde alla chat. Tutti gli altri canali sono utilizzati dagli script per la comunicazione tra oggetti. Se si esce dalla modalità di progettazione dell’oggetto, clic sulla X in alto a destra del dialogo di progettazione e si fa clic sull’oggetto, nella zona in basso a sinistra appare il messaggio che c’invia l’oggetto. TPSIT 5 434 um 368 di Per coloro che non posseggono un proprio terreno in cui fare le prove, esistono delle zone franche, dette sandbox, in cui si possono creare oggetti e provare script. Una di queste sandbox è raggiungibile al seguente indirizzo: Volksland 9, 248, 21 gli oggetti che si creano sono posti nella cartella Lost And Found dell’Inventory, dopo alcuni minuti di utilizzo al fine di mantenere la sandbox pulita; per continuare le prove basta trascinare nuovamente l’oggetto sul terreno. File PORTA SCORREVOLE.LSL Codice per programmare una porta scorrevole a chiusura automatica, si possono vedere l’uso degli stati e le varie transizioni. La porta può assumere quattro diversi stati. Lo stato predefinito, default, corrisponde alla porta chiusa. La porta si deve aprire se si fa clic sull’anta, il metodo touch_start effettua una transizione di stato ponendo la porta nello stato in apertura, opening. Sia per cambiare stato sia per definire uno stato si usa la parola chiave state. default { // evento scatenato dal tocco dell’avatar touch_start(integer total_number) { // cambiamo stato state opening; } } Dichiarare un nuovo stato racchiudendo tra parentesi graffe ({}), i gestori degli eventi che interesseranno l’oggetto nello stato che si sta definendo. Lo stato opening rappresenta l’atto dell’apertura della porta. La porta resta in questo stato solo il tempo necessario alla sua apertura. Si deve precisare che le animazioni relative al cambio di posizione degli oggetti, sono eseguite automaticamente dal simulatore di Second LIFE. // la porta si sta aprendo state opening { state_entry() { // ricaviamo la posizione corrente della porta vector pos = llGetPos(); // togliamo dalla componente trasversale la lunghezza della porta pos.y = pos.y - 2.000; // imponiamo la nuova posizione llSetPos(pos); // cambiamo stato state open; } } Per lo stato opening si deve codificare solo il metodo state_entry in cui si va a ricavare la posizione corrente della porta attraverso la funzione di libreria llGetPos; la posizione è rappresentata da un vettore che esprime le coordinate cartesiane del centro dell’oggetto. Quindi si va a togliere dalla componente trasversale della porta la sua lunghezza; infine, si setta la nuova posizione della porta con un’altra funzione di libreria, llSetPos. La porta sarà spostata nella nuova posizione con un effetto movimento creato dal TPSIT 5 434 um 369 di simulatore. Dopo aver imposto la nuova posizione, si cambia nuovamente stato, ponendo la porta nello stato aperto, open. La componente da aggiungere dipende dall’orientamento iniziale della porta; ciò vuol dire che se si orienta la porta lungo l’asse delle ascisse X, la componente su cui operare sarà, appunto, la componente X e il codice sarà modificato in: pos.x = pos.x - 2.000;. // la porta è aperta state open { state_entry() { // creiamo un timer di 2 secondi llSetTimerEvent(2.0); } // evento scatenato dal timer timer() { // cancelliamo il timer llSetTimerEvent(0); // cambiamo stato state closing; } } Lo stato open usa la funzione di libreria: llSetTimerEvent che consente d’implementare la chiusura automatica della porta; la funzione è utilizzata per scatenare un evento di tipo timer ogni qualvolta trascorre l’intervallo di tempo specificato come parametro della funzione. In questo caso, dopo due secondi è invocato il metodo timer che, in primo luogo annulla il timer, passando alla funzione llSetTimerEvent il valore zero, quindi esegue una nuova transizione di stato che pone la porta in chiusura, closing. // la porta si sta chiudendo state closing { state_entry() { // ricaviamo la posizione corrente della porta vector pos = llGetPos(); // aggiungiamo alla componente trasversale la lunghezza della porta pos.y = pos.y + 2.000; // imponiamo la nuova posizione llSetPos(pos); // cambiamo stato state default; } } Nello stato closing non si deve fare altro che compiere il processo inverso rispetto al metodo opening. Impostare quindi la posizione della porta al valore iniziale e portarsi nuovamente nello stato di default. File SEMAFORO BASE.LSL L’esempio precedente usava uno script associato ad un singolo elemento, prim ma gli oggetti più complessi necessitano una composizione di più prim. TPSIT 5 434 um 370 di Per esempio, un semaforo è composto da un supporto più le tre lampade dei colori. La composizione grafica del semaforo è semplice; basta creare tre cilindri e porli sopra una base rappresentata da un parallelepipedo. Per creare un unico oggetto è sufficiente selezionare ogni prim, quindi selezionare la voce Tools/Link. È importante definire l’ordine con cui si selezionano i prim dato che l’ultimo selezionato prima dell’operazione di link sarà il root prim che regolerà il comportamento dell’intero oggetto. Programmare il semaforo affinché riproduca il ciclo semaforico dei segnali verde, giallo e rosso. In questo caso, si vuole che i singoli prim interagiscano tra loro per sincronizzarsi a vicenda. In questo modo, il rosso comunica al verde quando accendersi; stessa cosa fa il verde nei confronti del giallo che, infine, a sua volta comunica con il rosso. Esistono diverse modalità di comunicazione tra prim; in alcune situazioni si utilizza la funzione di libreria llSay o llShout se si vuole comunicare a distanze superiori. Si deve specificare un canale diverso dallo 0 usato per la chat. Normalmente questa modalità è utilizzata per mettere in comunicazione prim fisicamente distanti come nel caso di un ascensore. Nel caso in cui i prim sono linkati si preferisce utilizzare la funzione di libreria llMessageLinked che invia messaggi solo ai prim che compongono l’oggetto; ciò mette al riparo da eventuali interferenze che potrebbero sorgere a causa di altri oggetti che, accidentalmente, comunicano sullo stesso canale. Stabilire che l’accensione del semaforo è scatenata dalla base che, in fase di avvio dell’oggetto, invia il primo messaggio ai prim. default { state_entry() { // inviamo il messaggio di "accensione rosso" all’insieme di prim llMessageLinked(LINK_SET, 0, "rosso", NULL_KEY); } } La funzione di libreria llMessageLinked ha diversi parametri; in questo caso il terzo parametro rappresenta il messaggio che è inviato ovvero la lampada da accendere. Il primo parametro rappresenta il destinatario del messaggio; la parola chiave LINK_SET indica che il messaggio sarà recapitato a tutti i prim dell’insieme. Il codice presente nelle tre lampade è simile; nello stato predefinito si mette all’ascolto dei messaggi linkati per mezzo del metodo link_message. Ogni qualvolta arriva un messaggio si verifica se è diretto al prim che si sta codificando; in caso positivo si attiva una transizione di stato, ovvero si accende la lampada, in caso negativo si spegne la lampada. File LAMPADA ROSSO.LSL default { // ascoltiamo i messaggi inviati all’insieme dei prim link_message(integer sender_num, integer num, string str, key id) { // se il messaggio è indirizzato all’oggetto if(str == "rosso") { // andiamo allo stato di accensione della lampada state accesa; } TPSIT 5 434 um 371 di Else { // spegnamo la lampada llSetAlpha(0.0, ALL_SIDES); } } } Per accendere e spegnere la lampada si usa la funzione di libreria llSetAlpha che definisce la trasparenza/opacità dell’oggetto. Quando si deve spegnere la lampada s’impone il valore del primo parametro a 0. Il secondo parametro della funzione serve per definire su quali facce dell’oggetto si deve imporre il valore di opacità; la parola chiave ALL_SIDES impone il valore in ogni faccia. // stato di accensione della lampada state accesa { state_entry() { // accendiamo la lampada llSetAlpha(1.0, ALL_SIDES); // imponiamo la durata dell’accensione della lampada llSetTimerEvent(3); } timer() { // cancelliamo il timer llSetTimerEvent(0); // informiamo l’insieme di prim che bisogna accendere il "verde" llMessageLinked(LINK_SET, 0, "verde", NULL_KEY); // spegniamo la lampada llSetAlpha(0.0, ALL_SIDES); // ci riposizioniamo nello stato predefinito state default; } } Nello stato accesa, come prima operazione si accende la lampada passando al metodo llSetAlpha il valore 1 come primo parametro. Contestualmente si attiva un timer llSetTimerEvent che rappresenta la durata dell’accensione della lampada. Trascorso il tempo prestabilito, tramite il metodo timer si devono effettuare le seguenti operazioni: annullare il timer, inviare un messaggio ai prim relativo alla successiva lampada da accendere, spegnere la lampada attuale, riposizionarsi nello stato di default. TPSIT 5 434 um 372 di File LAMPADA VERDE.LSL default { // ascoltiamo i messaggi inviati all’insieme dei prim link_message(integer sender_num, integer num, string str, key id) { // se il messaggio è indirizzato all’oggetto if(str == "verde") { // andiamo allo stato di accensione della lampada state accesa; } else { // spegnamo la lampada llSetAlpha(0.0, ALL_SIDES); } } } // stato di accensione della lampada state accesa { state_entry() { // accendiamo la lampada llSetAlpha(1.0, ALL_SIDES); // imponiamo la durata dell’accensione della lampada llSetTimerEvent(3); } timer() { // cancelliamo il timer llSetTimerEvent(0); // informiamo l’insieme di prim che bisogna accendere il "verde" llMessageLinked(LINK_SET, 0, "giallo", NULL_KEY); // spegniamo la lampada llSetAlpha(0.0, ALL_SIDES); // ci riposizioniamo nello stato predefinito state default; } } File LAMPADA GIALLO.LSL default TPSIT 5 434 um 373 di { // ascoltiamo i messaggi inviati all’insieme dei prim link_message(integer sender_num, integer num, string str, key id) { // se il messaggio è indirizzato all’oggetto if(str == "verde") { // andiamo allo stato di accensione della lampada state accesa; } else { // spegnamo la lampada llSetAlpha(0.0, ALL_SIDES); } } } // stato di accensione della lampada state accesa { state_entry() { // accendiamo la lampada llSetAlpha(1.0, ALL_SIDES); // imponiamo la durata dell’accensione della lampada llSetTimerEvent(3); } timer() { // cancelliamo il timer llSetTimerEvent(0); // informiamo l’insieme di prim che bisogna accendere il "verde" llMessageLinked(LINK_SET, 0, "giallo", NULL_KEY); // spegniamo la lampada llSetAlpha(0.0, ALL_SIDES); // ci riposizioniamo nello stato predefinito state default; } } File PULSANTE0.LSL Non tutti gli oggetti possono essere rappresentati in modo compatto linkando più prim. Nel caso di sistemi più complessi, le varie componenti possono trovarsi distribuite nello spazio come nel caso di un ascensore che è formato dalla cabina e dai pulsanti ai piani da utilizzare per chiamare l’ascensore. default { state_entry() { // creiamo un testo flottante al di sopra dell’oggetto llSetText("Chiamata",<0,0,0>,1.0); } touch_start(integer total_number) { // "gridiamo" sul canale 999 il messaggio "1" llShout(999, "0"); } } Per dare un aiuto agli avatar si deve mostrare un testo flottante in corrispondenza del tasto tramite la funzione di libreria llSetText. TPSIT 5 434 um 374 di Quindi si deve gestire la pressione del pulsante di chiamata codificando il metodo touch_start; in questo caso si usa la funzione di libreria llShout in modo che anche in un edificio alto le chiamate possano essere ascoltate dalla cabina. Si è scelto il canale 999 per trasmettere i messaggi rappresentati dal numero del piano cui deve portarsi la cabina. File PULSANTE1.LSL default { state_entry() { // creiamo un testo flottante al di sopra dell’oggetto llSetText("Chiamata",<0,0,0>,1.0); } touch_start(integer total_number) { // "gridiamo" sul canale 999 il messaggio "1" llShout(999, "1"); } } File PULSANTE2.LSL default { state_entry() { // creiamo un testo flottante al di sopra dell’oggetto llSetText("Chiamata",<0,0,0>,1.0); } touch_start(integer total_number) { // "gridiamo" sul canale 999 il messaggio "1" llShout(999, "2"); } } File PULSANTE3.LSL default { state_entry() { // creiamo un testo flottante al di sopra dell’oggetto llSetText("Chiamata",<0,0,0>,1.0); } touch_start(integer total_number) { // "gridiamo" sul canale 999 il messaggio "1" llShout(999, "3"); } } File CABINA.LSL Si deve gestire sia la chiamata ad un piano sia il comando inviato dall’interno della cabina, dall’avatar che sale o scende. Presupporre che la cabina, indipendentemente dalla sua forma, possegga una seduta che consentirà all’avatar che si siede, di scegliere il piano cui portarsi. Lo script dovrà essere posto all’interno della seduta che dovrà essere l’ultimo oggetto selezionato prima di linkare tutti i componenti della cabina ovvero il root prim dell’oggetto composito. Definire due variabili globali: una lista che contiene le altezze dei vari piani in modo da poter individuare immediatamente dove muovere la cabina e un’ulteriore lista per TPSIT 5 434 um 375 di contenere la lista dei piani. Le ulteriori variabili globali servono per definire la velocità con cui si muove la cabina, il piano e l’altezza attuale della cabina. Le variabili globali vanno dichiarate in testa allo script e sono accessibili in ogni metodo di ogni stato all’interno dello script. Nel metodo state_entry dello stato predefinito, default, tramite la funzione di libreria llListen, ci si pone in ascolto dei messaggi inviati sul canale 999, quello dei pulsanti ai piani. Su questo canale si devono trasmettere anche i comandi della pulsantiera della cabina. La funzione di libreria llSitTarget, predispone la seduta ad accogliere opportunamente l’avatar, accomodandolo esattamente sulla seduta e fronte verso l’esterno. L’ultima istruzione indica all’avatar di sedersi. list ALTEZZE = [28, 29, 30, 31]; list PIANI = ["0", "1", "2", "3"]; float SPEED = 0.1; integer piano; integer altezza; default { state_entry() { // ci poniamo all’ascolto del canale 999 llListen(999, "", NULL_KEY, ""); // facciamo in modo che l’avatar si segga corettamente llSitTarget(<0,-0.5,0.5>, llEuler2Rot(<0,0,-90>) ); // messaggio informativo per l’avatar llSetText("Sedersi prego",<0,0,0>,1.0); } // metodo che riceve i messaggi inviati dagli altri oggetti listen(integer channel, string name, key id, string message) { // verifichiamo che il messaggio proviene dal canale 999 if(channel == 999) { // impostiamo il piano a cui portare la cabina piano = (integer)message; // cambiamo stato state moving; } } // metodo scatenato dall’avatar che si siede in cabina changed(integer Change) { // mostriamo all’avatar la lista dei piani llDialog(llAvatarOnSitTarget(), "Scegliere il piano", PIANI, 999); } } Nello stato predefinito della cabina, si deve altresì codificare il metodo di ascolto dei messaggi, listen e il metodo scatenato dall’avatar che si siede in cabina, changed. Nel primo metodo, dopo aver verificato che il messaggio provenga dal canale 999, si valorizza la variabile globale piano con il valore del messaggio che contiene, appunto, il piano cui è stata richiesta la cabina; quindi posizionarsi sullo stato moving. Nel metodo changed che si accorge che un avatar si è seduto in cabina, si usa una funzione di libreria che consente d’interagire con gli avatar llDialog che mostra un dialogo ad un avatar, dove sono presenti diversi pulsanti che possono essere usati per consentire TPSIT 5 434 um 376 di una scelta. La funzione accetta quattro parametri: la key dell’avatar cui mostrare il dialogo, un messaggio esplicativo, una lista di opzioni che saranno trasformate in pulsanti e un canale su cui trasmettere la scelta effettuata. Affinché il dialogo sia mostrato effettivamente all’avatar che si siede in cabina, si usa la funzione di libreria llAvatarOnSitTarget che restituisce la key dell’avatar che è attualmente seduto sull’oggetto. Il canale che si utilizza per trasmettere la scelta dell’avatar è proprio il 999 dove sono trasmessi anche i comandi dei pulsanti ai piani. In questo modo si è centralizzata la gestione del movimento della cabina che, tramite il metodo listen, gestirà sia i messaggi provenienti dai piani sia quelli provenienti dalla cabina. // la cabina si sta muovendo state moving { state_entry() { // definiamo un timer che scatta ogni decimo di secondo llSetTimerEvent(0.1); // ricaviamo l’altezza cui portare la cabina altezza = llList2Integer(ALTEZZE, piano); } // metodo invocato ogni decimo di secondo timer() { // ricaviamo la posizione attuale della cabina vector pos = llGetPos(); // se non siamo arrivati al piano … if( pos.z!=altezza ) { // se la cabina è più in alto del piano a cui portarsi … if( pos.z>altezza ) { // facciamo scendere la cabina pos.z = pos.z - SPEED; } else { // facciamo salire la cabina pos.z = pos.z + SPEED; } } // se siamo in un "intorno" del piano inferiore allo step di movimento if( llFabs(pos.z - altezza) < SPEED ) { // definiamo precisamente la posizione della cabina pos.z = altezza; // azzeriamo il timer llSetTimerEvent(0); // applichiamo la posizione alla cabina llSetPos(pos); // informiamo l’avatar che siamo arrivati llSay(0,"Ascensore al piano" ); // ci posizioniamo nello stato predefinito state default; } // applichiamo la posizione alla cabina llSetPos(pos); TPSIT 5 434 um 377 di } } Per implementare il movimento della cabina, nel metodo state_entry dello stato moving, definire un timer che scatta ogni decimo di secondo. Nel corrispondente metodo timer, fare un’operazione di confronto tra l’altezza attuale della cabina, tramite la componente Z della sua posizione attuale e l’altezza cui la si deve portare. La differenza tra queste due grandezze consente di aggiungere o togliere uno step di movimento, definito tramite la variabile globale SPEED. Dato che questa operazione è eseguita ripetutamente ciò che si ricava è un movimento abbastanza fluido della cabina. Quando si è in corrispondenza del piano, ovvero quando la differenza tra la posizione attuale della cabina e l’altezza cui si deve portarla è minore, in valore assoluto, allo step di movimento, vuol dire che si è arrivati. In questa situazione si azzera il timer e s’informa l’avatar che si è arrivati. L’implementazione del movimento si può gestire indicando la posizione della cabina con il valore dell’altezza cui si deve portarla. L’effetto collaterale è che l’animazione che il simulatore produce a fronte di una semplice variazione di posizione è estremamente rapida. Questo vuol dire che ogni volta si è “sparati” al piano piuttosto che “portati”. Per questa ragione si è parcellizzato il movimento, scomponendolo in tanti piccoli movimenti. Per provare gli script non si deve necessariamente associarli agli oggetti che in produzione saranno animati da ciò che si produce. Nel caso dell’ascensore, per esempio, non si deve, per forza, creare un palazzo, una serie di piani, una cabina più o meno verosimile. Per provare lo script è sufficiente lavorare con i cubi che chiunque è in grado di costruire. Ad ogni cubo può essere associato il relativo script e quindi fare le prove del caso. Nella figura è presente l’ascensore e i pulsanti di chiamata tutti rappresentati da cubi. Il risultato grafico è tutt’altro che esaltante ma si riescono a testare le funzionalità. Questo consente di essere sicuri del risultato quando si metteranno gli script negli oggetti creati dai grafici. File PORTA CARDINI.LSL Progettare una classica porta che ruota attorno ai sui suoi cardini, bisogna studiare come far ruotare i corpi in LSL. TPSIT 5 434 um 378 di Si usa un oggetto composito formato dall’anta della porta e dai suoi cardini. Si possono rappresentare questi ultimi come un minuscolo parallelepipedo che si accosta ad uno dei lati dell’anta. Premesso che l’oggetto composito ha come root prim, i cardini, si mette proprio in questo oggetto il codice. Nello stato predefinito, default, considerare la porta chiusa. Per aprire la porta basta toccare la sua anta che scatena l’evento touch_start. Come prima operazione memorizzare la rotazione attuale della porta nella variabile globale rot, quindi eseguire una transizione di stato che posiziona l’oggetto nello stato opening. // variabile globale per memorizzare la rotazione attuale della porta rotation rot; // variabile globale per memorizzare la rotazione per aprire/chiudere la porta rotation delta; default { // evento scatenato dal tocco dell’avatar touch_start(integer total_number) { // memorizziamo la rotazione attuale della porta rot = llGetLocalRot(); // cambiamo stato state opening; } } Nello stato opening definire la rotazione che deve compiere la porta quando si apre. In questo caso, si usa la funzione di libreria llEuler2Rot che trasforma una rotazione espressa tramite un vettore in cui s’indicano gli angoli di rotazione attorno agli assi, in una variabile di tipo rotation che esprime la rotazione tramite una quaterna di valori. Per definire una rotazione longitudinale, ovvero attorno all’asse Z di 90 gradi, si passa alla funzione un vettore con le prime due componenti a zero e con la terza componente valorizzata con la parola chiave PI/2. L’effettiva rotazione si calcola moltiplicando il valore ottenuto dalla funzione, per la rotazione attuale. Tramite la funzione di libreria llSetRot, si applica la rotazione che è eseguita, come le transizioni longitudinali, in modo fluido dal simulatore. // la porta si sta aprendo state opening { state_entry() { // definiamo una rotazione di 90 gradi attorno all’asse Z delta = llEuler2Rot(<0.0,0.0,PI/2>); // calcoliamo la rotazione sulla base della rotazione attuale rot = delta * rot; // applichiamo la rotazione llSetRot(rot); // cambiamo stato state open; } } Dato che anche in questo caso si vuole dotare la porta di chiusura automatica, nello stato TPSIT 5 434 um 379 di open impostare un timer che rappresenta il tempo in cui la porta resta aperta. Trascorso il periodo di tempo impostato, posizionarsi nello stato close che compie la rotazione inversa e riposiziona la porta nel suo stato predefinito, default. // la porta è aperta state open { state_entry() { // creiamo un timer di 2 secondi llSetTimerEvent(2.0); } // evento scatenato dal timer timer() { // cancelliamo il timer llSetTimerEvent(0); // cambiamo stato state closing; } } // la porta si sta chiudendo state closing { state_entry() { // definiamo una rotazione di meno 90 gradi attorno all’asse Z delta = llEuler2Rot(<0.0,0.0,-PI/2>); // calcoliamo la rotazione sulla base della rotazione attuale rot = delta * rot; // applichiamo la rotazione llSetRot(rot); // ci riposizioniamo nello stato predeinito state default; } } La parola usata per indicare la creazione di un oggetto, in realtà è un termine coniato negli anni ‘80 quando, nel film Tron, la parola “de-rezz” era utilizzata per indicare che un oggetto del mondo 3D era distrutto o “de-resolved”, la parola usata al contrario, quindi senza il prefisso “de”, indica, quindi, la creazione di un oggetto. Fase di creazione di un oggetto, in gergo la fase di rez. Ogni oggetto creato in Second LIFE è individuato univocamente dalla sua chiave che è un identificatore univoco UUID (Universal Unique IDentifier), rappresentato da una stringa di numeri esadecimali di 36 caratteri nel formato 00000000-0000-0000-0000-000000000000, per esempio 66864f3c-e095-d9c8-058dd6575e6ed1b8. Questa chiave è la via per comunicare con l’oggetto, dato che tutte le funzioni di libreria, utili per la comunicazione, prendono come primo parametro proprio la chiave del destinatario. Normalmente, una volta che sviluppato l’oggetto, sia in termini grafici sia di funzionalità interattive, lo si ripone nell’inventario per poterlo riutilizzare in seguito. Questa operazione nasconde un’insidia che dev’essere analizzata per evitare malfunzionamenti degli script che si basano sulle chiavi degli oggetti. In effetti l’inventario dell’avatar non è una semplice borsa in cui porre gli oggetti. In verità, quando si pone un oggetto nell’inventario, clic con il tasto destro del mouse sull’oggetto, quindi clic sulla voce Take si cancella l’oggetto dal terreno e si porta TPSIT 5 434 um 380 di nell’inventario un suo modello; ogni qualvolta si estrae nuovamente l’oggetto dall’inventario, si crea una nuova copia. Nell’OOP (Object oriented Programming) è come se si ponesse nell’inventario una classe, e ogni volta che si estrae l’oggetto si crea una sua istanza. Queste precisazioni sono molto importanti, dato che la chiave dell’oggetto cambia ogni volta, in realtà è creato un nuovo oggetto, per cui se si progettano script basati su chiavi occorre stare attenti ai processi di rez e de-rez. File REZIT.LSL Progettare un oggetto. default { // scatenato dal tocco di un avatar touch_start(integer total_number) { // invia un messaggio istantaneo al proprietario dell’oggetto llOwnerSay((string)llGetKey()); } // scatenato ogni volta che l’oggetto è creato dall’inventario on_rez(integer start_param) { // invia un messaggio istantaneo al proprietario dell’oggetto llOwnerSay((string)llGetKey()); // resetta lo script llResetScript(); } } L’evento on_rez è scatenato ogni qualvolta un oggetto è creato a partire dall’inventario. All’interno dell’evento, si usa la funzione di libreria llOwnerSay che invia un messaggio al proprietario dell’oggetto, la stessa funzione la si usa anche per gestire il tocco dell’oggetto da parte di un avatar. Il messaggio è proprio la chiave dell’oggetto che si ricava invocando la funzione di libreria llGetKey, il risultato che è un valore di tipo key, è “castato” al tipo stringa in modo da evitare un errore di tipo dato che la funzione di libreria llOwnerSay accetta come parametro valori di tipo stringa. Ogni script rappresenta la programmazione di una macchina a stati finiti orientata agli eventi. Lo script è lanciato la prima volta che è salvato dall’editor, quindi evolve nel susseguirsi degli stati che rispondono agli eventi scatenati dall’oggetto o nell’oggetto. Quando si porta l’oggetto nell’inventario, questo è memorizzato nel suo stato attuale; ciò significa che quando lo si ricrea a partire dall’inventario, questo non “riparte” dallo stato di default ma da quello in cui lo si era lasciato quando si era messo nell’inventario. Di conseguenza anche tutte le variabili globali non hanno il valore iniziale ma quello che avevano al momento dell’inserimento nell’inventario. In realtà, quando si crea un nuovo oggetto a partire dall’inventario, l’intenzione è quella di creare un oggetto “nuovo” quindi che parta dallo stato di default con tutte le variabili con i valori predefiniti. La funzione di libreria llResetScript riporta esattamente lo script allo stato iniziale, in modo da avere un oggetto “nuovo”. Ovviamente si deve inserire il codice relativo all’evento on_rez in tutti gli stati che s’implementeranno per l’oggetto. Ogni volta che si crea un nuovo oggetto, il simulatore lo nomina con l’anonimo Object. TPSIT 5 434 um 381 di È buona norma di programmazione modificare subito questo parametro in modo che, quando lo si porta nell’inventario, si ha modo di riconoscerlo immediatamente. Nell’inventario, infatti, gli oggetti sono elencati mostrando il loro “nome”; se non si adotta un protocollo di denominazione degli oggetti, si avranno molti Object che non saremo in grado di riconoscere. In Second LIFE ogni residente è ben identificato all’interno del mondo. Questo vuol dire che, a differenza di ciò che accade per i siti web, si ha la possibilità di sapere esattamente chi visita per esempio i nostri negozi. In altre parole ciò vuol dire che in Second LIFE non esiste l’anonimato, da non confondere con l’incognito. Incognito vs anonimato Il fatto che in Second LIFE non esista anonimato è conseguenza della creazione dell’avatar stesso che, per sua natura, ha un nome, un cognome e, soprattutto, una chiave che lo identifica univocamente all’interno di Second LIFE. Ovviamente ciò non vuol dire che se ci si registra in Second LIFE ogni sconosciuto ha la possibilità di venire a bussare alla nostra porta reale. In fase di registrazione è possibile, anche se non consigliabile, fornire le generalità del cittadino più famoso d’Italia, il “Sig. Mario Rossi”. Quindi, sebbene si è sempre raggiungibili, in Second LIFE non si è anonimi, si può stare tranquilli che nella vita reale nessuno verrà a disturbare, siamo in incognito. Il potenziale che offre questa caratteristica di Second LIFE; a differenza dei siti web in cui si può al più avere una collezione di “hit” ovvero una serie d’indirizzi IP che fanno riferimento alle pagine visitate, in Second LIFE si può potenzialmente identificare nome e cognome di Second LIFE ovviamente di ogni visitatore. Ci sono diverse tecniche che consentono d’individuare gli avatar che ci visitano; si possono mettere in relazione le tecniche con gli eventi che consentono l’individuazione ovvero gli eventi scatenati dal tocco, dai sensori e dalle collisioni tra gli avatar e l’oggetto. Le tecniche differiscono fondamentalmente dal tipo d’interazione che deve effettuare l’avatar per essere individuato; in ogni caso si utilizzano un insieme di funzioni di libreria tutte individuate dal prefisso: llDetected. File DETECTION TOCCO.LSL default { touch_start(integer total_number) { // individuiamo la chiave dell?avatar che tocca l’oggetto key avatarKey = llDetectedKey(0); // individuiamo il nome dell?avatar che tocca l’oggetto string avatarName = llDetectedName(0); // inviamo alcuni messaggi all?avatar che tocca l’oggetto llInstantMessage(avatarKey, avatarName + " mi hai toccato!"); // convertiamo la chiave dell?avatar in stringa string savatarKey = (string)avatarKey; llInstantMessage(avatarKey, avatarName + " la tua chiave e’: " + savatarKey); } // scatenato ogni volta che l?oggetto viene creato dall’inventario on_rez(integer start_param) { // resetta lo script llResetScript(); } TPSIT 5 434 um 382 di } Per l’oggetto, implementare il solo evento di reazione al tocco dell’avatar touch_start. Al suo interno s’incontrano le funzioni di libreria per la detection: llDetectedKey e llDetectedName. Ad entrambe le funzioni si passa un parametro che rappresenta l’indice dell’oggetto individuato. In questo caso si passa il valore 0 in quanto il tocco scaturisce da un solo avatar. La prima delle due funzioni restituisce la chiave univoca dell’avatar, quindi un valore di tipo key; la seconda restituisce il nome e il cognome in Second LIFE dell’avatar. Nell’esempio, si mandano dei messaggi all’avatar che tocca l’oggetto. Questa tecnica è semplice da implementare ma presuppone un intervento da parte dell’avatar che, per essere individuato, deve esplicitamente toccare l’oggetto. TPSIT 5 434 um 383 di FACEBOOK INTRODUZIONE È un sito web di reti sociali ad accesso gratuito, il nome si riferisce agli annuari con le foto di ogni singolo soggetto che alcuni college statunitensi pubblicano all’inizio dell’anno accademico e distribuiscono ai nuovi studenti e al personale della facoltà come mezzo per conoscere le persone del campus. È stato fondato il 4 febbraio 2004 da Mark Zuckerberg (White Plains, 14 maggio 1984) all’epoca studente diciannovenne presso l’università di Harvard, con l’aiuto di Andrew McCollum e Eduardo Saverin. Zuckerberg fu aiutato da Dustin Moskovitz e Chris Hughes per la promozione del sito e Facebook si espanse in tutte le università americane. Alla fine dell’anno accademico, Zuckerberg e Moskovitz si trasferirono a Palo Alto in California con McCollum che aveva seguito uno stage estivo alla Electronic Arts. Affittarono una casa vicino all’Università di Stanford dove furono raggiunti da Adam D’Angelo e Sean Parker. Titolo originale Paese Anno Durata Genere Regia Soggetto Sceneggiatura Casa di produzione Fotografia Montaggio Musiche Scenografia Interpreti e personaggi Jesse Eisenberg: Andrew Garfield: Justin Timberlake: Brenda Song: Rashida Jones: Joseph Mazzello: Max Minghella: Rooney Mara: Armie Hammer: The Social Network Stati Uniti 2010 121 min Drammatico David Fincher Ben Mezrich Aaron Sorkin Columbia Pictures Jeff Cronenweth Kirk Baxter, Angus Wall Trent Reznor, Atticus Ross Donald Graham Burt Mark Zuckerberg Eduardo Saverin Sean Parker Christy Lee Marylin Delpy Dustin Moskovitz Martin Turner Erica Albright Cameron/Tyler Winklevoss Il film è stato adattato per il grande schermo dal libro di Ben Mezrich “Miliardari per caso L’invenzione di Facebook: una storia di soldi, sesso, genio e tradimento” (Sperling & Kupfer). FC (FACEBOOK CONNECT) È possibile collegare il proprio account Facebook con quello utilizzato per inserire commenti o articoli su un sito esterno, come ad esempio un blog. In pratica, è possibile portare la propria identità Facebook all’esterno, utilizzandola nei siti TPSIT 5 434 um 384 di che supportano FC. FC consente di rimuovere il form di registrazione, sostituendolo con il login di Facebook, questa funzionalità è definita “single signon”. In pratica, dopo essersi autenticati su Facebook, si può navigare sul web e ogni sito che implementa FC riconosce l’identità senza dover fare un’ulteriore autenticazione. Dal punto di vista tecnico FC è reso possibile da una libreria JavaScript rilasciata da Facebook e da un’estensione del linguaggio XHTML (eXtensible HTML) chiamata FBML che permette ai programmatori d’inserire elementi dinamici generati da Facebook sulle pagine di un sito. I punti fondamentali sui quali si basa la tecnologia e la strategia FC sono i seguenti. Identità reale e Autenticazione: gli utenti possono autenticarsi in modo sicuro e portare la propria identità Facebook su qualsiasi sito. Amici: la rete di amicizie ci segue ovunque, permettendoci d’interagire con gli amici anche nel contesto di un sito esterno. Comunicazione sociale: gli utenti potranno rendere note le azioni effettuate su un determinato sito, pubblicando i classici news feed su Facebook. Privacy: le impostazioni fatte su Facebook saranno utilizzate anche dai siti esterni. Per funzionare FC ha la necessità di scambiare costantemente informazioni tra il browser dell’utente che rimane puntato sul dominio e i server di Facebook. Tutti i browser, per ragioni di sicurezza, isolano le chiamate AJAX (Asynchronous JavaScript and XML) su ogni dominio di primo livello. Per esempio, www.miosito.it non può fare una chiamata a www.altrosito.it ma solo a servzi.miosito.it, per questo motivo occorre un file per consentire il SCDC (Secure Cross Domain Communication), un meccanismo per condividere dati fra domini differenti in modo sicuro e controllato. Tecnicamente si basa sullo scambio d’informazioni tra la pagina di un dominio e un iFrame che punta a una pagina di un secondo dominio. PROGRAMMAZIONE Nell’autunno 2008 Facebook ha reso disponibile un sistema di API lato client (JavaScript e PHP) e server (REST) per consentire al programmatore di realizzare applicazioni utilizzabili per integrare i siti esterni con Facebook. Lo scopo è di portare applicazioni di terze parti dentro la rete sociale. Registrazione Il primo passo per realizzare un’applicazione è possedere un account e in seguito installare il DEVELOPER. Per prima cosa è necessario creare una nuova applicazione Facebook. Dall’applicazione Facebook Developer http://www.facebook.com/developers cliccare sul tasto Set up new application. Dopo aver accettato le condizioni d’uso, assegnare un nome all’applicazione, una volta che è stata creata, prendere nota dell’API Key e dell’Application Secret che insieme identificano in modo univoco l’applicazione. Tra i campi opzionali compilare solo il campo Callback URL con l’indirizzo del server che ospiterà la pagina. Nella sezione dedicata a Connect, configurare il campo Connect URL e Base Domain, perché indispensabili per il corretto funzionamento dell’applicazione. La chiamata FB.init (Api_Key, Cross Domain Communication), inizializza la libreria passando due parametri: l’API Key assegnata all’applicazione ottenuto da Facebook e un file che permette di risolvere le chiamate tra domini differenti. La funziona FB.init lancerà due funzioni diverse a seconda che l’utente sia autenticato TPSIT 5 434 um 385 di oppure meno. Se l’utente non ha fatto login si visualizza all’interno della DIV 'user' il pulsante di login <fb:login-button/>. Altrimenti, se l’utente ha fatto login in Facebook, si visualizza l’immagine profilo utente <fb:profile-pic/> e il nome dell’utente <fb:name/>. Tutte le chiamate al servizio REST di Facebook sono fatte attraverso il metodo generico SendRequest<T> che ha lo scopo di confezionare la chiamata HTTP e restituire un’istanza del tipo specificato come parametro generico. Le chiamate al servizio REST devono essere preparate inserendo nel body del POST i seguenti parametri. api_key: è la stringa che identifica l’applicazione. session_key: è una stringa che rappresenta la sessione dell’utente autenticato. call_id: è un float che identifica la sequenza delle chiamate. sig: è l’hash in formato MD5 (Message Digest algorithm 5) di tutti i parametri della richiesta più Application Secret. v: il numero di versione dell’API. Il metodo SendRequest<T>, colleziona queste informazioni in un Dictionary e aggiunge i parametri standard richiamando il metodo AddStandartParameter. Una volta che il Dictionary contiene tutti i parametri, è necessario aggiungere il sid, uno sbaglio nel calcolo dell’hash MD5 causerà un errore 104 “Incorrect signature”. Silverlight è privo dell’algoritmo criptografico MD5 ma serve per la realizzazione di applicazioni che fanno uso dell’API di Facebook. Il delegate Action<T> è indispensabile in scenari, dove l’accesso ai dati è del tipo asincrono, in Silverlight questo è l’unico modello disponibile, quindi il poter passare un metodo da eseguire solo dopo la fine task asincrono risulta di fondamentale importanza. TPSIT 5 434 um 386 di Progettare un client per mantenersi in contatto con i collaboratori via Facebook che, interfacciandosi con il servizio REST, permette di recuperare i propri contatti e d’inviare dei messaggi che appariranno nella bacheca del contatto selezionato. Fare clic su File/Nuovo/Progetto… (CTRL+MAIUSC+N), nella finestra di dialogo Nuovo Progetto selezionare Visual C#/Silverlight/Applicazione Silverlight, scegliere come host un’Applicazione Web ASP.NET. TPSIT 5 434 um 387 di BING (Bing is not Google) INTRODUZIONE Bing è il motore di ricerca di Microsoft, nato da MSN (MicroSoft Network) Live Search nel giugno 2009. Insieme al motore di ricerca sono disponibili un servizio di mappe, un servizio di ricerca immagini, un servizio di ricerca news e un motore di ricerca dedicato allo shopping, in Italia in partnership con ciao.it. Per i webmaster è disponibile Webmaster Center, servizio che permette di verificare lo stato d’indicizzazione del sito e altre statistiche utili ai webmaster. Mette a disposizione API grazie alle quali i programmatori possono interagire con il motore di ricerca e recuparne i risultati per poterne poi disporre come meglio credono. PROTOCOLLO XML Capire come è strutturato il risultato della ricerca sia in caso di risultato corretto sia d’errore, è utile per riuscire a interpretare l’XML che si riceve a seguito della richiesta. Se si desidera eseguire una ricerca con il protocollo XML, occorre eseguire una richiesta web grazie alla classe HttpWebRequest, costruita concatenando l’indirizzo di endpoint del protocollo XML con i parametri di ricerca. Grazie alla chiamata a GetResponse della classe HttpWebRequest si ottiene un’istanza della classe HttpWebResponse dalla quale si può ricavare lo stream XML, da interpretare. La classe IMAGEXMLSERVICE.VB funge da servizio di business per la ricerca del progetto. Dim request = BuildRequest(searchKey, offSet, recordCount, filters) Me._LastDuration = Stopwatch.StartNew() Dim response = DirectCast(request.GetResponse, HttpWebResponse) Me._LastDuration.Stop() Dim stream = response.GetResponseStream() retList = CreateImageList(response, totalCount) BuildRequest costruisce la richiesta web concatenando i parametri della querystring. Protected Function BuildRequest(ByVal text As String, ByVal startindex As UInteger, ByVal recordCount As UInteger, ByVal filters As String) As HttpWebRequest Dim reqBuilder As New StringBuilder With reqBuilder .Append("http://api.search.live.net/xml.aspx?") .AppendFormat("AppId={0}&Query={1}&Sources=Image&Version={2}", AppID, text, Version) If Not String.IsNullOrEmpty(Market) Then .AppendFormat("Market={0}", Market) End If If Not String.IsNullOrEmpty(filters) Then .AppendFormat("Image.Filters={0}", filters) End If .AppendFormat("&Image.Count={0}&Image.Offset={1}", recordCount, startindex) End With Dim request=DirectCast(WebRequest.Create(reqBuilder.ToString()), TPSIT 5 434 um 388 di HttpWebRequest) Return request End Function La proprietà AppID contiene l’AppID recuperato dal portale Bing. La proprietà Market contiene il mercato di riferimento. La proprietà Version la versione del servizio Bing da utilizzare. La funzione CreateImageList si occupa d’interpretare lo stream di ritorno della richiesta per costruire la lista dei risultati di ricerca o eventuale lista di errori. Per interpretare lo stream XML di risposta è necessario fare uso della tecnica chiamata xPath, in particolare, sarà necessario definire i namespace per recuperare eventuali errori e i risultati della ricerca. Protected Function CreateImageList(ByVal response As HttpWebResponse, ByRef totalCount As UInteger) As List(Of Bing.ImageResult) Dim retList As List(Of Bing.ImageResult) = Nothing Dim document As New XmlDocument document.Load(response.GetResponseStream) Dim nsmgr As New XmlNamespaceManager(document.NameTable) nsmgr.AddNamespace("api", "http://schemas.microsoft.com/LiveSearch/2008/04/XML/element") Dim errors = document.DocumentElement.SelectNodes("./api:Errors/api:Error", nsmgr) If (errors.Count > 0) Then For Each errorNode As XmlNode In errors Dim [error] As New Bing.Error With [error] .Code = UInteger.Parse(GetNodeValue(errorNode.SelectSingleNode("./api:Code", nsmgr))) .HelpUrl = GetNodeValue(errorNode.SelectSingleNode("./api:HelpUrl", nsmgr)) .Message = GetNodeValue(errorNode.SelectSingleNode("./api:Message", nsmgr)) .Parameter = GetNodeValue(errorNode.SelectSingleNode("./api:Parameter", nsmgr)) End With Me._LastErrors.Add([error]) Next Else retList = New List(Of Bing.ImageResult) Dim root = document.DocumentElement nsmgr.AddNamespace("mms", "http://schemas.microsoft.com/LiveSearch/2008/04/XML/multimedia") Dim imageNode = root.SelectSingleNode("./mms:Image", nsmgr) Dim results = imageNode.SelectNodes("./mms:Results/mms:ImageResult", nsmgr) Dim offset As UInteger UInteger.TryParse(GetNodeValue(imageNode.SelectSingleNode("./mms:Offset", nsmgr)), offset) UInteger.TryParse(GetNodeValue(imageNode.SelectSingleNode("./mms:Total", nsmgr)), totalCount) For Each result As XmlNode In results Dim imageResult = New Bing.ImageResult() With imageResult .Title = GetNodeValue(result.SelectSingleNode("./mms:Title", nsmgr)) Dim fileSize As UInteger If TPSIT 5 434 um 389 di UInteger.TryParse(GetNodeValue(result.SelectSingleNode("./mms:FileSize", nsmgr)), fileSize) Then .FileSize = fileSize .FileSizeSpecified = True End If Dim height As UInteger If UInteger.TryParse(GetNodeValue(result.SelectSingleNode("./mms:Height", nsmgr)), height) Then .Height = height .HeightSpecified = True End If Dim width As UInteger If UInteger.TryParse(GetNodeValue(result.SelectSingleNode("./mms:Width", nsmgr)), width) Then .Width = width .WidthSpecified = True End If .MediaUrl = GetNodeValue(result.SelectSingleNode("./mms:MediaUrl", nsmgr)) .Thumbnail = New Bing.Thumbnail .Thumbnail.ContentType = GetNodeValue(result.SelectSingleNode("./mms:Thumbnail/mms:ContentType", nsmgr)) If UInteger.TryParse(GetNodeValue(result.SelectSingleNode("./mms:Thumbnail/mms:FileSiz e", nsmgr)), fileSize) Then .Thumbnail.FileSize = fileSize .Thumbnail.FileSizeSpecified = True End If If UInteger.TryParse(GetNodeValue(result.SelectSingleNode("./mms:Thumbnail/mms:Height ", nsmgr)), height) Then .Thumbnail.Height = height .Thumbnail.HeightSpecified = True End If If UInteger.TryParse(GetNodeValue(result.SelectSingleNode("./mms:Thumbnail/mms:Width" , nsmgr)), width) Then .Thumbnail.Width = width .Thumbnail.WidthSpecified = True End If .Thumbnail.Url = GetNodeValue(result.SelectSingleNode("./mms:Thumbnail/mms:Url", nsmgr)) .Url = GetNodeValue(result.SelectSingleNode("./mms:Url", nsmgr)) .ContentType = GetNodeValue(result.SelectSingleNode("./mms:ContentType", nsmgr)) .DisplayUrl = GetNodeValue(result.SelectSingleNode("./mms:DisplayUrl", nsmgr)) End With retList.Add(imageResult) Next End If Return retList End Function TPSIT 5 434 um 390 di Utilizzare la classe XmlDocument per caricare lo stream della risposta di Bing, quindi creare un XmlNamespaceManager per gestire i namespace di Bing e aggiungere il namespace di base che s’indica con il prefisso ”api”. Questo consente di verificare se la risposta contiene i nodi <error> utilizzando la query xPath nella chiamata al metodo SelectNodes. Se si ottengono degli errori, riempire la collezione degli stessi, mentre se non si sono ottenuti errori, si deve aggiungere il namespace Multimedia a XmlNamespaceManager per recuperare i risultati della ricerca e, quindi, si può utilizzare il metodo SelectSingleNode per effettuare le query xPath per recuperare tutti i risultati di ricerca, i valori di offset e il numero totale di record. Il metodo GetNodeValue è una funzione che permette di gestire l’assenza del nodo passato per argomento ritornando una stringa vuota. Private Function GetNodeValue(ByVal node As XmlNode) As String Dim retval As String = Nothing If node IsNot Nothing Then retval = node.InnerText End If Return retval End Function End Class PROTOCOLLO SOAP Richiesta SOAP (busta + lettera) inviata ad un server. La busta rappresenta il linguaggio che trasmette le informazioni, in altre parole il protocollo HTTP. La lettera rappresenta i dati, in altre parole il messaggio XML con le informazioni. Più semplice da utilizzare è il protocollo SOAP, si può recuperare il WSDL dei servizi SOAP all’indirizzo seguente. http://api.bing.net/search.wsdl?AppID= <appid>&Version=2.2 Nel progetto si può referenziare il servizio tramite l’opzione Progetto/Aggiungi riferimento al servizio… e ottenere l’insieme delle classi wrapper necessarie all’esecuzione delle ricerche. TPSIT 5 434 um 391 di La figura rappresenta il diagramma delle classi, relativamente alla fonte di ricerca delle immagini che sono generate dal WSDL del servizio Bing: BingPortTypeClient rappresenta la classe tramite la quale è possibile effettuare ricerche con il protocollo SOAP, utilizzando il metodo Search per le ricerche sincrone o SearchAsync per quelle asincrone. Il metodo Search prevede come parametro un oggetto di classe SearchRequest e restituisce un oggetto di classe SearchResponse. L’istanza di SearchRequest contiene tutti i dati necessari per la ricerca tra cui, ad esempio, il Market, la Version, le fonti di ricerca Sources e un oggetto di classe ImageRequest con i parametri relativi alla ricerca delle immagini. La classe SearchResponse contiene una proprietà Image, di tipo ImageResponse, con i dati di offset, del numero stimato di record totali e la collezione dei risultati di ricerca. Tutte le classi ottenute dal WSDL prevedono delle proprietà di tipo boolean per indicare la presenza o meno di proprietà che potrebbero non essere valorizzate come, ad esempio, la Width della classe ImageResult e la relativa WidthSpecified. Queste classi, quindi, sono utilizzabili in linguaggi in cui si fa largo uso del binding come, ad esempio, WPF o Silverlight, anche perché, inoltre, implementano la proprietà INotifyPropertyChanged. Grazie alla classe BingPortTypeClient si può implementare la funzione di ricerca. Public Function Search(ByVal searchKey As String, _ ByVal recordCount As UInteger, _ ByRef totalCount As UInteger, _ Optional ByVal offSet As UInteger = 0, _ Optional ByVal filters As String = "") As List(Of Bing.ImageResult) Dim retList As List(Of Bing.ImageResult) = Nothing If Not String.IsNullOrEmpty(searchKey) Then Dim parameters = New Bing.SearchRequest() With parameters .Image = New Bing.ImageRequest() .Image.Count = recordCount .Image.Offset = offSet .Market = Market .Query = searchKey .Sources = New SourceType() {SourceType.Image} End With Try Dim response = Me._Service.Search(parameters) If response.Errors IsNot Nothing Then Me._LastErrors = response.Errors.ToList() Else retList = response.Image.Results.ToList() totalCount = response.Image.Total End If Catch ex As Exception Throw End Try Else retList = New List(Of Bing.ImageResult) End If Return retList End Function TPSIT 5 434 um 392 di In pratica, si crea un’istanza della classe SearchRequest, impostare i valori dei parametri di ricerca, richiamare il metodo search e recuperare la lista dei risultati di ricerca. In caso di errore nella ricerca, utilizzare la collezione Errors della classe SearchResponse per ottenere l’elenco degli errori. Utilizzando il protocollo SOAP, non si ha la necessità d’interpretare lo streaming di ritorno della risposta, in quanto questo lavoro è fatto dalle classi wrapper generate a partire dal WSDL. PROTOCOLLO JSON I servizi di Bing sono utilizzabili con una pagina HTML ricorrendo al linguaggio Javascript. L’endpoint per utilizzare il protocollo JSON è il seguente. http://api.bing.net/json.aspx La struttura della richiesta è analoga a quella del protocollo XML. Nel seguente codice JavaScript c’è il modo con cui interrogare la piattaforma Bing. function search(offset, numRecords) { displayPager(false); var totalResultLabel = document.getElementById('lblTotalResults'); var executionTimeLabel = document.getElementById('lblExecutionTime'); var parent = document.getElementById('tblResults'); removeRows(parent); totalResultLabel.innerHTML = "<img src='./images/wait-icon.gif' style='verticalalign:middle;'/> Attendere prego, ricerca in corso!"; executionTimeLabel.innerHTML = ""; var search = "&query=" + document.getElementById('txtSearchText').value; var fullUri = serviceURI + AppId + search + "&Image.count=" + numRecords + "&Image.offset=" + offset; var head = document.getElementsByTagName('head'); var script = document.createElement('script'); script.type = "text/javascript"; script.src = fullUri; head[0].appendChild(script); startTime = new Date(); } La funzione search crea la stringa contenente l’URL necessario per la richiesta Bing selezionando la modalità di risposta “callback”, con searchDone come funzione di callback, crea un oggetto script e lo inserisce come figlio del nodo head della pagina. In questo modo, nel momento in cui la piattaforma Bing avrà effettuato la ricerca e saranno disponibili i risultati, sarà invocata la funzione searchDone che dovrà, evidentemente, essere presente nella pagina, passandole la struttura dati dei risultati. Un esempio di come potrebbe presentarsi la callback è il seguente codice. function searchDone(results) { endTime = new Date(); var result = null; var parent = document.getElementById('tblResults'); var row = null; var cell = null; TPSIT 5 434 um 393 di removeRows(parent); var totalResultLabel = document.getElementById('lblTotalResults'); totalResultLabel.innerHTML = "Numero di risultati stimati : " + results.SearchResponse.Image.Total; var executionTimeLabel = document.getElementById('lblExecutionTime'); var executionTime = endTime.getTime() - startTime.getTime(); executionTimeLabel.innerHTML="Tempo di esecuzione:"+(executionTime/1000)+" sec."; var offset = results.SearchResponse.Image.Offset; var numSearchRecord = results.SearchResponse.Image.Results.length; var totalPageNumber = results.SearchResponse.Image.Total / numSearchRecord + 1; var pageNumber = offset / numSearchRecord + 1; for (var i = 0; i < numSearchRecord; ) { row = document.createElement('tr'); for (var j = 0; j <= 4 && i < numSearchRecord; i++, j++) { result = results.SearchResponse.Image.Results[i]; cell = document.createElement('td'); var html = "<img onclick=" + String.fromCharCode(34) + "javascript:showPopupImage('" + result.Url + "');" + String.fromCharCode(34) + " "; html += "src='" + result.Thumbnail.Url + "' "; html += "style='border-width:0px;cursor: pointer; border: solid 1px black;' /><br />"; html += result.Title + "<br>"; cell.innerHTML = html; row.appendChild(cell); } parent.appendChild(row); } La funzione prevede che esista una tabella HTML, tblResults, nella pagina e crea delle righe con cinque celle ciascuna contenenti altrettanti risultati della ricerca, ogni cella contiene l’immagine descritta dal nodo Thumbnail e il titolo dell’immagine stessa. Progettare un motore per la ricerca d’immagini. TPSIT 5 434 um 394 di Implementare tre pagine ASPX in cui sono utilizzate le tre modalità di accesso alla piattaforma Bing per la ricerca d’immagini. Per quanto riguarda i protocolli XML e SOAP sono state create delle classi che forniscono i due servizi di ricerca. BINGSERVICE.VB Definisce la classe base di tutti i servizi per l’accesso a Bing, in particolare contiene AppID, Market, Version ed eventuali altri parametri comuni ai servizi. Inoltre, contiene una proprietà che restituisce l’elenco degli errori verificatisi nell’ultima interrogazione e un’istanza della classe StopWatch che si utilizza per calcolare le durate delle operazioni di ricerca. BINGSOAPSERVICE.VB Rappresenta la classe base per i servizi Bing che utilizzano il protocollo SOAP, rispetto alla classe BingService, prevede un attributo protected di classe BingPortTypeClient che è istanziato nel costruttore e può essere utilizzato dalle singole classi di servizio. IMAGEXMLSERVICE.VB Implementa le funzionalità di ricerca con il protocollo XML. IMAGESOAPSERVICE.VB Implementa le funzionalità di ricerca con il protocollo SOAP. Per quel che riguarda il protocollo JSON, le funzionalità di ricerca sono implementate con TPSIT 5 434 um 395 di delle funzioni JavaScript presenti nella pagina di ricerca. TPSIT 5 434 um 396 di RSS INTRODUZIONE RSS (RDF [Resource Description Framework] Site Summary). Oppure (Really Simple Syndication). È uno dei formati per la distribuzione di contenuti web; è basato su XML, da cui ha ereditato la semplicità, l’estensibilità e la flessibilità. RSS definisce una struttura adatta a contenere un insieme di notizie, ciascuna delle quali sarà composta da vari campi: nome autore, titolo, testo e riassunto. Quando si pubblicano delle notizie in formato RSS, la struttura è aggiornata con i nuovi dati; visto che il formato è predefinito, un qualunque lettore RSS potrà presentare in una maniera omogenea notizie provenienti dalle fonti più diverse. Il termine Syndication, nel linguaggio dei media, indica la diffusione di contenuti tramite agenzie che provvedono a distribuirli grazie a vari canali. Andare a consultare tutti i siti d’interesse per intercettarne le notizie può risultare un esercizio lungo e noioso. Questo problema è stato risolto da Netscape nel 1999 con la progettazione del formato RSS che serve a visualizzare le novità di un sito che è aggiornato frequentemente, senza che l’utente abbia bisogno ogni volta di controllare cosa c’è di nuovo. Un file RSS è un documento XML il cui nodo radice è l’elemento <rss>. All’interno del nodo <rss> è presente un elemento <channel> che raggruppa le informazioni caratteristiche del “canale”, in altre parole del feed in esame, nel quale sono elencati degli elementi obbligatori, per esempio <title>, <description> o <link> e degli elementi facoltativi, per esempio <language>, <lastBuildDate> o <image>. La vera sezione informativa si trova all’interno dell’elemento <channel>, nella collezione degli elementi <item>. Un <item> rappresenta l’unità atomica della fonte dati, per esempio può essere il post di un blog, la notizia di un giornale o la rilevazione della temperatura atmosferica di una località. Un elemento <item> prevede, a sua volta degli elementi come <title>, <link>, <description> e altri. Nell’elemento <description> è possibile inserire un testo HTML con le entità codificate. Tutti gli elementi di un <item> sono opzionali ma dev’essere almeno presente un <title o una <description>. Atom Basato su XML che consente una maggiore precisione nella definizione dei contenuti. L’elemento radice è <feed> al cui interno si trovano le informazioni caratterizzanti il feed stesso quali <title>, <subtitle> e <id>. In questo formato, i singoli elementi possono essere decorati con un attributo che ne qualifica il tipo di rappresentazione, per esempio, l’elemento <title> può avere l’attributo type il cui valore può essere: “text” il default, “html” o “xhtml”. Le unità atomiche, contenenti le notizie, sono gli elementi <entry>, composti da <link>, <updated>, <published> e <author>. Altri sotto elementi utilizzabili sono: <summary>, <category>, <rights> e <source>. Vantaggi Nell’iscrizione ad un feed, gli utenti non rivelano il loro indirizzo di posta elettronica. TPSIT 5 434 um 397 di In questo modo non si espongono alle minacce tipiche dell’email quali SPAM, virus, phishing e il furto d’identità. Se gli utenti vogliono interrompere la ricezione di notizie, non devono inviare richieste del tipo “annulla la sottoscrizione” ma basta che rimuovano il feed dal loro aggregatore. Il contenuto di un giornale online è aggiornato più volte al giorno con nuovi articoli, il formato RSS consente di avere sul PC l’informazione sull’articolo appena pubblicato con un link. Quando si pubblicano delle notizie in formato RSS, la struttura è aggiornata con i nuovi dati; visto che il formato è predefinito, un qualunque lettore RSS potrà presentare in una maniera omogenea notizie provenienti dalle fonti più diverse. I principali siti d’informazione, i quotidiani online, i fornitori di contenuti, i blog più popolari: tutti adottano il formato RSS. Gli utenti possono oggi accedere a migliaia di documenti RSS, chiamati feed: alcuni siti, directory, raccolgono i riferimenti agli innumerevoli feed disponibili sul web. Un file RSS è letto da un’applicazione chiamata feed reader che provvede anche a ricercare il flusso di RSS di un sito, controllando costantemente gli aggiornamenti. Questo rapporto automatizzato che s’instaura tra chi fornisce e chi riceve il flusso d’informazioni si chiama feed. Queste applicazioni possono essere divise in categorie. 1. Applicazioni stand-alone: per esempio FeedReader e Sharpreader per Windows, Netnewswire e Vienna per MC OS, Straw per Linux, Pulse per mobile; per esempio nella parte sinistra dell’interfaccia è mostrato l’elenco dei feed impostati dall’utente; nella parte in alto a destra l’elenco dei singoli contenuti del feed selezionato; nella parte principale l’intero contenuto testuale di un articolo. 2. Plugin all’interno del browser: per esempio Sage, Urss e Wizz RSS New Reader per Firefox. 3. Aggregatori web: è un sito web che offre un servizio di raccolta ed elaborazione dei feed: per esempio Google Reader, Bloglines, Feed on Feeds e Pluck. 4. Convertitori: applicazioni in grado di convertire un feed RSS in una serie di post in formato leggibile dai più popolari newsreader come, ad esempio, Mozilla Thunderbird, Forté Agent. Oltre a queste applicazioni, i browser si sono dotati di strumenti per il riconoscimento e la gestione dei feed RSS. Comprendere se un sito distribuisce un feed non richiede alcun accorgimento, è il browser stesso che si accorge e lo segnala con un’icona. Esempio, dato un DB che contiene banche dati presenti sui notebook distribuiti sul territorio, sincronizzarli. Basta esporre i dati via RSS in modo che i client siano liberi di aggiornarsi utilizzando un lettore di RSS. Il DB si compone di due tabelle. tblProdotti Contenente l’elenco dei prodotti con la relativa data di modifica, utile per sapere se un prodotto è stato modificato dall’ultima volta che sono stati letti i dati. tblTipiProdotti TPSIT 5 434 um 398 di Contiene le tipologie di prodotto. TPSIT 5 434 um 399 di Una soluzione è l’utilizzo di un feed RSS, con questa architettura. Bisogna progettare una “risorsa web”, in particolare un HttpHandler che, a richiesta, costruisca uno stream RSS e lo restituisca al chiamante. Per fare questo si utilizzano le classi del namespace System.ServiceModel.Syndication. Progettare una classe che recuperi i dati dal DB, i quali costituiranno il feed. Si usa LINQ e si realizza una classe di servizio, RSSService, contenente un metodo il cui scopo è quello di recuperare i record dal DB e impacchettarli in un oggetto SyndicationFeed. Questa operazione è eseguita dal metodo GetRSS. Recuperare l’ultima data d’inserimento dei prodotti che costituirà la data di aggiornamento del feed. Dim lastUpdate As DateTime = Aggregate p In Me._DBContext.tblProdotti Into Max(p.DataModifica) Si crea un’instanza della classe SyndicationFeed il cui scopo è quello di contenitore principale di un feed RSS, sia che si lavori con il protocollo Atom sia con il protocollo RSS. Dim feed = New SyndicationFeed("RSS Test", "RSS test", Nothing, "RSSFeedID", lastUpdate) La classe SyndicationFeed prevede diversi costruttori, impostare il titolo del feed, la descrizione del feed, il link alternativo per l’identificativo del feed e la data dell’ultimo aggiornamento dello stesso feed. Il contenitore di feed dev’essere riempito con gli item corrispondenti ai prodotti di cui si vogliono trasferire i dati ed è per questo che si crea un oggetto di classe SyndicationItem per ogni record presente nel DB recuperando solo quei prodotti che, alla data attuale, sono ancora validi. Per fare questo si usa LINQ per restituire una collezione di oggetti anonimi contenenti i prodotti e tipi prodotti. Dim products = From p In Me._DBContext.tblProdotti Join t In Me._DBContext.tblTipiProdotti On p.IDTipoProdotto Equals t.IDTipoProdotto Order By p.DataModifica Descending Where (Not p.DataValiditaDa.HasValue OrElse p.DataValiditaDa <= dtNow) And (Not p.DataValiditaA.HasValue OrElse p.DataValiditaA >= dtNow) Select New With {.Product = p, .ProductType = t} TPSIT 5 434 um 400 di La collezione di oggetti anonimi appena creata dev’essere convertita in una collezione di SyndicationItem da agganciare al contenitore del feed. Per fare questo, iterare la collezione appena recuperata e creare un oggetto di tipo SyndicationItem del quale impostare gli attributi che consentiranno di trasportare altre informazioni relative ai prodotti. Dim itemList As New List(Of SyndicationItem) For Each pair In products Dim item As New SyndicationItem(pair.Product.Descrizione, pair.ProductType.Descrizione, New Uri(String.Format("http://www.miosito.it/Item.aspx?id={0}", pair.Product.IDProdotto)), pair.Product.IDProdotto, pair.Product.DataModifica) item.AttributeExtensions.Add(New XmlQualifiedName("noteProdotto"), pair.Product.Note) item.AttributeExtensions.Add(New XmlQualifiedName("dataValiditaDa"), IIf(pair.Product.DataValiditaDa.HasValue, pair.Product.DataValiditaDa, "")) item.AttributeExtensions.Add(New XmlQualifiedName("dataValiditaA"), IIf(pair.Product.DataValiditaA.HasValue, pair.Product.DataValiditaA, "")) item.AttributeExtensions.Add(New XmlQualifiedName("idTipoProdotto"), pair.ProductType.IDTipoProdotto) item.AttributeExtensions.Add(New XmlQualifiedName("noteTipoProdotto"), pair.ProductType.Note) itemList.Add(item) Next Si assegna la descrizione del prodotto alla proprietà Title del SyndicationItem, la descrizione del tipo di prodotto alla proprietà Content dell’item e l’identificativo del prodotto alla proprietà ID in modo da avere un item interpretabile nel caso in cui si visualizzi l’RSS attraverso un aggregatore o un browser. Le altre informazioni del prodotto sono state agganciate all’item come attributi in modo da essere eventualmente recuperabili dal client che deve elaborare lo stream RSS. Una volta creati gli item costituenti l’RSS, basta agganciarli al contenitore del feed con la seguente istruzione. feed.Items = itemList E restituire lo stesso contenitore al metodo chiamante. Il metodo GetRSS è utilizzato dall’HttpHandler che si occupa di formattare il SyndicationFeed in base al protocollo scelto dall’utente e restituire lo stream XML. Questo avviene nel metodo seguente. Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest context.Response.ContentType = "text/xml" Using srv As New ClassLibrary.RSSService(ConfigurationManager.ConnectionStrings("DBConnectionString" ).ConnectionString) Dim rssWriter As XmlWriter = XmlWriter.Create(context.Response.OutputStream) Dim protocol = context.Request.QueryString("protocol") If String.Compare(protocol, "atom", True) = 0 Then Dim atomFormatter As Atom10FeedFormatter = New TPSIT 5 434 um 401 di Atom10FeedFormatter(srv.GetRSS()) atomFormatter.WriteTo(rssWriter) Else Dim rssFormatter As Rss20FeedFormatter = New Rss20FeedFormatter(srv.GetRSS()) rssFormatter.WriteTo(rssWriter) End If rssWriter.Close() End Using End Sub Nel metodo si utilizza la classe RSSService per fornire, agli opportuni formatter, l’oggetto contenitore dello stream. In particolare, se l’utente non definisce il protocollo RSS desiderato, il metodo utilizza un oggetto di classe Rss20FeedFormatter per generare lo stream secondo il formato RSS 2.0 mentre se l’utente utilizza il parametro atom nella querystring della chiamata all’HttpHandler, il metodo utilizza la classe Atom10FeedFormatter per formattare lo stream in base al formato Atom 1.0. In entrambi i casi, la scrittura dell’XML all’interno dello stream di output della response è eseguita tramite un oggetto di classe XmlWriter. Adesso bisogna progettare il client che, leggendo periodicamente lo stream, riesca a capire quando ci sono delle variazioni in modo da poter aggiornare la banca dati remota. Si può progettare un client con la stessa tecnologia e linguaggio utilizzati per il S/W che genera lo stream ma essendo il formato dei dati universale, XML, il client potrebbe essere realizzato in un qualsiasi linguaggio e su una qualsiasi piattaforma disponibile. Il client deve comprendere come richiedere lo stream RSS, interpretare i dati ottenuti e capire quali eventuali modifiche debbano essere apportate alla banca dati sottostante. In particolare, si utilizza un formatter per accedere ai dati contenuti nello stream. Dim reader = XmlReader.Create("http://localhost:60000/RSSWebSite/RSSFeed.ashx") Dim formatter = New Rss20FeedFormatter() formatter.ReadFrom(reader) Il reader XML consente di richiedere la risorsa RSS remota. Per leggere i dati dal reader è necessario un formatter RSS 2.0 e la chiamata al metodo ReadFrom. Ultimata la lettura, si possono ottenere i prodotti aggiornati, inseriti o modificati, utilizzando il codice seguente. Dim lastUpdatedatetime = formatter.Feed.LastUpdatedTime.DateTime Dim coll = From c In formatter.Feed.Items Where c.LastUpdatedTime.DateTime > Me.LastSyncDate Order By c.LastUpdatedTime Ascending Select CreateProduct(c) In questo modo si seleziona, tra tutti gli item restituiti nello stream RSS, quelli la cui data di modifica è successiva alla data di ultimo accesso allo stream stesso e si creano gli oggetti Product, grazie al metodo CreateProduct. In questo metodo si recuperano gli attributi relativi al prodotto e al tipo prodotto in maniera analoga a quanto visto per la creazione dello stream. Protected Function CreateProduct(ByVal item As SyndicationItem) As Product TPSIT 5 434 um 402 di Dim product As New Product() With product If item.AttributeExtensions(New XmlQualifiedName("dataValiditaDa")) = "" Then .DateFrom = Nothing Else .DateFrom = DateTime.Parse(item.AttributeExtensions(New XmlQualifiedName("dataValiditaDa"))) End If If item.AttributeExtensions(New XmlQualifiedName("dataValiditaA")) = "" Then .DateTo = Nothing Else .DateTo = DateTime.Parse(item.AttributeExtensions(New XmlQualifiedName("dataValiditaA"))) End If .Description = item.Title.Text .ID = Convert.ToInt32(item.Id) .ModifyDate = item.LastUpdatedTime.DateTime .Notes = item.AttributeExtensions(New XmlQualifiedName("noteProdotto")) .Type = New ProductType() With {.Description = item.Summary.Text,.ID = Convert.ToInt32(item.AttributeExtensions(New XmlQualifiedName("idTipoProdotto"))),.Notes = item.AttributeExtensions(New XmlQualifiedName("noteTipoProdotto"))} End With Return product End Function Una volta recuperati i prodotti modificati, si può, ad esempio, aggiornare la banca dati locale oppure visualizzarli a video. WINDOWS 8.X Leggere un feed RSS e un feed Atom // feed RSS var client = new SyndicationClient(); var feed = await client.RetrieveFeedAsync( new Uri("http://weblogs.miosito.it/rss.aspx")); // feed Atom var client = new AtomPubClient(); var feed = await client.RetrieveFeedAsync( new Uri("http://weblogs.miosito.it/atom.aspx")); Le classi SyndicationClient e AtomPubClient sono contenute rispettivamente nel namespace Windows.Web.Syndication e Windows.Web.AtomPub. Queste classi espongono il metodo RetrieveFeesAsync che ritorna un oggetto di tipo SyndicationFeed. Quest’oggetto contiene i dati del feed non in formato XML ma in un formato a oggetti e quindi è di facile consultazione. Leggere i dati salienti del feed. foreach (var item in feed.Items) { TPSIT 5 434 um 403 di var test = item.Summary.Text; var title = item.Title.Text; var pubdate = item.PublishedDate; var url = item.Links[0].Uri.ToString(); ... } Manipolare dati in formato JSON Per manipolare dati JSON, WinRT (Windows RunTime) mette a disposizione le classi del namespace Windows.Data.Json. Le classi principali che permettono di lavorare con JSON sono tre. 1. JsonObject: rappresenta un oggetto JSON ovvero una lista di coppie chiave/valore, in questo caso, la lista è l’equivalente di un oggetto e ogni coppia chiave/valore è l’equivalente di una proprietà con il suo valore. 2. JsonArray: rappresenta un array JSON, il quale può contenere dati semplici o, a sua volta, coppie chiave/valore. 3. JsonValue: rappresenta un valore in JSON. Utilizzare queste classi con il servizio REST che torna dati in formato JSON. var client = new HttpClient(); var body = await client.GetStringAsync("http://localhost:1892/api/values/1"); var json = JsonObject.Parse(body); var id = json.GetNamedNumber("Id"); var name = json.GetNamedString("Name"); Il metodo static Parse della classe JsonObject trasforma la stringa JSON in un oggetto JsonObject. Poi, tramite i metodi GetNamedString e GetNamedNumber, si recuperano rispettivamente le proprietà Name e Id, accettano in input i nomi delle proprietà e sono case sensitive. Nell’esempio le proprietà del cliente sono semplici ma volendo si possono avere anche proprietà complesse, come un indirizzo o una lista di contatti. In questi casi si deve usare il metodo GetNamedObject passando in input il nome della proprietà, per recuperare una proprietà che rappresenta un singolo oggetto sul quale poi è possibile usare i metodi già visti e il metodo GetNamedArray per recuperare una proprietà di tipo lista. Esempio, recuperare dati da un array JSON che contiene la lista dei clienti. var client = new HttpClient(); string body = await client.GetStringAsync("http://localhost:1892/api/values"); var json = JsonArray.Parse(body); for (var i =0; i<json.Count;i++) { var obj = json[i].GetObject(); var name = obj.GetNamedString("Name"); var id = obj.GetNamedNumber("Id"); } Il metodo Parse della classe JsonArray permette di trasformare la stringa JSON in un oggetto JsonArray. La proprietà Count indica il numero di elementi nell’array. Per ogni elemento che è un oggetto JSON contenente i dati del client, trasformare TPSIT 5 434 um 404 di l’elemento in un oggetto JsonObject, tramite il metodo GetObject, contenente i dati del cliente e poi recuperare le proprietà tramite i metodi GetNameString e GetNamedNumber. Esempio, trasformazione di un insieme di dati in una stringa JSON che dev’essere poi inviata al servizio REST. var obj = new JsonObject(); obj.Add("Id", JsonValue.CreateNumberValue(1)); obj.Add("Name", JsonValue.CreateStringValue("Andrea Sperelli")); var content = new StringContent(obj.Stringify(), Encoding.UTF8, "application/json"); var client = new HttpClient(); await client.PostAsync("http://localhost:1892/api/values", content); Creare un’istanza della classe JsonObject. Poi, tramite il metodo Add, aggiungere le proprietà Id e Name il cui valore è impostato sfruttando i metodi statici CreateNumberValue e CreateStringValue. A questo punto invocare il metodo Stringify per trasformare l’oggetto JSON in una stringa pronta per essere inviata al servizio REST. Esempio. { "Id": 1, "Name": "Alessio Leoncini" } TPSIT 5 434 um 405 di TWITTER INTRODUZIONE È un servizio di social network e microblogging che fornisce agli utenti una pagina personale aggiornabile tramite messaggi di testo con una lunghezza massima di 140 caratteri. Gli aggiornamenti possono essere effettuati tramite il sito stesso, via SMS (Short Message Service), con applicazioni di messaggistica istantanea, email, oppure tramite varie applicazioni basate sulle API di Twitter. Twitter è stato creato nel marzo 2006 dalla Obvious Corporation di San Francisco. Per inserire i propri twit in un sito esterno si usa un widget standard, prelevabile all'indirizzo http://twitter.com /widgets. Per progettare applicazioni che consentano di utilizzare Twitter in modo personale, il sito dedicato ai programmatori è: http://dev.twitter.com. Twitter for Websites: com/about/resources/buttons Consiste di due prodotti. Il primo è costituito da una serie di pulsanti, riutilizzabili per associare ad un articolo un pulsante che permette di attivare una fra quattro azioni possibili: segnalare quello che si è letto, seguire un ID, inviare un tweet con una hashtag già impostata oppure inviare un messaggio ad un ID. Il secondo rivolto ai siti web consente di citare un tweet mantenendo una formattazione aderente a quella della presentazione sul sito di Twitter. Per creare una cornice con un tweet bisogna aprire l’interfaccia web di Twitter twitter.com, fare un clic sulla foto dell’autore e cliccare sul link Details e, quando appare il lightbox con tutti i dettagli del tweet, fare un clic su Embed this tweet. Si apre un’interfaccia che permette di specificare come creare il codice che include il tweet in una pagina esterna. Se il tweet è in risposta ad un altro apparirà la conversazione e la formattazione sarà simile a quella originale. Componenti della piattaforma 1. Autenticazione e gestione dell’identità. 2. Ricerca nel DB. 3. Streaming in tempo reale. SEARCH API La ricerca nel DB di Twitter avviene con due distinte interfacce, entrambe basate sul web, quindi l’invio della richiesta e la ricezione della risposta sono su una chiamata HTTP. 1. Search API: il formato della richiesta è una chiamata ad un indirizzo specifico, seguita dai parametri dell’interrogazione: http://search.twitter.com/search.json?q=cosmic53, ricerca i tweet in cui si fa menzione di uno specifico utente Twitter. 2. REST: GET e POST, alcuni metodi richiedono autenticazione, quelli che accedono a dati pubblici, non ne necessitano; http://twitter.com/statuses/public_timeline.xml e gli elementi delle URL sono dati dal nome della risorsa (statuses), dal metodo invocato (public_timeline), dall’ID dell'elemento su cui si richiede l’azione e dal formato in cui si desidera ricevere la risposta, una volta costruite le URL, per trasmettere le richieste, si usa cURL. TPSIT 5 434 um 406 di Libreria cURL (Client URL Request Library) 1. cURL Funziona da prompt dei comandi, supportato nativamente da Linux, mentre va installato sugli altri SO. F:\>curl http://twitter.com/statuses/public_timeline.xml In risposta si otterranno i 20 twit più recenti degli utenti con profilo pubblico (public_timeline), oltre ad una serie d’informazioni di base su ogni utente, in formato XML. È possibile twittare direttamente dal prompt dei comandi. F:\>curl -u username:password –d status="inserisci qui il tuo messaggio" http://twitter.com/statuses/update.xml L'opzione -u imposta i parametri di autenticazione. L'opzione -d (data) imposta il metodo POST di trasmissione dei dati. Oltre a inserire il nuovo status dell’utente, l’API restituisce una stringa che riporta i dati inseriti, nel formato richiesto nella URL. 2. libcURL È una libreria portabile lato client, permette d’inviare comandi attraverso svariati linguaggi di programmazione. Esempio, progettare una pagina o un widget in cui siano visualizzati i 20 twit, esattamente come questi si susseguono nella timeline della pagina. <?php $url = "http://twitter.com/statuses/friends_timeline.xml"; // curl_init() apre la connessione $ch = curl_init(); // curl_setopt() imposta i parametri della richiesta, CURLOPT_RETURN TRANSFER // consente di ottenere una stringa con il valore della risposta curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_VERBOSE, 1); // CURLOPT_URL imposta l'ULR da trasmettere curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); // CURLOPT_USERPWD imposta i valori di username e password curl_setopt($ch, CURLOPT_USERPWD, "username:password"); // curl_exec() esegue la sessione cURL $result = curl_exec($ch); // curl_close() chiude la connessione curl_close($ch); // simplexml_load_string() accede ai dati: la risposta di twitter, // nel formato richiesto nell'URL (XML) è disponibile in $result $xml = simplexml_load_string($result); foreach ($xml->status as $status) { $icon = '<img src="' . $status->user->profile_image_url . '" title="' . $status->user>screen_name . '" align="middle" />'; $text = utf8_decode($status->text); TPSIT 5 434 um 407 di echo "<p>" . $icon . " " . $text . "</p>\n"; } ?> Esempio, inviare le richieste di aggiornamento dello status, in pratica trasmettere i messaggi di testo di 140 caratteri. <html> <head> <title></title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> <body> <form name="myform" method="post" action="<?php $_SERVER['PHP_SELF']; ?>"> <input name="mytwit" type="text" size="60" maxlength="140"> <input type="submit" name="invia" value="Invia"> </form> <?php // verifichiamo che siano stati trasmessi i dati attraverso il form if(isset($_POST['invia'])) { // attiviamo la connessione a cURL e fissiamo i parametri necessari $msg = $_POST['mytwit']; $url = $url = 'http://twitter.com/statuses/update.xml'; $username = "username"; $password = "password"; $ch = curl_init(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_URL, $url); // CURLOPT_CONNECTTIMEOUT numero di secondi d'attesa prima dell'interruzione della connessione curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2); curl_setopt($ch, CURLOPT_POST, 1); // CURLOPT_POSTFIELDS imposta i parametri da trasmettere col metodo POST curl_setopt($ch, CURLOPT_POSTFIELDS, stripslashes(utf8_encode("status=$msg"))); curl_setopt($ch, CURLOPT_USERPWD, "$username:$password"); $result = curl_exec($ch); curl_close($ch); // Twitter risponde, nel formato richiesto nella URL, con l'elemento status $status = simplexml_load_string($result); $image = $status->user->profile_image_url; $name = $status->user->screen_name; $icon = '<img src="' . $image . '" title="' . $name . '" align="middle" />'; $text = utf8_decode($status->text); echo "<p>" . $icon . " " . $text . "</p>"; } ?> </body> </html> TPSIT 5 434 um 408 di MODULO 4 CLOUD COMPUTING Introduzione Google Microsoft Azure TPSIT 5 434 um 409 di INTRODUZIONE GENERALITÀ La realizzazione di un sito è un’operazione semplice: si sceglie la grafica e si pubblica l’home page e qualche pagina informativa. Per pubblicare le pagine su un server web, dopo la creazione del DNS, si devono eseguire le seguenti operazioni. 1. Assegnare un IP libero al server web. 2. Creare una cartella sul server web. 3. Creare un nuovo sito in IIS. 4. Aprire un punto FTP per consentire la pubblicazione del progetto da parte dei programmatori. 5. Marcare il sito come applicazione e assegnare la versione corretta di ASP.NET. 6. Altre operazioni manuali. I compiti descritti richiedono un intervento amministrativo sul server che comporta il coinvolgimento di una persona diversa dal programmatore sia per un problema di competenza sia per una questione di permessi di accesso. In conclusione, si può verificare una serie di problemi quali la versione di ASP.NET non è stata configurata correttamente, manca spazio su disco per i file, IIS non è configurato bene. In altre parole, un sito web dovrebbe essere gestito da chi ha competenze sistemistiche e dovrebbe essere inserito in una topologia di rete che ne renda sicuro l’accesso. Per le aziende piccole, dove il programmatore “fa un po’ di tutto”, il problema è evidente in quanto una sola persona non può più essere in grado di conoscere i dettagli dell’intero spettro di tecnologie/prodotti/leggi. Nelle grandi aziende che hanno una suddivisione netta dei ruoli fra sistemisti, esperti di sicurezza, programmatori e quindi una competenza elevata in tutti gli ambiti coinvolti dalla creazione di un’applicazione, il problema è ancora maggiore: entrano in gioco meccanismi e dinamiche strane, secondo le quali diventa complesso richiedere anche solamente un ambiente di test per le applicazioni. Scalabilità Chi investe nello sviluppo di applicazioni S/W desidera realizzare prodotti che garantiscano tempi di risposta accettabili anche quando il traffico e la quantità di dati elaborati aumentano. Il programmatore, la scalabilità la gestisce già in fase di progettazione e realizzazione. La gestione dell’applicazione e del sistema stesso ha un costo, l’applicazione vedrà versioni successive da installare, così come devono essere aggiornate frequentemente le tecnologie di base, dal SO al motore di DB. Queste operazioni richiedono molto tempo sia lato sviluppo sia lato sistemistico. Un altro problema è rappresentato dai costi. Acquistare H/W per una nuova applicazione è un’operazione costosa: oltre al costo iniziale, ci sono i costi di manutenzione e sostituzione di componenti obsoleti, senza contare che spesso è difficile, soprattutto per le applicazioni web, dimensionare l’H/W, per esempio per gestire i momenti di “picco”. Hosting Si mette il sito web sul server dell’ISP. Vantaggi: costo basso. TPSIT 5 434 um 410 di Svantaggi: si condivide lo stesso server e la stessa banda con gli altri clienti. Housing Il server è di proprietà e gestito dell’azienda ma è collocato nei locali dell’ISP. Vantaggi: si possono scegliere tutti i componenti desiderati. Svantaggi: è costoso e richiede esperienza nella gestione del server. Se si portano le macchine fisiche da un ISP, non si risolve il problema del costo iniziale e il problema della gestione dell’applicazione; non si risolve neanche il problema di competenze/passaggi d’informazioni: si è semplicemente spostata la locazione fisica dove accadono i problemi. Se si creano VM da mettere in hosting si risolve il problema del costo iniziale in quanto non si deve acquistare H/W ma si possono sfruttare i server dell’ISP; si abbatte anche il tempo di aggiornamento visto che molti upgrade saranno montati direttamente dall’ISP. In ogni caso la gestione della VM è un compito dell’azienda che si complica notevolmente quando si vuole garantire load-balancing o fault-tolerance: non solo si devono installare e manutenere più macchine ma si rende necessario anche effettuare la distribuzione dell’applicazione e delle release/patch successive su tutte le macchine. La risposta a queste esigenze è il cloud computing. In informatica con il termine inglese cloud computing (letteralmente nuvola informatica) s’indica un insieme di tecnologie che permettono, sotto forma di un servizio offerto da un ISP al cliente, di elaborare e/o memorizzare dati grazie all’utilizzo di risorse H/W e S/W distribuite e virtualizzate in rete. L’implementazione effettiva delle risorse non è definita in modo dettagliato; anzi l’idea è proprio che l’implementazione sia un insieme eterogeneo e distribuito: the cloud di risorse le cui caratteristiche non sono note all’utilizzatore. La definizione mette però in luce due aspetti. 1. Utilizzo di risorse distribuite. 2. Risorse le cui caratteristiche non sono note all’utilizzatore, fa riferimento alle caratteristiche fisiche dell’H/W che non sono note neanche al programmatore della soluzione stessa; per risorse fisiche s’intendono i dischi su cui leggere e scrivere, i nomi delle cartelle sul file system, l’indirizzo IP della macchina, il nome della macchina stessa. Per esempio, un VPS (Virtual Private Server) ha la possibilità di espandere le proprie risorse secondo le capacità del nodo fisico che lo ospita. Di fatto, può richiedere più CPU, più memoria e persino più spazio disco, qualora questi valori non dovessero essere sufficienti; tutto ciò, però, non solo non avviene in automatico ma è confinato ad una infrastruttura composta da pochi server. La virtualizzazione rimane pertanto alla base della tecnologia cloud ma in essa trova fattivamente una scalabilità maggiore, data da un’intera infrastruttura progettata per scalare, non solo a livello di server ma anche di storage e networking: è il data center a essere concepito per l’offerta di un servizio cloud e non la singola parte di esso, come avveniva per la virtualizzazione nelle prime infrastrutture create negli anni precedenti. TIPOLOGIE DI SISTEMI CLOUD ASP (Application Service Providing). SOA. SAAS (Software As A Service): applicazioni S/W erogate come servizi online, l’utente utilizza l’applicazione tramite il browser, per esempio posta elettronica, scambio di documenti e produttività individuale. PAAS (Platform As A Service): la gestione dell’infrastruttura H/W e S/W della TPSIT 5 434 um 411 di piattaforma di sviluppo è totalmente trasparente al programmatore che si deve solo concentrare esclusivamente sulla propria applicazione. IAAS (Infrastructure As A Service): offre l’architettura H/W necessaria per l’erogazione di un’applicazione web, per esempio uno o più server, un bilanciatore di carico, un pool d’indirizzi IP e un firewall. HAAS (Hardware As A Service). Esempi di sistemi cloud. Microsoft Azure: SO per il cloud. Antivirus: F-Secure, McAfee, Kaspersky, Panda. Google Docs di Google. Office 365 di Microsoft, MobileMe di Apple. ePrint di HP (Hewlett Packard): cloud printing. MicroERP (Enterprise Resource Planning) di Zucchetti. InpresaSemplice di Telecom Italia, MyCustomerEasy di Olivetti. I costi sono in funzione dei seguenti fattori. Numero di processi utilizzati. Traffico dati. Spazio occupato. Pay only for use Servizi su Internet da utilizzare quando servono e solo per il tempo necessario. Al cliente è fornito un pannello di controllo nativo da cui gestire la creazione di nuove istanze, un’istanza è di fatto una VM e soprattutto un set di API che s’interfacciano direttamente con l’infrastruttura e permettono, quindi, ai clienti di automatizzare, direttamente nelle loro applicazioni, la gestione delle risorse cloud. Quel che si può fare è impostare regole per la gestione delle risorse, per la creazione di nuove VM che distribuiscano il carico con quelle già presenti o modificare il comportamento dell’applicazione nel caso vi sia un alto carico in ingresso. Sono tre le parole che servono per capire cosa differenzia il cloud computing dalla virtualizzazione e cosa quindi porta una VM a essere un’istanza cloud. 1. Scalabilità. 2. Ridondanza. 3. Flessibilità. Il cloud computing è una soluzione ottimale quando si deve gestire un carico non costante, per esempio picchi di utenti e che nel tempo è destinato a crescere. Questo, unito alla necessità di non affrontare un corposo investimento iniziale, rende il TPSIT 5 434 um 412 di cloud computing la soluzione ideale per le aziende che portano online le loro applicazioni, aprendole al pubblico e quindi dovendo affrontare qualsiasi livello di richieste. Cloud Print È una tecnologia concepita per le applicazioni web e mobile, stampare da qualsiasi dispositivo e applicazione su qualsiasi stampante, senza l’uso di alcun driver locale. Utilizzando un componente che tutti i principali dispositivi e SO hanno in comune, l’accesso alla nuvola, il servizio permette a qualsiasi applicazione web, desktop o mobile su qualsiasi dispositivo di stampare su qualsiasi stampante. Anziché affidarsi al driver e all’interfaccia di stampa del SO sottostante, con Cloud Print le applicazioni possono inviare le richieste di stampa ad un proxy remoto insieme ad alcune informazioni sulla stampante locale: il server utilizza queste informazioni per tradurre opportunamente le richieste e inoltrarle direttamente alla stampante attiva. Il sistema che si premura altresì d’informare costantemente l’applicazione sullo stato della stampa, funziona con tutte le stampanti collegate all’USB (Universal Serial Bus) o direttamente alla rete. Il grande vantaggio fornito dalla stampa via cloud è che si bypassa completamente il driver nativo: questo consente di stampare anche dai dispositivi e dai SO per i quali non sono disponibili i driver o che non supportano le funzioni di stampa. Il principio di funzionamento di Cloud Print è basato su stampanti cloud-aware, ossia capaci di comunicare direttamente con la cloud senza l’intermediazione di un PC o di un router: tali stampanti permetteranno, ad esempio, di stampare direttamente da uno smartphone anche quando il PC è spento. Gli elementi con icone differenti fanno riferimento a servizi distinti. 1. dev Rappresenta un servizio di tipo Hosted Service, ovvero il progetto che consente di gestire, testare ed effettuare la distribuzione dell’applicazione. Fornisce la potenza computazionale necessaria all’esecuzione del codice; i parametri di configurazione consentono di aumentare o diminuire il numero delle istanze attive senza effettuare la distribuzione delle applicazioni su nuovi server o nuove VM. Ogni applicazione è installata automaticamente sui nodi indicati nella configurazione TPSIT 5 434 um 413 di liberando il programmatore e il sistemista da tutti i compiti di configurazione di virtual directory, copia di file, gestione dei firewall o di apparati di load-balancing. Il SO fornisce uno spazio in cui le applicazioni possono memorizzare informazioni. 2. dev storage Rappresenta un servizio di tipo Storage Account, ovvero la definizione di uno spazio a disposizione per memorizzare risorse. Il termine servizio indica il “servizio offerto” dal SO: nel primo caso il servizio consta nella possibilità di far girare del codice, mentre nel secondo caso il servizio mette a disposizione spazio per memorizzare le risorse. CLOUD VPS È possibile creare infrastrutture di cloud computing che non necessariamente si appoggiano su più data center e prevedono l’utilizzo di migliaia di server virtualizzati. Cloud privata S’indica una porzione del data center aziendale adibita al cloud computing, composta anche da una serie di Cloud VPS. Cloud pubblica S’intende un servizio esterno che fornisce un’infrastruttura HAAS utilizzabile dai clienti, con il modello pay per use. Un ISP che intende offrire un servizio di cloud computing, deve organizzare la propria struttura in modo da garantire ridondanza a livello di rete, di storage e di server. Occorre un hypevisor che si occupa di gestire tutte le risorse del data center, rendendole dinamiche, ovvero facendo sì che queste possano essere allocate al crescere TPSIT 5 434 um 414 di dell’infrastruttura e possano essere monitorate e gestite da una sola soluzione S/W. In un’infrastruttura di cloud computing si cerca di avere un buon numero di macchine perché queste devono consentire alle singole istanze di migrare da una macchina all’altra nel caso sia necessario e di poter supportare la creazione, in poco tempo, di un buon numero di nuove istanze. Si utilizzano sistemi che uniscono una ridondanza dei dischi ad una velocità di trasmissione, utile per non rendere l’I/O un collo di bottiglia poco sopportabile. Per esempio, SAN (Storage Area Network) collegate alle macchine server in fibra ottica. Le istanze cloud permettono al cliente di scegliere il numero di core di CPU assegnati e di aggiungere più RAM qualora sia necessaria e la banda. Creare nuove istanze e associarle ad un pool comune di VPS è un’altra delle caratteristiche offerte da molti servizi, questo consente di creare reti private fra queste macchine e porre alcune di esse come bilanciatori sulle altre per il traffico in entrata. Amministrazione Deve consentire di riavviare ed eseguire il re-image di ogni istanza senza l’intervento dell’ISP: dev’essere possibile la reinstallazione del SO direttamente da pannello di controllo, oltre al controllo di accensione, spegnimento e riavvio delle macchine. Lo storage associato può essere fornito insieme alla VM o risiedere su un servizio di cloud storage separato che permette di creare volumi. L’amministrazione di una soluzione cloud VPS non differisce da quella di qualsiasi altro sistema server. Un browser non è più soltanto un S/W con cui accedere ad una pagina web ma un vero e proprio ambiente in cui far girare applicazioni di ogni tipo. Se il browser diventa ambiente di lavoro, allora lo si può estendere fino a farlo diventare SO, per esempio Google Chrome OS. Il cloud computing è di due tipi. 1. Low-level: memoria, spazio su disco, in pratica virtualizzazione dei sistemi. 2. High-level: accesso ad un DB, ad un sistema di pagamento online, in pratica servizi di larga scala. Quello che ieri si comprava, oggi si affitta, trasformando così i costi fissi in variabili, il cui ammontare è legato alle necessità dell’azienda. TPSIT 5 434 um 415 di GOOGLE INTRODUZIONE Offre i seguenti servizi. Google Compute Engine Fornisce un sistema di cloud computing basato su VM interconnesse in rete, usato da Google Search e Gmail. Google Cloud Storage Infrastruttura di memorizzazione, backup compreso, con accesso semplice e veloce via REST API. Google BigQuery Permette l’analisi dei dati in real-time, valutandone i trend. Google Cloud SQL Permette di usufruire di DB relazionali MySQL nel cloud. Google App Engine È una piattaforma di hosting semplice da gestire e scalabile. Permette lo sviluppo di applicazioni web con i linguaggi: Java, Python e Go. Le applicazioni sono eseguite in una sandbox, con un accesso limitato al SO e indipendentemente dall’H/W, in modo da poter distribuire il carico di lavoro su più server. I principali servizi offerti con le relative API sono i seguenti. Google Accounts: è possibile integrare nell’applicazione l’autenticazione dell’utente con l’account di Google. URL Fetch: l’applicazione può accedere alle risorse di rete come WS e dati. Mail: è possibile usare l’infrastruttura di Google Mail. Memcache: cache di tipo key-value accessibile da istanze multiple dell’applicazione, utile per tenere traccia di dati temporanei che non devono essere resi permanenti. Image Manipulation: servizio di manipolazione delle immagini. GWT (Google Web Toolkit): permette di progettare interfacce mediante il drag & drop e la generazione automatica del codice. Sandbox È lo spazio privato in cui un’applicazione vive e non può accedere a sandbox di altre applicazioni. L’accesso alle risorse è vincolato solo all’interno della sandbox di ciascuna applicazione. TPSIT 5 434 um 416 di MICROSOFT AZURE INTRODUZIONE È il servizio cloud di Microsoft, a differenza degli altri servizi simili, Azure è stata progettato come un vero e proprio SO per ambienti cloud. C’è coerenza tra ambienti S/W in locale e nel cloud con lo sviluppo S/W, infatti, la gestione e l’installazione funzionano sia in locale sia in remoto. È composto dai seguenti componenti. 1. AppFabric Controller: è il servizio di gestione dell’intera infrastruttura e si occupa della distribuzione, dell’aggiornamento, della gestione e del monitoraggio delle applicazioni. 2. Storage: memorizza dati. 3. Compute: è l’ambiente di esecuzione per due tipi di applicazioni. 3.1. Web Role: applicazioni web in grado di esaudire richieste di tipo HTTP o HTTPS. 3.2. Worker Role: è un progetto di tipo back-end, non è esposto su un URL pubblico, non è accessibile dall’esterno e gira in background, come i servizi Windows. 4. Cloud service: esegue le applicazioni in maniera scalabile e affidabile. 5. SQL Azure: permette di gestire RDBMS (Relational DBMS) nel cloud. 6. Web site: ospita un sito Internet. 7. Mobile service: servizi che permettono alle app di fornire un back-end e un sistema di push notification. TPSIT 5 434 um 417 di 8. CDN (Content Delivery Network): servizi di memorizzazione ridondanti tra i server per migliorare la velocità delle risposte, rende più rapido l’accesso ai dati binari tramite un sistema di cache, l’obiettivo è migliorare le performance di lettura degli utenti “lontani” rispetto allo storage. 9. Service bus: permettono la veicolazione di messaggi tramite code e sottoscrizione. 10. Media service: automatizza i processi di codifica dei contenuti multimediali. 11. Connessione: gestisce il livello IP. Azure è un SO scalabile, fault-tolerant, una piattaforma di servizi: ASP (Azure Services Platform) dove è possibile acquistare una serie di unità elaborative, unità di conto o cloud unit, sotto forma di VM, basate su Windows Server. Azure SQL Data Warehouse È un AAS che gestisce PB di dati e porta il data analytics ad un nuovo livello di scalabilità ed efficienza. Azure Data Lake È un repository per i big data a livello di PB. SQL Elastic DataBase PROGRAMMAZIONE Bisogna installare Azure SDK e l’emulatore locale per l’ambiente di hosting. L’integrazione con l’ambiente di sviluppo è possibile tramite l’installazione di Azure Tools per Visual Studio, per la creazione, la configurazione e il test di applicazioni e servizi per Azure. Per creare un nuovo progetto in Visual Studio per questa piattaforma, fare clic su File/Nuovo/Progetto… (CTRL+MAIUSC+N). Nella finestra di dialogo Nuovo Progetto selezionare un linguaggio a scelta dall’elenco, per esempio Visual C#/Cloud. Selezionare il template Progetto servizio cloud Windows Azure. Immettere il nome nel campo Nome: WindowsAzure1, quindi per creare il progetto, premere OK. TPSIT 5 434 um 418 di Nella finestra successiva, specificare il tipo di applicazione, per esempio selezionare Ruolo Web ASP.NET. Eventualmente, se si avesse un progetto ASP.NET, è possibile creare un nuovo progetto e aggiungerlo in seguito al progetto esistente. Premuto il tasto OK, si apre la finestra seguente. TPSIT 5 434 um 419 di Premuto il tasto OK, Visual Studio crea una soluzione con al suo interno due progetti. TPSIT 5 434 um 420 di WebRole1 È il Web Role da sviluppare, un servizio di front-end esposto su un URL pubblico e accessibile, in pratica è una normale applicazione ASP.NET o ASP.NET MVC. File WEBROLE.CS Contiene l’importazione dei namespace per lo sviluppo in the cloud. using System; using System.Collections.Generic; using System.Linq; using Microsoft.WindowsAzure; using Microsoft.WindowsAzure.Diagnostics; using Microsoft.WindowsAzure.ServiceRuntime; namespace WebRole1 { public class WebRole : RoleEntryPoint { public override bool OnStart() TPSIT 5 434 um 421 di { return base.OnStart(); } } } File DEFAULT.ASPX <%@ Page Title="Home page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="TestWebRole1._Default" %> <asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent"> </asp:Content> <asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent"> <h2> ASP.NET </h2> <div> Inserisci il tuo nome: <asp:TextBox runat ="server" ID = "txtNnome" /> <asp:Button runat ="server" ID = "btnPulsante" Text ="Premi" OnClick = "btnPulsante_Click"/> <br /> <asp:Label runat ="server" ID = "lblNome" /> </div> </asp:Content> File DEFAULT.ASPX.CS using System; using System.Web.UI; namespace WebRole1 { public partial class _Default : Page { protected void Page_Load(object sender, EventArgs e) { } protected void btnPulsante_Click(object sender, EventArgs e) { lblNome.Text = String.Format("Ciao, {0}!", txtNnome.Text); } } } WindowsAzure1 Rappresenta l’istanza di Windows Azure, in questo caso simulata dallo SDK, sulla quale effettuare la distribuzione dell’applicazione, si trova in tutti i progetti per Windows Azure. File SERVICECONFIGURATION.CLOUD.CSCFG È il modello che descrive le necessità dell’applicazione, in pratica è il file di configurazione del servizio e contiene la definizione del <Role>, al suo interno si trova l’elemento <Instances> il cui attributo count è impostato a uno. Con questa sintassi s’indica a Windows Azure che si vuole una sola istanza del servizio. <?xml version="1.0" encoding="utf-8"?> <ServiceConfiguration serviceName="WindowsAzure1" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="4" osVersion="*" schemaVersion="2013-10.2.2"> <Role name="WebRole1"> <Instances count="1" /> TPSIT 5 434 um 422 di <ConfigurationSettings> <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="UseDevelopmentStorage=true" /> </ConfigurationSettings> </Role> </ServiceConfiguration> File SERVICECONFIGURATION.LOCAL.CSCFG <?xml version="1.0" encoding="utf-8"?> <ServiceConfiguration serviceName="WindowsAzure1" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="4" osVersion="*" schemaVersion="2013-10.2.2"> <Role name="WebRole1"> <Instances count="1" /> <ConfigurationSettings> <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="UseDevelopmentStorage=true" /> </ConfigurationSettings> </Role> </ServiceConfiguration> File SERVICEDEFINITION.CSDEF Le impostazioni descrivono il servizio per il nodo su cui sarà installato: in base al numero delle istanze, Windows Azure esegue la distribuzione su VM diverse; in base all’impostazione di questo file ogni nodo risponde in ingresso sulla porta 80. <?xml version="1.0" encoding="utf-8"?> <ServiceDefinition name="WindowsAzure1" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition" schemaVersion="2013-10.2.2"> <WebRole name="WebRole1" vmsize="Small"> <Sites> <Site name="Web"> <Bindings> <Binding name="Endpoint1" endpointName="Endpoint1" /> </Bindings> </Site> </Sites> <Endpoints> <InputEndpoint name="Endpoint1" protocol="http" port="80" /> </Endpoints> <Imports> <Import moduleName="Diagnostics" /> </Imports> </WebRole> </ServiceDefinition> Compilare ed eseguire la soluzione con i diritti di Amministratore, Visual Studio esegue il componente di Azure SDK denominato Compute Emulator. L’emulatore dell’applicazione server di Azure che ha il compito di effettuare la distribuzione su cartelle locali senza necessità di configurare IIS e l’esecuzione dell’applicazione. In pratica, legge il file SERVICECONFIGURATION.CSCFG per identificare il numero delle TPSIT 5 434 um 423 di istanze e avvia i processi relativi. Nell’area di notifica compare una nuova icona. A fronte delle richieste che arrivano via HTTP, l’emulatore effettua il load-balancing sui processi avviati, si può verificare questo comportamento aprendo la console di amministrazione che si presenta con tanti box quanti sono i processi. Sono visualizzati messaggi di trace. È possibile scrivere nel trace tramite il metodo WriteToLog della classe RoleManager. TPSIT 5 434 um 424 di In Gestione attività Windows si trova il processo chiamato WAIISHOST.EXE che rappresenta l’istanza creata in automatico. Eseguire il kill del processo: l’emulatore ricrea immediatamente l’istanza simulando quello che avverrà su Azure perché è garantita la fault-tolerance anche fra nodi, ovverosia: se una VM è disconnessa oppure va in crash, il SO si occupa di distribuire il servizio su una nuova VM. L’emulatore apre il browser e avvia l’applicazione. TPSIT 5 434 um 425 di Dopo aver sottoscritto un abbonamento per i servizi di Azure, si ha a disposizione un’istanza dove poter caricare l’applicazione, in due ambienti. 1. Production: ambiente di produzione che conterrà l’applicazione e che potrà essere visto dagli utenti del sito web. 2. Staging: ambiente di test per il caricamento temporaneo dell’applicazione modificata. Il contenuto dei due ambienti può essere scambiato in qualsiasi momento, permettendo di testare l’applicazione nell’ambiente di produzione. In caso di malfunzionamento, si possono scambiare nuovamente i contenuti dei due ambienti per ripristinare l’applicazione pubblica e per sottoporre a nuova fase di test l’applicazione non ancora perfettamente funzionante. Per eseguire l’upload sul portale, selezionare il progetto WindowsAzure1 e fare clic con il pulsante destro, dal menu contestuale selezionare Pacchetto…. TPSIT 5 434 um 426 di Visual Studio crea due file. File WINDOWSAZURE1.CSPKG Rappresenta il package dell’applicazione, ovvero il codice compilato dell’applicazione. Qualunque modifica al codice nei file contenuti nel progetto implica un nuovo processo di distribuzione, è possibile eseguire l’upgrade in-place dei progetti senza dovere seguire per intero il processo di distribuzione. È necessario eseguire l’upload del CSPKG e decidere quali Web Role aggiornare, così come quali eventuali modifiche effettuare alla configurazione esistente. File SERVICECONFIGURATION.CLOUD.CSCFG <?xml version="1.0" encoding="utf-8"?> <!-- ********************************************************************************************** Questo file è stato generato mediante uno strumento dal file di progetto: ServiceConfiguration.Cloud.cscfg Le modifiche a questo file possono causare un comportamento non corretto e verranno perse se il file viene rigenerato. ********************************************************************************************** --> <ServiceConfiguration serviceName="WindowsAzure1" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="4" osVersion="*" schemaVersion="2013-10.2.2"> <Role name="WebRole1"> <Instances count="1" /> <ConfigurationSettings> <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="UseDevelopmentStorage=true" /> </ConfigurationSettings> </Role> </ServiceConfiguration> Si aprirà la pagina web che consente di autenticarsi con il Live ID per accedere all’istanza di Azure. TPSIT 5 434 um 427 di Una volta effettuato l’accesso fare clic sul pulsante Deploy, si apre una pagina di caricamento dei due file generati. Una volta terminata la fase di caricamento, l’applicazione sarà posta nello stato di Stopped. Con un clic sul pulsante Run, l’applicazione passerà prima allo stato Initializing, poi Busy e infine Ready. Nel momento in cui si vuole sospendere l’applicazione, sia nell’ambiente di produzione sia in quello di staging, premere il pulsante Suspend e l’applicazione della relativa area sarà sospesa, con la possibilità di essere riavviata in un altro momento. Appena si procede alla sospensione, apparirà un messaggio che informa che non sarà sospeso l’addebito per il servizio. Per evitare di sostenere spese inutili per un’applicazione non avviata, è quindi necessario eliminare l’applicazione, pulsante Delete e cancellare il servizio. Azure segue le istruzioni e durante una distribuzione installerà l’applicazione in un solo nodo del data center. In qualunque momento si può premere il pulsante Configure sul portale e modificare questa impostazione per far sì che il SO distribuisca il carico su tanti nodi quante sono le istanze configurate. Una volta premuto Run l’applicazione sarà raggiungibile al suo URL. In pratica, il programmatore “deve” concentrarsi sul codice dell’applicazione, quindi scriverlo il meglio possibile e indicare ad Azure le necessità. Maggiori sono le informazioni che si forniscono nel modello applicativo, migliore sarà il supporto che il SO fornirà per gestire le politiche di ripartizione del carico. L’onere della gestione fisica delle macchine e degli strumenti di amministrazione è compito di chi offre la piattaforma, nell’esempio Microsoft che offre Azure. Questo coinvolge anche l’aspetto della manutenzione. Sicuramente nessuno meglio di Microsoft conosce il .NET Framework, il kernel del SO e servizi come IIS e SQL Server. La lettura/scrittura sul file system è identica al PC: l’unica differenza è rappresentata dal percorso in cui si deve scrivere che potrà essere sempre diverso a seconda del nodo virtuale su cui l’applicazione gira. Per ottenere la root per gestire file e cartelle, si usa la classe RoleManager. lblPercorso.Text = RoleEnvironment.GetLocalResource("StorageLocale").RootPath; Se si testa l’applicazione in locale si vede che ogni istanza avrà la sua cartella su disco creata durante la distribuzione dell’applicazione da parte dell’emulatore. Data la natura “locale” del LS, è indispensabile utilizzare questo spazio solo per dati temporanei: questo spazio non è condiviso dalle varie istanze e quindi non può adattarsi ad un’applicazione che deve poter essere scalabile. Inoltre, è uno spazio che è perso nel caso in cui l’istanza vada in crash e Azure decida di spostare l’istanza su un server più potente in base al modello applicativo. Il LS si utilizza anche per gestire la cache applicativa ma non dev’essere usata per i dati gestiti dall’applicazione. STORAGe LS (Local Storage) In Azure è possibile configurare LS per sopravvivere ai recycle del processo, tramite l’attributo cleanOnRoleRecycle della configurazione <LocalStorage>. TPSIT 5 434 um 428 di Questa impostazione consente di evitare la cancellazione dello store locale in caso di riciclo del processo: ad esempio, a fronte di una modifica alla configurazione oppure in seguito ad un upgrade, è probabile che il processo dove sta girando l’applicazione debba essere riciclato, come accade in IIS nelle soluzioni che girano su un PC. Questo flag consente di evitare di perdere il contenuto del LS, in ogni caso lo storage è locale all’istanza ed è comunque perso in caso di spostamento del codice su un altro nodo del cloud. È sufficiente la definizione di uno storage nel file SERVICECONFIGURATION.CSDEF. <LocalStorage name="StorageLocale" sizeInMB="10"/> Il file indica ad Azure e durante lo sviluppo in locale con l’emulatore di creare uno storage sull’istanza denominato Storage Locale. Indicare la dimensione è un’informazione preziosissima per il load-balancer che dovrà scegliere una VM con capacità sufficiente ad accogliere lo storage. Storage Account Questo servizio rappresenta un “posto” dove le applicazioni, quelle legate all’account, possono gestire i dati e permette l’utilizzazione di tre strutture dati specifiche. Sono accessibili da applicazioni ospitate su Azure così come da applicazioni che girano su 1. BLOB (Binary Large OBject) File binari di grande dimensione. 2. Queue Code di messaggi necessarie per gestire la comunicazione asincrona tra uno o più Worker Role e uno o più Web Role, come MSMQ. 3. Table Tabelle semplici per gestire dati strutturati, questo nome è fuorviante, in quanto ricorda un DB in cui memorizzare dati in tabelle; invece è uno spazio diverso. Non necessita della definizione delle colonne: è possibile memorizzare e ricercare un’entità composta da un numero arbitrario di proprietà. Nella stessa tabella possono essere memorizzate informazioni diverse. Non si definiscono indici o relazioni fra tabelle, è gestito un indice sulla chiave primaria ed è l’unico a poter essere utilizzato. Non ci sono relazioni fra tabelle in quanto il servizio ha nulla a che vedere con un DB relazionale. Non esistono le tipologie di colonne; la tipologia di dato è definito direttamente nella struttura dell’entità che si vuole memorizzare. Lo storage creato diventa quindi accessibile “da ovunque” senza bisogno di acquistare e TPSIT 5 434 um 429 di configurare server e dischi, politiche di accesso dal firewall, creazione di servizi raggiungibili dall’esterno e problema di gestione della sicurezza. L’accesso via REST garantisce l’utilizzo da Python, PHP, Ruby e JavaScript. Affinity group Ha un duplice obiettivo. 1. Indicare in quale data center, regione geografica, devono essere memorizzati fisicamente i dati. 2. Se anche l’applicazione è ospitata su Azure, è importante tenere “vicini” codice e storage per evitare roundtrip fuori dal data center per ogni richiesta. Questa vicinanza può essere garantita con la creazione di un affinity group in cui tenere le applicazioni e lo storage: se questi due elementi stanno nello stesso affinity group è garantito l’utilizzo di macchine dello stesso data center per entrambi. Fornisce tre endpoint pubblici, esposti tramite HTTP che rappresentano gli indirizzi di base per l’accesso a BLOB, queue e table. Per gestire i BLOB si deve effettuare una richiesta GET su questa URL per recuperare un file, una richiesta POST per inserire un nuovo file, una richiesta PUT per modificare un file, una richiesta DELETE per cancellare un file. Ognuna di queste richieste deve autenticarsi verso lo storage prima di poter essere eseguita. Le due chiavi, Primary e Secondary servono a questo scopo: è necessario firmare le richieste con un algoritmo di hashing utilizzando una delle due chiavi. Custom Domains Consente di gestire gli URL degli endpoint tramite i propri indirizzi DNS. DISTRIBUZIONE E DEBUG Nel momento in cui si sviluppa un’applicazione, ci si trova di fronte al problema di dover spostare tutti servizi: hosting, storage e DB, dal PC locale ad Azure. Quando tutti i servizi sono ospitati nel PC locale di sviluppo, si può eseguire il debug come con qualsiasi altra applicazione, fermando l’esecuzione su una specifica istruzione, per esempio per verificare il contenuto delle variabili. Quando tutti i servizi sono su Azure, invece, le possibilità di debug sono ridotte. Ci possono essere tre situazioni diverse. 1. local:local: si ha tutto nel PC di sviluppo: sia l’applicazione sia i servizi cui esso si collega; in questa fase si può completare lo sviluppo dell’applicazione e si può effettuare il debug di circa il 70% dell’applicazione perché non è utilizzata la rete. 2. cloud:cloud: si ha tutta l’applicazione e tutti i servizi sul cloud all’interno di uno stesso data center; si può verificare il funzionamento dell’applicazione e terminare lo sviluppo. 3. local:cloud: si ha l’applicazione nel PC di sviluppo, mentre i servizi di storage e di DB si trovano su Azure; si può ancora eseguire il debug dell’applicazione; questa fase permette di completare e di effettuare il debug per un altro 20-25% dell’applicazione, questa fase ha le prestazioni peggiori rispetto alle altre due. L’ambiente che esegue l’applicazione è il PC, mentre l’ambiente che offre i servizi di storage e di DB è in un data center. Questo significa che sull’applicazione incide fortemente l’attività di comunicazione in rete, tra il PC e il data center. Si può usare questa fase per mettere sotto stress l’applicazione: gli eventuali ritardi e time out di comunicazione possono così essere testati nelle condizioni peggiori possibili. Progettare un client di chat il cui server utilizza il DB SQL Azure per memorizzare gli utenti registrati. TPSIT 5 434 um 430 di Codice per creare e aggiornare il DB degli utenti. USE [ChatApp] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO DROP TABLE [dbo].[Users]; GO CREATE TABLE [dbo].[Users] ( [ScreenName] NVARCHAR (50) NOT NULL, [EmailAddress] NVARCHAR (250) NOT NULL, [Password] NTEXT NOT NULL, [Online] BIT NOT NULL, [ClientID] INT IDENTITY (1, 1) NOT NULL); USE [ChatApp] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO DROP TABLE [dbo].[Friends]; GO CREATE TABLE [dbo].[Friends] ( [UserID] INT NOT NULL, [FriendsUserID] INT NOT NULL ); TPSIT 5 434 um 431 di Client FILE FRMCHAT.CS Applicazione client. File FRMLOGIN.CS Il form di login per l’utente. File FRMREGISTER.CS Il form di registrazione per l’utente, per evitare il collegamento al server web di registrazione. Server CLASSES/AZURESQL.CS Il modulo contiene tutti le istruzioni SQL e le query di consultazione del FB. CLASSES/PUBLICIP.CS Il modulo contiene il codice per ottenere l’IP pubblico. FORMS/FRMAZUREDATABASELOGIN.CS Il form di login per il DB di Azure. File FRMSERVER.CS Applicazione server. TPSIT 5 434 um 432 di MOBILE SERVICES È lo strumento per progettare un back-end ospitato su un DB SQL Server sul cloud che potrà essere interrogato tramite chiamate REST con supporto a OData per il filtraggio e manipolazione dei dati, senza bisogno di scrivere esplicitamente il codice di accesso, poiché lato server è tutto gestito automaticamente dall’infrastruttura. In caso di necessità si può usare uno script basato su NODE.JS per specificare le operazioni da eseguire in fase d’inserimento, lettura, modifica, cancellazione dei dati e definire attività schedulate su richiesta o a intervalli di tempo. I Mobile Services possiedono anche le seguenti funzionalità. Supporto all’autenticazione: Microsoft Account. Twitter. Facebook. Google: protocollo OAuth. Modifiche push verso i dispositivi. Il Mobile Services SDK gestisce la comunicazione con il server ed è disponibile anche per IOS e Android. Progettare un Mobile Services per gestire un’agenda da un’app Windows Store, con autenticazione Facebook e uno scheduler per inviare promemoria delle attività inserite. Creare il Mobile Services in Azure, quindi nella procedura guidata assegnare il nome alla risorsa e specificare il DB da usare. Definire due tabelle. 1. Appointments: memorizza gli appuntamenti. 2. Channels: gestisce le notifiche push. Registrare l’app sul Windows Store per ottenere il token di accesso al servizio, memorizzare Identificatore di sicurezza del pacchetto (SID) e Chiave privata client perché si dovrà inserire nei campi Package SID e Client.Secret della sezione PUSH del pannello di amministrazione del Mobile Services. Progettare un’app su Facebook cui collegare il servizio, memorizzare App ID/API Key e Chiave segreta dell’applicazione perché si dovranno inserire nei campi App ID/API Key e App Secret della sezione IDENTITY del Mobile Services sotto la voce facebook settings. TPSIT 5 434 um 433 di Fare clic con il pulsante destro sul nome del progetto in Esplora soluzioni, dal menu contestuale selezionare Store/Associa applicazione a store… seguire la procedura guidata per aggiornare il file manifest con i dati del Windows Store. Aggiungere il Mobile Services SDK al progetto. L’UI è composta da due parti. 1. L’area per inserire i nuovi appuntamenti. 2. L’elenco delle attività. TPSIT 5 434 um 434 di