Skip to content
This repository has been archived by the owner on Nov 22, 2018. It is now read-only.

Cache-Control: no-cache breaks the middleware #81

Closed
colltoaction opened this issue Nov 28, 2016 · 13 comments
Closed

Cache-Control: no-cache breaks the middleware #81

colltoaction opened this issue Nov 28, 2016 · 13 comments
Assignees

Comments

@colltoaction
Copy link

TL;DR: this breaks with the Cache-Control: no-cache header since the IResponseCachingFeature feature is not set up and we hit this exception.


Hi,

I'm just starting with your middleware, and the basic functionality is great. I just specify a [ResponseCache] and it gets cached automatically, which is awesome :).

But I'm getting an error 500 each time I do "Ctrl+F5" or I disable caching in the Chrome dev tools. The error is the one in the title:

An unhandled exception occurred while processing the request. InvalidOperationException: 'VaryByQueryKeys' requires the response cache middleware.

Microsoft.AspNetCore.Mvc.Internal.ResponseCacheFilter.OnActionExecuting(ActionExecutingContext context)

The error is the same as if I had not called app.UseResponseCaching(); or called it in the wrong order. The exact line is the following, which shows that it's checking for a Feature of type IResponseCachingFeature: https://github.com/aspnet/Mvc/blob/d8c6c4ab34e1368c1b071a01fcdcb9e8cc12e110/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseCacheFilter.cs#L132.

I investigated the source code and came to the conclusion that the error is produced because:

  1. Browsers send Cache-Control: no-cache header
  2. The request is marked as not cacheable here
  3. Since it's not cacheable we skip the call to ShimResponseStream
  4. And we don't set up the Feature for the next middlewares.
  5. So we end up with the mentioned exception in this line

If you could give me any workaround until a fix is published I'd really appreciate it!

Thanks,
Martín.

@Tratcher Tratcher added the bug label Nov 29, 2016
@Tratcher
Copy link
Member

@tinchou Thanks for the detailed investigation. We'll get this dealt with ASAP. One possible workaround would be to always set an IResponseCachingFeature. Try putting this between the caching middleware and MVC:

app.Use((context, next) =>
{
  if (context.Features.Get<IResponseCachingFeature>() == null)
  {
    context.Features.Set<IResponseCachingFeature>(new FakeResponseCachingFeature());
  }
  return next();
});

@muratg @DamianEdwards This is a patch candidate bug.

@DamianEdwards DamianEdwards added this to the 1.1.1 milestone Nov 29, 2016
@colltoaction
Copy link
Author

Can confirm the workaround works for me.

I implemented FakeResponseCachingFeature as:

    public class FakeResponseCachingFeature : IResponseCachingFeature
    {
        public string[] VaryByQueryKeys
        {
            get => null;
            set { }
        }
}

@JunTaoLuo
Copy link
Contributor

@pranavkm we'll need to build a 1.1.1 branch for this.

@JunTaoLuo
Copy link
Contributor

JunTaoLuo commented Nov 30, 2016

@Tratcher and I think the middleware needs to be fixed. The feature should be added regardless of whether the request is cacheable since it is used when generating the response. Cache-Control headers are unidirectional and response generated by a request with no-cache can be used to update the existing entry in the cache.

@Tratcher
Copy link
Member

In other words, the request header Cache-Control: no-cache says do not retrieve the resource from cache, it does not prohibit caching the new response.

@colltoaction
Copy link
Author

colltoaction commented Nov 30, 2016 via email

@DamianEdwards
Copy link
Member

@tinchou when it comes to HTTP-based response caching, yes, the client still maintains ability to send cache control headers, thus affecting upstream caching behavior. As a result, you shouldn't rely on whole response caching to control load on your application. Using IMemoryCache or the <cache> Tag Helper are ways you can cache parts of the your application logic and fragments of HTML responses separately from systems that use HTTP cache control headers.

The old docs for ASP.NET Caching (from the System.Web days) covers the differences quite nicely in this introduction.

All that said, it might still be nice to have the response caching middleware support a mode whereby it ignores all client-based HTTP cache control headers, and instead acts as a completely server-controlled cache.

@colltoaction
Copy link
Author

colltoaction commented Nov 30, 2016

@DamianEdwards yes, I think that would be my preferred scenario. Since I'm already encoding caching information in my ResponseCache attribute, it'd be really nice if I could have server-side caching automatically set up too.

