ETags and HTTP Handlers

A few days ago, I was going to write a complaint about people not putting the Last-Modified and ETag headers on their HTTP Handlers, and/or not putting something rational in their Last-Modified. I was hitting a bunch of RSS and WeblogAPI endpoints with Fiddler, and noticed that many people would put the request time in the Last-Modified header, or that it wouldn't be there at all. If you've done this, you've basically asked for bandwidth problems. It means that the only way for a client app to determine if you've changed anything is for it to re-request everything from that API.

It's a pretty simple matter to add a valid Last-Modifed header to the results of an HTTP Handler (and you're not using an aspx for your RSS or other API endpoints, are you?):

context.Response.Cache.SetLastModified(someDate)';

Notice that I didn't just say, "SetLastModified(DateTime.Now)". Always setting to now means you've just sent a useless Last-Modified header. Odds are you're pulling some data off of disc, or out of a database. Therefore, you should know when the data was actually last modified, don't you? Grab the most recent time any entry was created/updated, or the datetime stamp of the file, or something. Give the client a hint that things have changed. It means they can do code like (I say "like" as I'm sure there's a better solution than swallowing the 304)

WebRequest _req = WebRequest.Create(url);
if (null != _data) {
    //add headers for ETag, Last-Modified
    if (null != _data.ETag && String.Empty != _data.ETag) {
        _req.Headers.Add(HttpRequestHeader.IfNoneMatch, _data.ETag);
    }

    if (null != _data.LastUpdated) {
        ((HttpWebRequest)_req).IfModifiedSince = _data.LastUpdated;
    }
}

WebResponse _resp = null;
try {
    _resp = _req.GetResponse();
    StreamReader _r = new StreamReader(_resp.GetResponseStream());
    _data.Contents = _r.ReadToEnd();
} catch (WebException wex) {
    //if it's a 304, we're OKdOK 
    //it just means the data hasn't changed
    if (WebExceptionStatus.ProtocolError == wex.Status) {
        if (HttpStatusCode.NotModified == ((HttpWebResponse)wex.Response).StatusCode) {
            //gulp
        }
    }
}

ETags are a little more difficult, it seems. When I first tried adding an ETag to the output of an HTTP Handler, nothing was added to the output. After a bit of fiddling, I discovered that you needed to set the SetCacheability to either ServerAndNoCache or ServerAndPrivate (Others might work as well).

if (!String.IsNullOrEmpty(context.Request.Headers["If-Modified-Since"])) {
    String lastModified = context.Request.Headers["If-Modified-Since"];
            
    DateTime lastModifiedDate = DateTime.Parse(lastModified, CultureInfo.InvariantCulture);
    if(lastModifiedDate.CompareTo(fileModifiedDate) < 0) {
        isNewRequest = false;
    }
}  

if (isNewRequest) {
    if (!String.IsNullOrEmpty(context.Request.Headers["If-None-Match"])) {
        String etag = context.Request.Headers["If-None-Match"];
        if (etag.CompareTo(ETAG) == 0) {
            isNewRequest = false;
        }
    }
}

if (isNewRequest) {
    //do your stuff here
} else {
    context.Response.StatusCode = 304;
}

Now, clients can remember these two values (Last-Modified and ETag) and ask if there are any updates. Similarly, any handlers that I write can see if the client has passed these two (in the form of If-Modified-Since or If-None-Match headers), and return a 304 instead, saving myself a bit of bandwidth.

Print | posted on Tuesday, September 05, 2006 10:56 PM

Feedback

# re: ETags and HTTP Handlers

left by Anonymous at 9/6/2006 1:55 AM Gravatar
Great info...

I spent ages on this for on10.net, trying to get the right combination on our images mostly... so that browsers wouldn't keep requesting the same image on every visit!

# re: ETags and HTTP Handlers

left by kent at 9/6/2006 2:46 AM Gravatar
Duncan -- thank you

Then, could you fix your Last-Modified for the RSS feeds so that it isn't DateTime.Now?

# re: ETags and HTTP Handlers

left by Anonymous at 9/6/2006 1:05 PM Gravatar
>> and you're not using an aspx for your RSS or other API endpoints, are you

could you elaborate why one should'nt do this?

WM_THX
-thomas

# re: ETags and HTTP Handlers

left by kent at 9/6/2006 7:57 PM Gravatar
Hi Thomas,

Part of the problem of reusing the aspx infrastructure for RSS or other "code" endpoints, is that you're carrying around the code-weight required for generating pages, using controls etc when it isn't needed. It also means you're having to essentially lie to the system and clients about MIME-types (e.g. aspx files are 'always' text/html unless it's an rss.aspx file).

# re: ETags and HTTP Handlers

left by Anonymous at 9/6/2006 10:38 PM Gravatar
"could you fix your Last-Modified for the RSS feeds so that it isn't DateTime.Now"

I'll give it a shot, we should be pushing out an update around 9/15 so perhaps it will make it into there :)

# re: ETags and HTTP Handlers

left by kent at 9/6/2006 10:43 PM Gravatar
Duncan (and others) correctly tell me that my MIME type argument is "a little weak". However, I still think the "why carry around the Page infrastructure if you don't need it" argument is still valid.
Comments have been closed on this topic.