This article is available in English too.
In questo articolo vedremo come controllare con precisione la cache del
client (il browser del vostro PC ad esempio) in modo da evitare di trasmettere
dati dal server quando il client già li possiede. Il server ASP.NET già possiede
dei sistemi di caching dei dati, ad esempio per evitare di continuare ad
eseguire la stessa query nel database; ma sti dati, non sarebbe bello evitare di trasmetterli, risparmiando
il consumo di banda?
La tecnica si chiama GET condizionale ed utilizza l'HTTP
status code 304 ed in pratica è come se avvenisse il seguente dialogo
tra client e server:
Client: "Ciao server, avrei bisogno di alcuni dati.
L'ultima volta che me li hai forniti mi avevi detto che erano aggiornati alla
seguente data/ora"
A questo punto il server controlla la data/ora fornita dal client e a seconda
che i dati siano cambiati o no risponde:
Server: "Ciao client, i dati da allora sono cambiati,
eccoti quelli nuovi. Se dovessi richiedermeli di nuovo, sappi che questi che ti
ho appena mandato sono aggiornati alla seguente data/ora."
oppure
Server: "Ciao client, i dati da allora non sono
cambiati, sicché evito di mandarteli di nuovo, ce li hai nella tua cache."
Questo tipo di ottimizzazione è particolarmente utile quando i dati sono
generati dinamicamente lato server e cambiano durante il tempo.
Vediamo di tramutare questo dialogo in codice C#, utilizzando
come esempio un HttpHandler che genera un file RSS
dinamicamente (è quello che accade quando vi sottoscrivete ai feed RSS
di questo blog). In questo esempio non affronteremo l'argomento della
generazione dinamica degli RSS feed poiché andrebbe oltre lo
scopo dell'articolo, ma ci limiteremo a vedere come implementare il
caching condizionale.
Diamo per assunto che il metodo GetLastBlogUpdateDateTime()
ritorna la data/ora UTC
dell'ultimo aggiornamento del blog.
public
class
BlogRssHandler : IHttpHandler, IRequiresSessionState
{
public
virtual
void
ProcessRequest(HttpContext Context)
{
// If the client already has the updated RSS, do not send it again
if
(BuildResponseHeader(Context) ==
false
)
return
;
else
{
// Generate the RSS feed
...
}
}
// Returns false if contents can be retrieved from the browser's cache
// (HTTP response 304 optimization)
private
Boolean BuildResponseHeader(HttpContext Context)
{
// Get the last modification date/time stored in the database
// NOTE: the date/time must be UTC
DateTime serverLastUpdateUTC = GetLastBlogUpdateDateTime();
// Get the last modification date provided by the browser (if any)
String ifModifiedSinceHeaderText = Context.Request.Headers.
Get(
"If-Modified-Since"
);
if
(!String.IsNullOrEmpty(ifModifiedSinceHeaderText))
{
DateTime clientLastUpdateUTC = DateTime.Parse(ifModifiedSinceHeaderText);
clientLastUpdateUTC = clientLastUpdateUTC.Value.ToUniversalTime();
// If the modification date/time of the client is equal to the server one
if
(serverLastUpdateUTC <= clientLastUpdateUTC)
{
// ...tell the browser that contents have not changed and return
Context.Response.ClearContent();
Context.Response.StatusCode = (Int32)
System.Net.HttpStatusCode.NotModified;
Context.Response.StatusDescription =
"Not Modified"
;
Context.Response.SuppressContent =
true
;
return
false
;
}
}
Context.Response.ContentType =
"text/xml"
;
// Tell the browser to cache the new contents
Context.Response.Cache.SetCacheability(HttpCacheability.Private);
Context.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
Context.Response.Cache.SetLastModified(DateTime.SpecifyKind(
serverLastUpdateUTC.Value, DateTimeKind.Utc));
// Force the browser to not use it's current cache
Context.Response.Cache.SetMaxAge(
new
TimeSpan(0, 0, 0));
Context.Response.Cache.SetExpires(DateTime.Now.ToUniversalTime());
return
true
;
}
...
}
Quando il server invia al client la data/ora di modifica, è meglio che venga
arrotondata al secondo rimuovendo i millisecondi.
E' inoltre importante specificare che la data/ora è UTC (metodo
DateTime.SpecifyKind
), questa cosa non è citata in alcuni esempi in giro
per il web ed invece è essenziale.