But if the client can void my cache as they please, then that defeats the whole performance story.


EDIT:

I'm curious, why would anyone want the server to ignore the cache when told by the client?

@DamianEdwards
Copy link
Member

Because that's normally how HTTP-based caching works. There's a client, server, and any amount of intermediaries in between, and they all speak HTTP and participate in caching, using the same cache control headers.

We should investigate the caching behavior of the popular edge caching appliances & services, along with other frameworks, to see how they differentiate these concerns.

@guardrex
Copy link

The Azure CDN doesn't respect an invalidation attempt using the Cache-Control header.

Initial request for a stale image serves from the origin ...

capture1

HTTP/1.1 200 OK
Cache-Control: public,max-age=2592000
Content-Type: image/png
Date: Wed, 30 Nov 2016 23:15:08 GMT
Etag: 0x8D39DF4E61FD969
Last-Modified: Sun, 26 Jun 2016 19:05:54 GMT
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-blob-type: BlockBlob
x-ms-lease-status: unlocked
x-ms-meta-CbModifiedTime: Fri, 24 Oct 2014 23:37:07 GMT
x-ms-request-id: fdf3f5e1-0001-0028-6f5f-4b9658000000
x-ms-version: 2009-09-19
Content-Length: 12787

Second request with Cache-Control: no-cache is not respected by the CDN and it serves from the edge node. Note X-Cache: HIT and Server ...

capture2

HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: public,max-age=2592000
Content-Type: image/png
Date: Wed, 30 Nov 2016 23:15:13 GMT
Etag: 0x8D39DF4E61FD969
Last-Modified: Sun, 26 Jun 2016 19:05:54 GMT
Server: ECAcc (dfw/56E0)
X-Cache: HIT
x-ms-blob-type: BlockBlob
x-ms-lease-status: unlocked
x-ms-meta-CbModifiedTime: Fri, 24 Oct 2014 23:37:07 GMT
x-ms-request-id: fdf3f5e1-0001-0028-6f5f-4b9658000000
x-ms-version: 2009-09-19
Content-Length: 12787

@ctolkien
Copy link

The Azure CDN doesn't respect an invalidation attempt using the Cache-Control header.

Neither does CloudFlare.

image

image

So I think this behaviour is typical of edge caches.

@guardrex
Copy link

it might still be nice to have the response caching middleware support a mode whereby it ignores all client-based HTTP cache control headers, and instead acts as a completely server-controlled cache.

👍 I like this idea. If offered, I'll definitely use it.

JunTaoLuo added a commit that referenced this issue Jan 10, 2017
- Always add IresponseCachingFeatu8re before calling the next middleware #81
- Use If-Modified-Since instead of the incorrect If-Unmodified-Since header #83
- Handle proxy-revalidate in the same way as must-revalidate #83
- Handle max-stale with no specified limit #83
- Bypass cache lookup for no-cache but store the response #83
- Bypass response capturing and buffering when no-store is specified #83
- Replace IsRequestCacheable cache policy with three new independent policy checks to reflect these changes
JunTaoLuo added a commit that referenced this issue Jan 10, 2017
- Always add IresponseCachingFeatu8re before calling the next middleware #81
- Use If-Modified-Since instead of the incorrect If-Unmodified-Since header #83
- Handle proxy-revalidate in the same way as must-revalidate #83
- Handle max-stale with no specified limit #83
- Bypass cache lookup for no-cache but store the response #83
- Bypass response capturing and buffering when no-store is specified #83
- Replace IsRequestCacheable cache policy with three new independent policy checks to reflect these changes
- Modify middleware flow to accommodate cache policy updates
JunTaoLuo added a commit that referenced this issue Jan 10, 2017
- Always add IresponseCachingFeatu8re before calling the next middleware #81
- Use If-Modified-Since instead of the incorrect If-Unmodified-Since header #83
- Handle proxy-revalidate in the same way as must-revalidate #83
- Handle max-stale with no specified limit #83
- Bypass cache lookup for no-cache but store the response #83
- Bypass response capturing and buffering when no-store is specified #83
- Replace IsRequestCacheable cache policy with three new independent policy checks to reflect these changes
- Modify middleware flow to accommodate cache policy updates
@JunTaoLuo JunTaoLuo modified the milestones: 1.2.0, 1.1.1 Jan 10, 2017
@JunTaoLuo
Copy link
Contributor

Fixed in #88. Will need to back port to 1.1.1 in a separate issue.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants