10

I have a strange issue with IIS 7.
Sometimes it seems to return a 304 instead of a 200.

Here is a sample request captured with Fiddler:
(Note that the file requested is not located in my browsers cache yet.)

GET https://[mysite]/Content/js/jquery.form.js HTTP/1.1
Accept: */*
Referer: https://[mysite]/Welcome/News
Accept-Language: sv-SE
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; OfficeLiveConnector.1.4; OfficeLivePatch.1.3; .NET4.0C; .NET4.0E)
Accept-Encoding: gzip, deflate
Host: [mysite]
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: ...

Note that there is no If-Modified-Since or If-None-Match in the request.
But still the response is:

HTTP/1.1 304 Not Modified
Cache-Control: public
Expires: Tue, 02 Mar 2010 06:26:08 GMT
Last-Modified: Mon, 22 Feb 2010 21:58:44 GMT
ETag: "1CAB40A337D4200"
Server: Microsoft-IIS/7.5
X-Powered-By: ASP.NET
Date: Mon, 01 Mar 2010 17:06:34 GMT

Does anyone have a clue of what could be wrong here?

I'm running IIS 7 on Windows Web Server 2008 R2.

EDIT:

I've found a workaround, enable caching and then disable it on an extension level did the trick for me.

<configuration>
  <system.webServer>
    <caching enabled="true" enableKernelCache="true">
      <profiles>
        <add extension=".png" policy="DisableCache" kernelCachePolicy="DisableCache" />
        <add extension=".gif" policy="DisableCache" kernelCachePolicy="DisableCache" />
        <add extension=".js" policy="DisableCache" kernelCachePolicy="DisableCache" />
        <add extension=".css" policy="DisableCache" kernelCachePolicy="DisableCache" />
      </profiles>
    </caching>
    <staticContent>
      <clientCache cacheControlMode="NoControl" />
    </staticContent>
  </system.webServer>
</configuration>
  • I'm having the EXACT same issue, and it's causing a lot of problems on a very busy web server. IIS returns a 304, even though the client doesn't yet have a copy of the resource. – Philippe Leybaert Apr 28 '10 at 13:05
  • @Philippe, have you found a solution yet? Otherwise see my edit above for an workaround and checkout the new answer below. – Ola Herrdahl Jun 4 '10 at 13:12
  • @OlaHerrdahl, your workaround is perfect :) I had this exact issue as well. Thanks! – Ardee Aram Aug 21 '12 at 2:51
3

According to section 14.9 of the HTTP1.1 spec, the no-cache directive for the Cache-Control header is only imposable by the origin server, which means IIS is ignoring the header in your request.

The cache-control directives can be broken down into these general categories:

  - Restrictions on what are cacheable; these may only be imposed

by the origin server.

Section 14.9.1 defines public, private, and no-cache as the directives restricting what is cacheable, which can only be imposed by the server.

If you don't want your .js file to be cached you'll either need to set the no-cache directive in the app (ie- the ASP.NET code) or you'll need to change the Cache-Control header in the request to use the no-store directive instead of no-cache.

EDIT:
Based on your comment - yes I assumed you did not want the file cached. The 304, then, might be coming as a result of the file being in one of IIS's internal caches. Have a look at these:

  • I think you misunderstood my problem here. The file is not located in my cache yet. But still the server replies with a 304... This seems to happen randomly when browsing the site in all major browsers. – Ola Herrdahl Mar 1 '10 at 18:45
  • @Ola Herrdahl: Have a look at my edit. – squillman Mar 1 '10 at 19:15
  • I have tried disabling caching in IIS as well and it makes no difference... :( – Ola Herrdahl Mar 2 '10 at 8:21
1

I have been having the same problem for a while and have all caching turned off... However, I installed the Compression module for IIS7 at some point which by default had enabled compression of static files on my existing sites. I turned all compression off for the impacted sites and now they seems to be working fine touch wood.

1

We were also experiencing this bug but we were using an asset management library (Cassette). After an extensive investigation of this issue, we've found that the root cause of this issue is with a combination of ASP.NET, IIS, and Cassette. I'm not sure if this is your problem (using the Headers API rather than the Cache API), but the pattern seems to be the same.

Bug # 1

Cassette sets the Vary: Accept-Encoding header as part of its response to a bundle since it can encode the content with gzip/deflate:

However, the ASP.NET output cache will always return the response that was cached first. For example, if the first request has Accept-Encoding: gzip and Cassette returns gzipped content, the ASP.NET output cache will cache the URL as Content-Encoding: gzip. The next request to the same URL but with a different acceptable encoding (e.g. Accept-Encoding: deflate) will return the cached response with Content-Encoding: gzip.

This bug is caused by Cassette using the HttpResponseBase.Cache API to set the output cache settings (e.g. Cache-Control: public) but using the HttpResponseBase.Headers API to set the Vary: Accept-Encoding header. The problem is that the ASP.NET OutputCacheModule is not aware of response headers; it only works via the Cache API. That is, it expects the developer to use an invisibly tightly-coupled API rather than just standard HTTP.

Bug # 2

When using IIS 7.5 (Windows Server 2008 R2), bug # 1 can cause a separate issue with the IIS kernel and user caches. For example, once a bundle is successfully cached with Content-Encoding: gzip, it's possible to see it in the IIS kernel cache with netsh http show cachestate. It shows a response with 200 status code and content encoding of "gzip". If the next request has a different acceptable encoding (e.g. Accept-Encoding: deflate) and an If-None-Match header that matches the bundle's hash, the request into IIS's kernel and user mode caches will be considered a miss. Thus, causing the request to be handled by Cassette which returns a 304:

However, once IIS's kernel and user modes process the response, they will see that the response for the URL has changed and the cache should be updated. If the IIS kernel cache is checked with netsh http show cachestate again, the cached 200 response is replaced with a 304 response. All subsequent requests to the bundle, regardless of Accept-Encoding and If-None-Match will return a 304 response. We saw the devastating effects of this bug where all users were served a 304 for our core script because of a random request that had an unexpected Accept-Encoding and If-None-Match.

The problem seems to be that the IIS kernel and user mode caches are not able to vary based on the Accept-Encoding header. As evidence of this, by using the Cache API with the workaround below, the IIS kernel and user mode caches seem to be always skipped (only the ASP.NET output cache is used). This can be confirmed by checking that netsh http show cachestate is empty with the workaround below. ASP.NET communicates with the IIS worker directly to selectively enable or disable the IIS kernel and user mode caches per-request.

We were not able to reproduce this bug on newer versions of IIS (e.g. IIS Express 10). However, bug # 1 was still reproducible.

Our original fix for this bug was to disable IIS kernel/user mode caching only for Cassette requests like others mentioned. By doing so, we uncovered bug # 1 when deploying an extra layer of caching in front of our web servers. The reason that the query string hack worked is because the OutputCacheModule will record a cache miss if the Cache API has not been used to vary based on the QueryString and if the request has a QueryString.

Workaround

We've been planning to move away from Cassette anyways, so rather than maintaining our own fork of Cassette (or trying to get a PR merged), we opted to use an HTTP module to work around this issue.

public class FixCassetteContentEncodingOutputCacheBugModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.PostRequestHandlerExecute += Context_PostRequestHandlerExecute;
    }

    private void Context_PostRequestHandlerExecute(object sender, EventArgs e)
    {
        var httpContext = HttpContext.Current;

        if (httpContext == null)
        {
            return;
        }

        var request = httpContext.Request;
        var response = httpContext.Response;

        if (request.HttpMethod != "GET")
        {
            return;
        }

        var path = request.Path;

        if (!path.StartsWith("/cassette.axd", StringComparison.InvariantCultureIgnoreCase))
        {
            return;
        }

        if (response.Headers["Vary"] == "Accept-Encoding")
        {
            httpContext.Response.Cache.VaryByHeaders.SetHeaders(new[] { "Accept-Encoding" });
        }
    }

    public void Dispose()
    {

    }
}

I hope this helps someone 😄!

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.