Il lavoro di questi giorni sul mio IMHO 2.0 sta vertendo su un tema che ha suscitato il mio interesse e mi ha stimolato molto. La necessità deriva dal nuovo "preview pane" che dovrebbe consentire la visualizzazione dei post già all'interno della finestra di gestione invece che in quella di edit. L'architettura di IMHO, che ricordo essere basata su .NET remoting, per effettuare una preview del post con tutte le immagini ad esso collegate richiederebbe di scaricare in locale il testo del post e tutte le immagini ricreando una copia temporanea da passare al controllo WebBrowser. Pur essendo indubbiamente fattibile questo comporta di dover fare il parsing dell'html e di re-referenziare le immagini, oltre a dover attendere il completo scaricamento dei file prima di iniziare la visualizzazione. L'idea che alla fine mi è balenata è quella di sfruttare al 100% le capacità del controllo WebBrowser e di lasciare a lui l'onere di fare quelo che in effetti è il suo lavoro. Questo però mi ha posto il problema di dover realizzare un mini Web Server che rispondesse alle chiamate HTTP dal lato server del sistema, così da intervenire nel processo il minimo indispensabile. Ecco quindi che comincia con questa puntata una serie di articoli che ripercorrono le tematiche, anche piuttosto complesse che si sono presentate nell'implementare questo affascinante protocollo.

Cominciamo subito con il dire che nel Framework 2.0 esiste una classe, la HttpListener, che in realtà fa già tutto quello che serve. Tale classe però ha una piccola ma significativa limitazione ovvero funziona solo con Windows 2003 Server o con Windows XP dalla SP2 in su. Tale limitazione, che per i più può apparire insignificante, nel mio caso è una barriera insormontabile, infatti è davvero inaccettabile che IMHO non possa funzionare con Windows 2000 che è un sistema ancora molto diffuso o anche solo con Windows XP SP1 o Windows 98. Ecco quindi che ho dovuto rimboccarmi le maniche e iniziare a scrivere il codice di un web server praticamente da zero, anche se lo confesso, pensare di scrivere qualcosa da zero con il framework .NET è proprio impossibile.

Un piccola overview

Pensare di scrivere un web server e farlo sul serio sono due cose molto distanti l'una dall'altra, separate perlomeno dal proverbiale mare. Le problematiche che si debbono affrontare sono le più disparate, e in questo paragrafo vorrei dare un'idea della loro natura.

Il primissimo problema che si incontra è di threading. Un server http, in quanto server non può certo essere un eseguibile in puro stile Java che occupa il desktop del pc quando lanciato. Esso deve essere in grado di funzionare in qualunque momento anche se il server è chiuso in una sala macchine sotto chiave con la macchina non loggata. Ecco quindi che è richiesto che tale server giri sul sistema come servizio windows, e dato che nel mio caso il servizio esisteva già deve anche configurarsi come un thread parallelo eseguito all'interno di tale servizio. In realtà, volendo realizzare una classe furba, si dovrà pensare che ad ogni istanza di classe vi sia un server in ascolto su un determinato indirizzo ip e su una determinata porta. Perciò lanciando più classi si avranno più server contemporaneamente attivi e ad ognuno di essi corrisponderà un Thread che si occupa dell'evasione delle richieste.

Il secondo problema è di comunicazione. Un server http per definizione deve mettersi in ascolto su una determinata porta tcp accogliere richieste ed evaderle. Quindi occorre almeno scomodare i socket per implementare questa fondamentale funzione, considerando però che le richieste pervenute potranno essere multiple che quindi bisognerà lavorare in modo non bloccante. Per fortuna il framework .NET dispone di un set di classi splendide che permettono di assolvere a questa parte in modo molto efficace, anche se comunque del lavoro da fare c'è sempre.

Un altro problema è dato da una affermazione del paragrafo precedente. Un server http deve essere in grado di evadere più richieste contemporaneamente e questo comporta che le richieste debbano essere evase contemporaneamente. Ecco quindi che occorrerà lanciare un thread per ognuna di esse in modo che il thread principale sia libero di tornare in ascolto il più rapidamente possibile.

Bisogna poi considerare che il thread principale non è "infinito" ma anzi deve essere in grado di avviarsi e fermarsi "gracefully" per usare un termine inglese. Questo che può sembrare ovvio, in realtà impedisce di usare appieno alcune classi del framework che nella pratica invece bloccano l'esecuzione fino all'arrivo di una nuova chiamata. Per questo motivo occorrerà definire un AutoResetEvent che informi il thread principale di quando è venuto il momento di uscire.

Infine, last but not least il problema di gran lunga più complesso è quello di fare un parsing efficace delle richieste in arrivo e di creare una struttura che sopperisca per la gran parte ai dettami del protocollo HTTP 1.1. Questo comprende il supporto a tutti i codici nelle risposte, 200, 300, 400 e 500, la lettura degli headers, il supporto ad un subset efficace dei metodi (GET, POST), senza poi considerare che volendo scrivere una singola classe che si occupi di questo bisognerà corredarla dei metodi e delle proprietà adatte a configurarne il funzionamento e a rispondere alle richieste.

Progettiamo le classi

Come ogni buon progetto, la prima fase del nostro lavoro è quella di fare un'ipotesi sulle classi che dovranno essere realizzate. In realtà nel nostro caso il disegno delle classi è addirittura fondamentale trattandosi di un componente che dovrà essere riutilizzato, e quindi a tutti gli effetti di una libreria. L'idea, come dicevo poche righe fa, è quello di avere una classe che una volta istanziata si occupi di tutto ciò che serve. E vero che si potrebbero delegare i vari compiti a varie classi, ma in prima battuta una classe unica mi è sembrata la soluzione migliore. Magari alla fine con un po di refactoring vedremo di definire al meglio il design. Chiamiamo HttpServer tale classe e definiamo che essa avrà un certo numero di costruttori con vari parametri il cui scopo è sostanzialmente di determinare quale indirizzo IP e quale porta essa dovrà usare. La medesima classe inoltre disporrà di un metodo Start() e di un metodo Stop() dei quali non è necessario spiegare l'utilità, ma che in definitiva governeranno l'avvio e l'uscita del thread principale. Infine la classe HttpServer dovrà esporre un evento RequestArrived che consenta all'applicazione che l'ha istanziata di gestire le chiamate e di fornire le corrette risposte. Infatti, a differenza di un normale Web Server, la nostra classe non avrà coscienza (se mi perdonate il termine) della directory da cui prelevare i file, ma semplicemente notificherà per mezzo di un evento l'arrivo della richiesta e demanderà all'handler la creazione della risposta. In questo modo, si potrà agire anche in modi diversi, ad esempio creando al volo le risposte.

Chiaramente, nominando Richiesta e Risposta viene naturale pensare che debbano esistere delle classi che le rappresentano. In particolare esse saranno HttpServerRequest e HttpServerResponse. Mentre la prima verrà creata dal server come risultato del parse della richiesta HTTP, la seconda dovrà essere confezionata dall'handler che gestisce la richiesta sulla base della HttpServerRequest ricevuta.

Infine, almeno per ora occorre considerare che esistono 4 possibili soluzioni di una chiamata HTTP. Queste quattro soluzioni, rispondono alle classi di codice restituito dal web server. 200, sono i codici che informano sul buon esito della richiesta, 300 indicano le richieste di redirect, 400 gli errori di tipo client (ad esempio il famoso 404 not found) e infine gli errori di tipo server che ricadono nella classe 500. Se consideriamo che solo i codici 200 indicano che la richiesta è stata soddisfatta, potremmo considerare gli altri casi come delle eccezioni e trattarli appunto come tali definendo delle opportune classi derivanti da HttpServerException. Quindi per i codici 300 avremo HttpServerRedirectException, per i codici 400 HttpServerClientException, e naturalmente HttpServerServerException per i 500. Mentre codici di tipo 500 potranno essere generati anche dalla classe HttpServer stessa, in caso di una eccezione non gestita interna (internal server error vi dice niente?), tutti gli altri dovranno essere sollevati dall'handler che gestirà le richieste. Internamente il server http elaborerà queste eccezioni trasformandole nel relativo codice di ritorno.

Ecco quindi che abbiamo definito le classi del dominio, che nelle prossime puntate appunto cominceremo a realizzare. Per questa volta l'articolo è finito, ma naturalmente vi invito a mandarmi i vostri commenti in merito al fine di migliorare il design prima di procedere con le successive fasi.


Commenti (1) -

# | Radicalmente | 05.12.2005 - 00.35

Aggiungi Commento