R – Expires headers when testing in Chrome

fiddlergoogle-chrome

Getting very confused about 'Expires' header here!
Sometimes it works as expected – and some times not.

I am using the following code to set my expiration headers. Note this is being done with ASP.NET in an MVC custom attribute – thats not really relevant here – but explains where 'filterContext' is coming from.

HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
TimeSpan cacheDuration = TimeSpan.FromSeconds(Duration);

// my own custom header so we know what time it was
filterContext.HttpContext.Response.AddHeader("CurrentTime", DateTime.Now.ToString());

cache.SetCacheability(HttpCacheability.Public);
cache.SetExpires(DateTime.Now.Add(cacheDuration));
cache.SetMaxAge(cacheDuration);
cache.AppendCacheExtension("must-revalidate, proxy-revalidate");

This will sometimes give me headers like this :

Cache-Control: public, must-revalidate, proxy-revalidate, max-age=413
Date: Wed, 18 Feb 2009 05:24:19 GMT
Expires: Wed, 18 Feb 2009 05:21:12 GMT
CurrentTime: 2/17/2009 9:21:12 PM

Sometimes like this :

Cache-Control: public, must-revalidate, proxy-revalidate, max-age=600
Date: Wed, 18 Feb 2009 05:27:55 GMT
Expires: Wed, 18 Feb 2009 05:27:55 GMT
CurrentTime: 2/17/2009 9:27:55 PM

I am running everything through Fiddler and watching to see when things are re-requested and when they come from the browser cache.

Now the weird thing is in IE the caching always works as expected. The link to my ASP.NET MVC action method appears in Fiddler and then when I click on that same link again it is coming from cache.

However in Chrome it sometimes will and sometimes won't come from cache! By coming from cache I mean no additional HTTP request.

For instance a link like this :

 http://ipv4.fiddler:62669/gallery/mainimage/2

will come from cache in IE, but come back with a 200 in chrome. Then sometimes in Chrome it DOES come from the cache. I've tried emptying the browser cache and trying again – same result each time.

Is Chrome trying to do something 'clever' and just failing miserably – or do I need an additional header?

What I'm wondering is if it has anything to do with the fact that my Expires header date is never actually in the future. if I look at google's headers for their hosted jQuery file I see that the headers are as follows (with Expires here in 2010 – one year in the future).

Cache-Control: public, max-age=31536000
Date: Wed, 18 Feb 2009 05:44:53 GMT
Expires: Thu, 18 Feb 2010 05:44:53 GMT

Shouldn't Expires actually be in the future??

According to the HTTP spec :

If a response includes both an Expires
header and a max-age directive, the
max-age directive overrides the
Expires header, even if the Expires
header is more restrictive. This rule
allows an origin server to provide,
for a given response, a longer
expiration time to an HTTP/1.1 (or
later) cache than to an HTTP/1.0
cache. This might be useful if certain
HTTP/1.0 caches improperly calculate
ages or expiration times, perhaps due
to desynchronized clocks.

Therefore it seems that Chrome should respect the max-age directive even if 'Expires' is the same as the current time but it doesn't seem to be doing that.

Best Answer

I found the following in the ASP.NET MVC source code :

 public virtual void RenderView(ViewContext viewContext) {
        // TODO: Remove this hack. Without it, the browser appears to always load cached output
        viewContext.HttpContext.Response.Cache.SetExpires(DateTime.Now);
        ViewUserControlContainerPage containerPage = new ViewUserControlContainerPage(this);
        // Tracing requires Page IDs to be unique.
        ID = Guid.NewGuid().ToString();

        RenderViewAndRestoreContentType(containerPage, viewContext);
    }

So this explains why my Expires header always is the current time. However I really don't think this is what is tripping up Chrome becasue I created the simplest possible page as follows, and Chrome was still quite happily going back to the server and giving me a 200

public partial class test : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        TimeSpan cacheDuration = TimeSpan.FromSeconds(33);
       var cache = Response.Cache;

        cache.SetCacheability(HttpCacheability.Public);
        cache.SetExpires(DateTime.Now.Add(cacheDuration));
        cache.SetMaxAge(cacheDuration);
        cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
    }
}