views:

1135

answers:

2

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.

+3  A: 

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");
    }
}
Simon_Weaver
i even added <%= DateTime.Now.ToString() %> to this aspx page, and ran it across the network and it still went back EVERY TIME with a 200 and updated the time. but in IE it was cached for 33 seconds. messed up
Simon_Weaver
A: 

I've pretty much concluded that it is Chrome doing something REALLY REALLY whacky with caching.

I simplified it to the lowest possible level - getting jQuery from Google's server.

I typed in :

http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js

to Chrome, and Fiddler came up with the following 200 request :

Requests started at:    22:58:00:7756
Responses completed at: 22:58:03:5020
Total Sequence time:    00:00:02.7263880
DNS Lookup time:    531ms
TCP/IP Connect time:    63ms

RESPONSE CODES
--------------
HTTP/200:   1

The headers were as follows (note Expires is 1 year after today):

Cache-Control: public, max-age=31536000
Date: Wed, 18 Feb 2009 06:58:01 GMT
Expires: Thu, 18 Feb 2010 06:58:01 GMT
Vary: Accept-Encoding

and then i waited a few seconds and hit enter - in the same tab. Fiddler came up with ANOTHER *200 request:

Requests started at:    22:58:09:2516
Responses completed at: 22:58:12:3999
Total Sequence time:    00:00:03.1482360

RESPONSE CODES
--------------
HTTP/200:   1

And the headers were :

Cache-Control: public, max-age=31536000
Date: Wed, 18 Feb 2009 06:58:09 GMT
Expires: Thu, 18 Feb 2010 06:58:09 GMT
Vary: Accept-Encoding

Obviously this is NOT what I was expecting.

Yes - Accept-Encoding was the same for both requests.

Yes - A third request gave me a 304

This was on a new install of Chrome that I'd never done any development on - and on which I'd only just installed Fiddler for the first time.

I cant wait for someone to explain that to me. For now I'm giving up - I think my caching and expiration code is fine. In addition ASP.NET MVC appears to be forcing Expires to be the current time. This obviously isn't a factor in my google example.

I think Chrome is being too clever and this has to be a bug - I'm on version 1.0.154.48.

Simon_Weaver

related questions