Doufám, že vás můj předchozí článek navnadil na psaní HTTP modulů a pohled do budoucnosti přesvědčil, že se jedná o užitečnou dovednost. Podívejme se tedy do útrob jednoho HTTP modulu, jak funguje.
Oním kouskem software bude můj modůlek SkinAnywhere. Princip jeho činnosti je jednoduchý: v jakékoliv aplikaci "pod ním" lze jeho prostřednictvím přepínat kaskádové styly. Funguje to tak, že můj modul prohlíží aplikací vygenerované HTML a hledá v něm odkaz na originální stylesheet. Pokud ho najde, modifikuje HTML tak, aby místo něj byl odkaz na nějaký jiný stylesheet, v závislosti na nastavení konfigurační cookie. Tuto cookie lze nastavit zavoláním speciální stránky s vhodnými parametry.
Z hlediska funkčnosti tedy kód obsahuje tři hlavní části:
- Načítání konfigurace (tato problematika byla již popsána dříve a nebudu se jí věnovat).
- Ve vstupní fázi (před generováním výstupu ASPX stránkami) kontrolovat, zda se nejedná o požadavek na speciální URL pro změnu stylu (nastavení cookies) a pokud ano, zpracovat ho.
- Ve výstupní fázi (po vygenerování výstupu ASPX stránkami) prohledávat vygenerovaný kód a nahradit v něm odkaz na CSS vlastním.
IHttpModule
Jádrem celé aplikace je vlastní HTTP modul. Jedná se o třídu která implementuje rozhraní System.Web.IHttpModule
. To specifikuje dvě metody: Init
a Dispose
.
Význam metody Dispose
je jasný - slouží k uvolnění všech používaných zdrojů při odstranění modulu. My žádné zdroje nealokujeme a tudíž není co uvolnit.
Klíčová je pro nás metoda Init
, která se zavolá při inicializaci (zavedení) modulu. V ní je možno pověsit vlastní metody jako event handlery na obsluhu událostí, které se přihodí v průběhu zpracování požadavku na stránku. Těch je mnoho a každá má svůj význam, proto se na ně podíváme podrobněji.
Události při zpracování požadavku
Následující události nastanou při zpracování webového požadavku přes HTTP runtime, v uvedeném pořadí:
BeginRequest
- pokud chcete provádět nějaké specifické věci týkající se přesměrování a podobně, učiňte tak nyní.AuthenticateRequest
- teď se hledá odpověď na otázku "kdo jsi" - pomocí dostupných (nakonfigurovaných) metod zabezpečení se ověřuje platnost uživatelského jména a hesla (nebo jiného autentizačního prostředku).AuthorizeRequest
- teď se zjišťuje "co tady děláš", tedy zda v předchozím kroku úspěšně identifikovaný uživatel má právo vznésti tento požadavek.ResolveRequestCache
- v tomto kroku se zjišťuje, zda je stránka odpovídající tomuto požadavku v cache a nebo zda se musí skutečně vykonat.AcquireRequestState
- v tomto kroku se načtou hodnoty session state apod.PreRequestHandlerExecute
- tento krok nastane těsně předtím, než se požadavek předá k vykonání patřičnému HTTP handleru.- V tomto okamžiku se zavolá příslušný HTTP handler a vykoná se vlastní kód stránky.
PostRequestHandlerExecute
- tento krok nastane těsně potom, co se vykoná HTTP handler.ReleaseRequestState
- v tomto okamžiku se uloží změněná data zpět do session apod.UpdateRequestCache
- pokud to pravidla cacheování umožní, uloží se v tomto kroku vygenerovaná hodnota do cache.EndRequest
- poslední událost těsně před tím, než se vygenerovaná data pošlou na klienta.
Pomocí vlastních HTTP modulů můžeme tedy rozšířit nebo přepsat prakticky všechni činnosti, které při běhu .NET aplikace nastávají. Z výše uvedeného seznamu je jasné, kdy budeme vyřizovat jaké úkoly:
Na událost BeginRequest
pověsíme vyhodnocení požadované adresy a přijetí vhodných opatření v případě, že se jedná o adresu pro změnu stylu. Na událost PostRequestHandlerExecute
pověsíme zpracování a pozměnění vygenerovaných dat.
Za tímto účelem si vytvoříme metody HandleBeginRequest
a HandlePostRequestHandlerExecute
(mohou se jmenovat fakticky jakkoliv). V Init
je pak přiřadíme k patřičným událostem:
Public Sub Init(ByVal context As System.Web.HttpApplication) Implements System.Web.IHttpModule.Init AddHandler context.PostRequestHandlerExecute, AddressOf HandlePostRequestHandlerExecute AddHandler context.BeginRequest, AddressOf HandleBeginRequest End Sub
Přepisování výstupního HTML
K dalšímu zpracování vygenerovaných dat lze použít vlastní třídu, odvozenou (Inherits
) od System.IO.Stream
. Instanci této třídy jest přiřaditi vlastnosti Response.Filter
. To náš HTTP modul provádí v rámci obsluhy události PostRequestHandlerExecute
nějak takto:
Public Sub HandlePostRequestHandlerExecute(ByVal sender As Object, ByVal e As EventArgs) With System.Web.HttpContext.Current ' If output is not HTML, give up If .Response.ContentType.ToLower() <> "text/html" Then Return' Append filter .Response.Filter = New Filter(.Response.Filter) End With
End Sub
Naše vlastní třída Filter
slouží jako skutečný filtr: z jedné strany (metodou Write
) jsou do něj data cpána a z druhé strany (metodou Read
) z ní zase vytékají:
Public Class Filter Inherits System.IO.StreamPrivate Base As System.IO.Stream Public Overrides Function Read(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer) As Integer Return Me.Base.Read(buffer, offset, count) End Function Public Sub New(ByVal ResponseStream As System.IO.Stream) If ResponseStream Is Nothing Then Throw New ArgumentNullException("ResponseStream") Me.Base = ResponseStream End Sub Public Overrides Sub Write(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer) ' Get HTML code Dim HTML As String = System.Text.Encoding.UTF8.GetString(buffer, offset, count) ' Do something with output HTML = HTML.Replace("DEMO", "TEST") ' Send output buffer = System.Text.Encoding.UTF8.GetBytes(HTML) Me.Base.Write(buffer, 0, buffer.Length) End Sub
End Class
Skutečnou logiku nahrazování jsem pro přehlednost vypustil, tento kód jenom nahradí jakýkoliv výskyt řetězce "DEMO" řetězcem "TEST". Stejným způsobem jako s metodou Read
je nutno naložit se všemi dalšími metodami a vlastnostmi, jimiž Stream
oplývá - pokud nás jejich osud nezajímá, prostě zavoláme tutéž metodu se stejnými parametry, ovšem u "underlying" (vnitřního) streamu, který jsme mimo jiné za tímto účelem v konstruktoru zřídili.
Odchycení speciálního požadavku
V rámci HTTP handleru můžeme provést i odchycení speciálního požadavku, který například necheme předat níže ležící aplikaci. V našem případě se jedná o volání stránky pro přepnutí stylu. Odchycení provedeme v event handleru události BeginRequest
. Skutečný kód je opět složitější, níže určený příklad jenom při požadavku na stránku /moduletest.aspx
vrátí pevně definovaný text. Stránka moduletest.aspx
přitom vůbec nemusí existovat (a i pokud existuje, vůbec na ní nezáleží, nikdy se nevykoná).
Public Sub HandleBeginRequest(ByVal sender As Object, ByVal e As EventArgs)
Dim Context As System.Web.HttpContext = System.Web.HttpContext.Current If Not Context.Request.Url.AbsolutePath.ToLower() = "/moduletest.aspx" Then Return
Context.Response.Clear() Context.Response.Write("<html><head><title>HTTP module test</title></head>")
Context.Response.Write("<body><h1>HTTP module test</h1></body></html>") Context.Response.End() End Sub
Registrace HTTP modulu
Aby byl HTTP modul aktivní (vykonán) je nutno ho nejenom napsat, ale též zaregistrovat v souboru web.config
. Děje se tak v sekci /configuration/system.web/httpModules
. Ukázkový konfigurační soubor může vypadat takto:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.web> <httpModules> <add name="SkinAnywhere" type="AltairCommunications.SkinAnywhere.HttpModule, SkinAnywhere" /> </httpModules> </system.web> </configuration>
Přiřazení se děje pomocí elementu add
a jeho dvou atributů:
name
je uživatelsky přítulné jméno vašeho modulu. V kontextu našeho příkladu na něm nezáleží.type
je označení typu HTTP module (tedy té třídy která implementujeIHttpModule
).
Závěr
- Prostřednictvím HTTP modulů je možno se napojit na kteroukoliv událost HTTP request processing pipeline.
- V ukázkách je návod jakým lze modifikovat požadavek předtím než je zpracován, i potom.
- Aby se handler vykonal, je nutno ho zaregistrovat v souboru
web.config
(nebomachine.config
pro celý server)
Kompletní komentovaný zdrojový kód jednoduchého ale užitečného HTTP modulu si můžete stáhnout na webu Altair Communications.