Gli HTTP Module di ASP.NET sono oggetti che vengono chiamati ad ogni richiesta effettuata su qualsiasi tipo di risorsa della nostra applicazione Web. In un articolo precedente abbiamo approfondito la conoscenza degli HTTP Handler, una parte molto importante della pipeline di ogni richiesta ASP.NET, con i moduli siamo in grado di modificare e quindi estendere il ciclo di vita di ogni richiesta HTTP e di metter mano a particolari eventi di questo ciclo.
In un modulo è possibile accedere agli stessi eventi della pipeline di richiesta che gestiamo nel Global.asax
. In questo file sono presenti infatti tutti gli event handler per gestire gli eventi scatenati durante le richieste HTTP (BeginRequest, EndRequest, etc.).
È sconsigliabile però usare il Global.asax per almeno due ragioni:
- è decisamente scomodo in fase di deployment
- viene meno la modularità dell'applicazione Web (nel .NET Framework in generale, abbiamo classi specifiche per ogni tipo di funzionalità).
Proprio per questi motivi, abbiamo la possibilità di creare i nostri moduli personalizzati che reagiscano ad eventi precisi durante le richieste HTTP per eseguire particolari operazioni in base alle nostre esigenze.
Il runtime di ASP.NET fa un uso decisamente elevato degli HTTP Module. Basti pensare che tutte le features come l'autenticazione (anonima, integrata o tramite Form), le autorizzazioni su file e percorsi, i ruoli, i profili, la gestione della sessione e della cache, sono tutte implementate attraverso degli HTTP Module.
Tale scelta è stata sicuramente dettata dalla possibilità di aggiungere funzionalità particolari alla pipeline di esecuzione di ogni richiesta HTTP.
Come per gli HTTP Handler, anche gli HTTP Module hanno una sezione del web.config
a loro riservata per permetterci di visualizzare quelli registrati di default nel runtime di ASP.NET, per eliminarne (in caso non ci volessimo avvalere di qualche funzionalità di base del framework) e per registrare i nostri personalizzati. Questa sezione di configurazione è rappresentata dall'elemento <httpModules />
, figlio diretto dell'elemento <system.web />
.
Per visualizzare i moduli predefiniti, basta raggiungere il percorso
C:WINDOWSMicrosoft.NETFrameworkVx.x.xxxxxCONFIG
e visualizzare contenuto del file web.config
(file unico per tutte le applicazioni web installate sul server).
Questi i principali HTTP Module utilizzati dal framework di ASP.NET:
- FormsAuthenticationModule - Modulo per gestire l'utilizzo dell'autenticazione tramite le Forms (rappresentato dall'omonima classe, presente sotto il namespace
System.Web.Security
). - ProfileModule - Modulo per gestire l'utilizzo dei profili nel sito web (rappresentato dall'omonima classe, presente sotto il namespace
System.Web.Profile
). - UrlAuthorizationModule - Modulo in grado di autorizzare o negare l'accesso all'utente su particolari percorsi richiesti (rappresentato dall'omonima classe, presente sotto il namespace
System.Web.Security
). - SessionStateModule - Modulo per l'utilizzo della sessione nell'applicazione web (rappresentato dall'omonima classe, presente sotto il namespace
System.Web.SessionState
). - OutputCacheModule - Modulo per l'utilizzo dei meccanismi di caching propri di ASP.NET (rappresentato dall'omonima classe, presente sotto il namespace
System.Web.Caching
).
La creazione di un HTTP Module personalizzato
Un HTTP Module, è una classe .NET che implementa l'interfaccia IHttpModule
che espone due metodi:
- Init() - metodo che viene richiamato alla creazione di ogni nuova istanza dell'applicazione web, e che solitamente viene utilizzato per dichiarare quali eventi della pipeline di esecuzione devono essere gestiti dall'HTTP Module. Questo metodo, prende come parametro l'istanza dell'app domain corrente (un oggetto di tipo
HttpApplication
). - Dispose() - metodo che viene invocato per la rimozione dalla memoria unmanaged, delle risorse utilizzate dal modulo.
La classe che dobbiamo creare, che implementa l'interfaccia IHttpModule
, possiamo o inserirla in un'assembly specifico, o possiamo salvarla direttamente nella directory App_Code
in modo da sfruttare le nuove caratteristiche di compilazione a runtime messe a disposizione da ASP.NET 2.
Come primo esempio, creiamo un modulo che intercetti l'inizio della richiesta e la fine, e che in fondo alla pagina, stampi a video i secondi impiegati per l'esecuzione completa della richiesta.
using System;
using System.Web;
public class TimerHttpModule : IHttpModule
{
private DateTime startDate, endDate;
public void Dispose() {}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(OnBeginRequest);
context.EndRequest += new EventHandler(OnEndRequest);
}
void OnBeginRequest(object sender, EventArgs e)
{
startDate = DateTime.Now;
}
void OnEndRequest(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
endDate = DateTime.Now;
TimeSpan ts = new TimeSpan(endDate.Ticks - startDate.Ticks);
app.Context.Response.Write("<hr />");
app.Context.Response.Write("Pagina caricata in ");
app.Context.Response.Write(ts.TotalSeconds.ToString() + " secondi !");
}
}
Il modulo d'esempio è abbastanza semplice. Nel metodo Init, dichiara quali dovranno essere gli event handler per gestire gli eventi di inizio della richiesta (BeginRequest) e di fine (EndRequest), poi registra la data di inizio della richiesta e la sua data di fine e stampa a video i secondi di differenza tra le due date. Per aggiungere informazioni alla risposta si è dovuto prelevare l'istanza dell'app domain corrente, recuperare la proprietà Response del contesto HTTP ed inserirci i dati scelti (attraverso il metodo Write della classe HttpContext).
Una volta inserita questa classe nella directory App_Code
, abbiamo bisogno di registrare l'HTTP Module nel file di configurazione dell'applicazione, dichiarandone il nome e il tipo.
<system.web>
<httpModules>
<add name="TimerHttpModule" type="TimerHttpModule" />
</httpModules>
</system.web>
Fatto questo, ogni pagina del sito web in questione, avrà calcolato in fondo alla pagina i secondi impiegati per l'esecuzione della richiesta.
Visto questo primo esempio, cerchiamo di dare una panoramica ancora più completa riguardo i possibili sviluppi tramite degli HTTP Module; per far ciò, scorriamo l'elenco degli eventi (propri della classe HttpApplication) del ciclo di vita di una singola richiesta HTTP, cui possiamo metter mano:
Evento | descrizione |
---|---|
AcquireRequestState | evento che si scatena non appena il runtime di ASP.NET acquisisce lo stato della sessione |
AuthenticateRequest | evento che si scatena non appena un utente prova ad autenticarsi all'applicazione web attraverso le feature di autenticazione di ASP.NET |
AuthorizeRequest | evento che si scatena quando è stata controllata una qualsiasi tipo di autorizzazione per un utente |
BeginRequest | evento che viene scatenato all'inizio della richiesta |
EndRequest | evento che viene scatenato alla fine della richiesta |
Error | evento che si scatena quando scatta un'eccezione non gestita |
LogRequest | evento utile a salvare informazioni sulla richiesta corrente |
PostAcquireRequestState | evento che viene scatenato subito dopo che è stato acquisito lo stato della sessione |
PostAuthenticateRequest | evento che si scatena subito dopo che un utente è stato autenticato |
PostAuthorizeRequest | evento che si scatena subito dopo che l'utente è stato autorizzata a compire qualche azione |
PostReleaseRequestState | evento che viene scatenato quando il runtime di ASP.NET ha rilasciato lo stato della richiesta corrente ed è finita l'esecuzione di tutti gli HTTP Handler attivi |
PostRequestHandlerExecute | evento che si scatena subito dopo la fine dell'esecuzione dell'handler di riferimento per la risorsa richiesta (pagine o web service) |
PostResolveRequestCache | evento che viene scatenato quando il runtime di ASP.NET permette al modulo relativo alla gestione della cache, di servire la richiesta dalla memoria |
PostUpdateRequestCache | evento che si scatena non appena il runtime ha completato l'aggiornamento dei valori della richiesta corrente inseriti in cache |
PreRequestHandlerExecute | evento che si scatena prima che venga eseguito l'HTTP Handler di riferimento per la risorsa richiesta (pagina o web service) |
PreSendRequestContent | evento che occorre subito prima che venga mandato del contenuto al client |
PreSendRequestHeaders | evento che si scatena prima che il runtime di ASP.NET spedisca gli headers HTTP al client |
ReleaseRequestState | evento che occorre quando il runtime di ASP.NET ha completato l'esecuzione di tutti gli event handler legati alla richiesta |
UpdateRequestCache | evento che occorre quando il runtime di ASP.NET ha finito di eseguire l'HTTP Handler di riferimento e inizia ad aggiornare la cache con le informazioni della richiesta corrente, utili alle richieste successive |
Agendo su questi eventi, abbiamo la possibilità di aggiungere delle funzionalità avanzate alle nostre applicazioni web.
Un HTTP Module per prevenirsi dallo SPAM
Un'implementazione molto particolare degli HTTP Module è quella di poter filtrare la pagina di risposta, e modificarne l'output a nostro piacimento. Possiamo, per esempio, cercare tramite una regular expression, tutti gli indirizzi e-mail inseriti nelle pagine e riscriverli, in modo tale che non possano essere utilizzati dagli agenti di spam.
Questo tipo di tecnica risulta molto utile in siti gia avviati e di grosse dimensioni, in quanto basta modificare solo una piccola parte dell'applicazione, per poter poi filtrare in automatico tutte le pagine che essa contiene.
Il filtro da realizzare, deve prendere lo stream di risposta e modificarlo. Il codice del filtro verrà omesso, in quanto non inerente all'argomento principale dell'articolo; i sorgenti sono disponibili però per il download.
La cosa interessante, cui porre attenzione, è l'evento che dobbiamo catturare per l'inserimento del nostro filtro personalizzato. L'evento in questione è il ReleaseRequestState
. Perché proprio questo? Semplice, perché negli eventi scatenati prima, non era presente ancora il rendering dei controlli web di ASP.NET, e la pagina risultava ancora vuota.
using System;
using System.Web;
public class NoSpamModule : IHttpModule
{
public void Dispose() {}
public void Init(HttpApplication context)
{
context.ReleaseRequestState += new EventHandler(OnReleaseRequestState);
}
void OnReleaseRequestState(object sender, EventArgs e)
{
HttpResponse response = HttpContext.Current.Response;
if (response.ContentType == "text/html")
response.Filter = new SpamFilter(response.Filter);
}
}
Il fulcro del nostro filtro anti-spam (la classe SpamFilter), è rappresentato dall'override del metodo Write, proprio della classe padre Stream. Questo metodo preleva i dati della risposta corrente e cerca ogni occorrenza di un link con all'interno un indirizzo e-mail, tramite l'utilizzo delle regular expression; per ogni collegamento trovato, si occupa poi di sostituire la chiocciola (@) con la parola (at) e il punto (.) con la parola (dot).
public override void Write(byte[] buffer, int offset, int count)
{
StringBuilder html = new StringBuilder();
string strBuffer = UTF8Encoding.UTF8.GetString (buffer, offset, count);
Regex fine = new Regex ("</html>", RegexOptions.IgnoreCase);
if (!fine.IsMatch(strBuffer))
{
html.Append(strBuffer);
}
else
{
html.Append(strBuffer);
string finalHtml = html.ToString();
string pattern = @"(mailto:|)(w+[a-zA-Z0-9.-_]*)@(w+).(w+)";
Regex re = new Regex(pattern, RegexOptions.IgnoreCase);
finalHtml = re.Replace(finalHtml, new MatchEvaluator(MailMatch));
byte[] data = UTF8Encoding.UTF8.GetBytes (finalHtml);
responseStream.Write (data, 0, data.Length);
}
}
Una volta registrato questo HTTP Module all'interno del web.config dell'applicazione web, ogni pagina richiesta verrà filtrata e cambierà tutti gli indirizzi e-mail presenti, in modo tale da renderli irriconoscibili per gli agenti di spam.
Conclusioni
Questo articolo ha voluto offrire una panoramica su uno degli argomenti più importanti riguardo la pipeline di esecuzione di ASP.NET; ma non ha certo esaurito il tema poiché, anche qui, le possibili implementazioni di un HTTP Module sono pressoché infinite. Quelle che risultano essere le più utilizzate sono implementazioni di url rewriting, filtri sulla risposta al client, azioni personalizzate sulla gestione della sessione e della cache o implementazioni di meccanismi di autenticazione e autorizzazione custom, però nulla ci vieta di inventarci le funzionalità custom proprie per le esigenze delle nostre applicazioni web.