- Come attivare la compressione lato-server con ASP .Net -
 
COSA SERVE PER QUESTO TUTORIAL
Download | Chiedi sul FORUM | Glossario cognizioni basiche di ASP .Net
Come utilizzare in maniera intelligente il Content-Encoding

COME E PERCHÉ USA LA COMPRESSIONE LATO SERVER
Prestazioni, banda e contesto.

Oramai non è più solo una questione di banda consumata e neppure di utenti impazienti, i tempi di caricamento di una pagina assumono man mano maggiore rilevanza anche nelle valutazioni che i motori di ricerca effettuano nei confronti del nostro sito. Fra i vari metodi da utilizzare per minimizzarli sicuramente una tecnica efficace è attivare la compressione dei contenuti lato server, creando in sostanza questa situazione: il server genera il responso alla richiesta, lo comprime servendosi di due algoritmi molto veloci (deflate o gzip), lo invia al client, il quale decomprime i dati e quindi li mostra all'utente. Poiché l'anello debole della catena che costituisce i tempi di caricamento della pagina è in genere la trasmissione dei dati, diminuendo la quantità di byte da trasmettere si possono ottenere risultati davvero buoni.
Si noti però che la compressione non va attivata solamente su risorse già compresse, ad esempio archivi ZIP, immagini JPEG, GIF o PNG in quanto i risultati che si otterrebbero sarebbero tanto scarsi da non valere la quantità di risorse di calcolo impiegate per effettuare la compressione. Al contrario su un file HTML, JS e CSS si può arrivare a risparmiare anche il 70% del peso.
IIS offre questa funzionalità integrata, ma di default non è installata, e comunque può capitare di trovarsi in situazioni in cui non è possibile modificare manualmente le impostazioni di IIS (tipicamente un hosting condiviso), per cui vediamo come implementare in maniera semplice ed efficace la compressione del contenuto tramite ASP .Net.

LA FUNZIONE DI COMPRESSIONE DEL RESPONSO
Selezione delle richieste da comprimere e attivazione del filtro di compressione.

In genere quando si necessità di eseguire codice per ogni richiesta, ad esempio per effettuare una riscrittura dell'URL o modificare dinamicamente il responso della pagina, ci si serve di un modulo o di un handler di ASP .Net, ovvero classi (da registrate in web.config) che gestiscono le richieste in arrivo ad ASP .Net. Tuttavia in questo caso il codice è tanto semplice che non è necessario andare a scomodare moduli e handler, basterà utilizzare il file global.asax, che contiene funzioni che gestiscono l'intero sito. Ad esempio se si definisce una funzione Application_Error essa verrà richiamata ogni volta che una qualunque pagina del sito lancia un'eccezione, come visto nell'articolo sulla gestione centralizzata degli errori. Global.asax in sostanza definisce una classe che eredita da HttpApplication, per avere una lista completa delle funzioni che permette di utilizzare vedere la reference di HttpApplication.
In questo caso la funzione che ci interessa è Application_PostReleaseRequestState, che viene richiamata dopo che il responso è stato completamente generato ma non ancora inviato, è l'ultimo stadio del ciclo di vita di una pagina ASP .Net in cui è possibile impostare un filtro; al suo interno dovremo rilevare se il client supporta la compressione, quale tipo supporta e in caso affermativo attivarla per i tipi di risorsa non compressi. Vediamo il corpo della funzione:


Private Sub Application_PostReleaseRequestState(ByVal sender As Object, ByVal e As EventArgs)
    'Prendiamo l'oggetto HttpApplication
    Dim app As HttpApplication = DirectCast(sender, HttpApplication)
    
    'Il MIME type della risorsa restituita
    Dim strContentType As String = app.Response.ContentType.ToLower()
            
    'Variabile che conterrà l'header Accept-Encoding del client
    Dim strAcceptEncoding As String
    'Usiamo una classe interna di ASP .Net per avere il valore dell'header Accept-Encoding
    Dim worker As HttpWorkerRequest = _
        DirectCast(app.Context, IServiceProvider).GetService(GetType(HttpWorkerRequest))
    
    strAcceptEncoding = worker.GetKnownRequestHeader(HttpWorkerRequest.HeaderAcceptEncoding)
    
    'Metodo meno efficiente per avere l'header Accept-Encoding
    'strAcceptEncoding = app.Request.Headers("Accept-Encoding")
    
    'Controlliamo che la richiesta corrente 
    '1) accetti qualche forma di compressione
    '2) contenga qualche formato testuale, javascript o XML
    '3) non sia una richiesta AJAX 
    If Not String.IsNullOrEmpty(strAcceptEncoding) _
        AndAlso ( _
            strContentType.EndsWith("javascript") _
            OrElse strContentType.StartsWith("text/") _
            OrElse strContentType.EndsWith("xml") _
        ) _
        AndAlso app.Request("HTTP_X_MICROSOFTAJAX") Is Nothing Then
                
        strAcceptEncoding = strAcceptEncoding.ToLower()
        
        'Se il client supporta gzip
        If strAcceptEncoding.Contains("gzip") OrElse strAcceptEncoding = "*" Then
            'Aggiungiamo un filtro di compressione GZip al responso
            'basandoci su quello originale (dati non compressi)
            app.Response.Filter = New System.IO.Compression.GZipStream(app.Response.Filter, _
                System.IO.Compression.CompressionMode.Compress)
            'Aggiungiamo l'header Content-Encoding che informa il client
            'che il contenuto è compresso con gzip
            app.Response.AppendHeader("Content-Encoding", "gzip")
        ElseIf strAcceptEncoding.Contains("deflate") Then
            'Come prima ma utilizziamo l'algoritmo deflate
            app.Response.Filter = New System.IO.Compression.DeflateStream(app.Response.Filter, _
                System.IO.Compression.CompressionMode.Compress)
            app.Response.AppendHeader("Content-Encoding", "deflate")
        End If
    End If
