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

Download the source directly.

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");
}

Advertisement

Feedback

Voting Panel
Is this useful?
or
Did you find any bugs?
or
Could this be improved
or
Did it solve your programming problem?
or
Do you need more documentation?
or
Rate this script: (0=poor, 5=very good)
Answers are anonymous, only the combined totals are stored. Uses cookies.