Cache Control
Audience Level
Intermediate to advanced
Summary
A JScript ASP function to implement HTTP ETag and If_Modified_Since client-server cache negotiation.
Instructions
Use this JScript function to handle HTTP 304 content negotiation in Classic ASP. Invoke the function at
the start of an ASP page once the freshness information of the ASP page's content is known. The
function will then compare that freshness information with the headers that the client browser sent.
If the freshness information matches then the function will cancel ASP page processing (“Response.End”)
and send the client a HTTP 304 to inform the client that it should display the version that it must already
have saved in its own webpage cache.
Invoke as cacheControl(objLastModified, blnOverride) where
“objLastModified” is the date that the resource on
the server was last modified — this could be the LastModified() date of a physical file
for example. This can either be a JScript “Date” object, a String of the form:
“Thu, 12 May 2005 11:16:13 UTC” or the number of milliseconds since
“1970-01-01 00:00:00 GMT+00:00”.
The second parameter to the function “blnOverride” is a Boolean and is used
as a flag on whether to pay attention to the cache information the client has sent. If the value
is Boolean “true” then this tells the function to not
perform content negotiation — so a HTTP 304 will not be sent regardless of
whether the client already had a cached copy of the content being requested. A value of Boolean “false”
is the default state — content negotiation routines are active.
Source Code
Function: cacheControl()
Description: Handles cache control negotiation through Last-Modified and ETag headers as per RFC2616 (ftp://ftp.isi.edu/in-notes/rfc2616.txt)
Returns: (HTTP Headers)
History:
20040319 0049UTC v1 Andrew Urquhart Created
20041017 0005UTC v1.1 Andrew Urquhart Logic overhaul after re-reading RFC2616
20050322 0340UTC v1.2 Andrew Urquhart Added Expires header and set cache headers also when sending a 304
*/
function cacheControl(objLastModified, blnOverride) {
// Get Incoming freshness info from client
var objInHttpIMS = Request.ServerVariables("HTTP_IF_MODIFIED_SINCE");
objInHttpIMS = (objInHttpIMS.Count ? new Date(objInHttpIMS.Item(1)) : null);
var strInHttpINM = Request.ServerVariables("HTTP_IF_NONE_MATCH");
strInHttpINM = (strInHttpINM.Count ? "" + strInHttpINM : null); // Get CSV string of entities
// Get Outgoing freshness info from server
var objOutHttpLM = new Date(objLastModified); // 'Last-Modified' date from server
var strOutHttpE = "\"AndrewU_" + objOutHttpLM.valueOf().toString(16) + "\""; // 'Etag' from server
/* Test if both incoming INM and IMS validate against the server's versions.
If both INM and IMS were received then *both* must validate (ie && not ||)
Otherwise if only one was received it alone must validate */
var blnDo304 = null;
if (!blnOverride) {
// Check if client and server dates match
var blnValidIMS = Boolean(objInHttpIMS && (objOutHttpLM.valueOf() == objInHttpIMS.valueOf()));
// Check if client sent returning INM ETag
if (strInHttpINM) {
// OK, check if client and server etags match
if (strInHttpINM.indexOf(strOutHttpE) >= 0 || strInHttpINM.indexOf("*") >= 0) {
// OK, they do, now make sure that if the IMS date exists that it matches the LM date
if (blnValidIMS || !objInHttpIMS) {
// OK, either dates match or there was no IMS date:
blnDo304 = true;
}
else {
// Valid etag but invalid IMS date:
blnDo304 = false;
}
}
else {
// Etags don't match:
blnDo304 = false;
}
}
else if (blnValidIMS){
// No etags to compare, but IMS date validates:
blnDo304 = true;
}
}
// Set expires as minus one day
var yesterday = new Date();
yesterday.setUTCDate(yesterday.getUTCDate() - 1);
// Need dates in RFC822 string format
var strOutExpiresRFC822 = getDateRFC822(yesterday);
strOutHttpLMRFC822 = getDateRFC822(objOutHttpLM);
if (blnDo304) {
// Send HTTP 304. Do not send content.
Response.Clear();
Response.CacheControl = "must-revalidate, proxy-revalidate";
Response.AddHeader("ETag", strOutHttpE);
Response.AddHeader("Last-Modified", strOutHttpLMRFC822);
Response.AddHeader("Expires", strOutExpiresRFC822);
Response.Status = 304;
Response.Flush();
Response.End();
}
else {
// No valid cache conditions found, so send content with current freshness headers
Response.CacheControl = "must-revalidate, proxy-revalidate";
Response.AddHeader("ETag", strOutHttpE);
Response.AddHeader("Last-Modified", strOutHttpLMRFC822);
Response.AddHeader("Expires", strOutExpiresRFC822);
}
}
Download
Note
You need to use this function before you have committed sending headers to the client. I.e. you need to
invoke it before any “Response.Write()” statements or before any other text/HTML
output.
Function getDateRFC822() is listed below in case anyone was wondering what it was:
Function: getDateRFC822()
Description:
Returns:
History:
20040507 2243BST v1 Andrew Urquhart Created
*/
function getDateRFC822(strDate) {
return new Date(strDate).toUTCString().replace(/UTC/ig, "GMT");
}