End Sub

Per prima cosa recuperiamo l'oggetto HttpApplication che ci serve per avere informazioni sulla richiesta e sul responso generato, ad esempio memorizziamo il ContentType della risorsa, così da poter poi verificare se è tra quelli testuali e quindi adatti a essere compressi.
Per sapere se il client supporta la compressione del responso bisogna verificare l'header HTTP Accept-Encoding i cui valori, se presente, sono in genere "gzip", "deflate", "none" o "gzip, deflate". Per ottenere il valore dell'header non ci serviamo di Request.Headers, utilizziamo invece la funzione GetKnownRequestHeader della classe interna HttpWorker, che risulta essere più prestante, non utilizzando un dizionario. Si ricordi infatti che le prestazioni in questa funzione sono molto importanti, poiché verrà eseguita ogni volta che viene effettuate una richiesta!
Reperite queste informazioni verifichiamo dapprima se l'header Accept-Encoding è vuoto, quindi effettuiamo un controllo sul MIME type della risorsa che stiamo restituendo: accettiamo tutti quelli che finiscono per "javascript" o "xml" e che iniziano per "text/". Saranno quindi inclusi "text/html", "text/xml", "application/atom+xml", "image/svg+xml", "application/rss+xml", "text/plain", "application/x-javascript", "text/css", mentre non verranno compressi "image/jpeg", "image/png", "application/x-zip-compressed" e molti altri. L'ultima condizione impone che se stiamo effettuando una richiesta AJAX da parte di un browser Microsoft non comprimiamo il contenuto in quanto la cosa è nota per creare problemi.
A questo punto non ci resta che verificare quale metodo di compressione il client supporti tra GZip e deflate, mentre se il valore è "none" semplicemente non viene eseguita alcuna operazione aggiuntiva. Se è supportato GZip non dobbiamo fare altro che impostare la proprietà Filter di HttpResponse su una nuova istanza della classe GZipStream (collocata in System.IO.Compression) alla quale passiamo come parametro la proprietà Filter stessa e comunichiamo di effettuare una compressione, dato che questa classe funziona anche come decompressore. Stiamo in sostanza inglobando lo stream di dati da inviare al client all'interno di un altro stream che non fa altro che leggere il primo e inviarlo al client compresso. Infine aggiungiamo l'header che comunica al client che il responso ricevuto è compresso tramite la codifica GZip, Content-Encoding.
Se invece il client supporta solamente deflate, replichiamo la stessa identica operazione ma con la classe DeflateStream.

COMPRESSIONE SU PAGINE NON ASP.NET E TEST
Come testare se la compressione sta funzionando e attivarla su pagine non ASPX.

Perché la compressione abbia effetto anche su pagine con estensione differente da ASPX, come HTM, HTML, CSS e JS, bisogna che queste estensioni siano mappate per essere gestite da ASP .Net come spiegato nell'articolo dedicato. In particolare, oltre a modificare le impostazioni di IIS, bisogna ovvero aggiungere in web.config per ognuna di queste estensioni il BuildProvider System.Web.Compilation.PageBuildProvider e l'HttpHandler System.Web.StaticFileHandler se si vuole che siano trattate come risorse statiche o System.Web.UI.PageHandlerFactory se si desidera invece che funzionino esattamente come un file ASPX. Per i file JS è inoltre necessario mettere il seguente elemento prima del suo BuildProvider:

<remove extension=".js" />

Questo perché il compilatore di pagine ASP .Net di default interpreta i file JS come file JScript.Net (quindi script lato server).
In sostanza il file web.config dovrebbe assomigliare al seguente:


<configuration>
    <system.web>
        <compilation>
            <buildProviders>
                <remove extension=".js" />
                <add extension=".js" type="System.Web.Compilation.PageBuildProvider" />
                <add extension=".css" type="System.Web.Compilation.PageBuildProvider" />
                <add extension=".htm" type="System.Web.Compilation.PageBuildProvider" />
            </buildProviders>
        </compilation>
        <httpHandlers>
            <add verb="*" path="*.js" type="System.Web.StaticFileHandler" />
            <add verb="*" path="*.css" type="System.Web.StaticFileHandler" />
            <add verb="*" path="*.htm" type="System.Web.UI.PageHandlerFactory" />
        </httpHandlers>
    </system.web>
</configuration>

Per verificare se effettivamente la compressione funziona si può provare con telnet (Start, Esegui, cmd, telnet localhost 80) o HyperTerminal a connettersi al proprio server locale e incollare la seguente richiesta HTTP:


GET /percorso/al/file.css HTTP/1.1
Accept-Encoding: gzip
Host: localhost


Specificando al posto di "/percorso/al/file.css" l'effettivo percorso della risorsa da verificare. Se il responso contiene l'header Content-Encoding impostato su gzip e il corpo della risposta sono caratteri apparentemente privi di senso, la compressione sta funzionando.

 

<< INDIETRO by VeNoM00