Using If-Modified-Since in HTTP Requests

Conditionally requesting the download of a web page only if it has been modified after a given time seems like it should be as simple as setting the IfModifiedSince property and making the request:

HttpWebRequest request = (HttpWebRequest) WebRequest.Create(@"http://code.logos.com/blog/");
request.IfModifiedSince = new DateTime(2009, 6, 3);
using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
{
    if (response.StatusCode == HttpStatusCode.NotModified)
    {
        // page wasn't modified; use cached version

    }
}

But of course it’s not that simple (as some others have noticed).

The designers of HttpWebRequest decided that some particular HTTP status codes would cause a WebException to be thrown. (As far as I can tell, this list is undocumented, but 304 “Not Modified” is one of them.) This is a vexing exception, because the situation is hardly exceptional. In fact, because it can only happen if IfModifiedSince is explicitly set (or if request.Headers were modified), one could argue that it’s quite expected and intentional. To avoid duplicating logic in the try block (for handling 200 “OK”) and in the catch block (for handling “304” Not Modified), I wrote a utility method that swallows any WebException thrown due to a ProtocolError (e.g., an “invalid” HTTP status code):

public static class HttpWebRequestUtility
{
    /// <summary>

    /// Gets the <see cref="HttpWebResponse"/> from an Internet resource.

    /// </summary>

    /// <param name="request">The request.</param>

    /// <returns>A <see cref="HttpWebResponse"/> that contains the response from the Internet resource.</returns>

    /// <remarks>This method does not throw a <see cref="WebException"/> for "error" HTTP status codes; the caller should

    /// check the <see cref="HttpWebResponse.StatusCode"/> property to determine how to handle the response.</remarks>

    public static HttpWebResponse GetHttpResponse(this HttpWebRequest request)
    {
        try
        {
            return (HttpWebResponse) request.GetResponse();
        }
        catch (WebException ex)
        {
            // only handle protocol errors that have valid responses

            if (ex.Response == null || ex.Status != WebExceptionStatus.ProtocolError)
                throw;

            return (HttpWebResponse) ex.Response;
        }
    }
}

The code to consume this reads very similarly to the first snippet in this post; you just have to remember that normal errors (e.g., 404 “Not Found”) are reported through a valid HttpWebResponse, so its StatusCode property must be checked before acting on the response.

Posted by Bradley Grainger on June 05, 2009