dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.43k stars 10.01k forks source link

Implement response caching middleware #2629

Closed aspnet-hello closed 6 years ago

aspnet-hello commented 6 years ago

From @Tratcher on Tuesday, June 9, 2015 9:53:36 AM

MVC and other application level components will set standard HTTP cache headers (Cache-Control, Vary, etc.) for clients and proxies.

Middleware will read the response headers and cache in a local memory cache when appropriate. Then it can short circuit incoming requests and serve them from the cache.

We may also need a feature interface for interacting with server level caches (e.g. to limit double caching).

Copied from original issue: aspnet/ResponseCaching#1

aspnet-hello commented 6 years ago

From @darrelmiller on Wednesday, June 10, 2015 6:17:10 AM

Considering @Tratcher has done most of the part that I don't know how to do (i.e. the middleware stream handling ) would you consider a PR for the caching logic based on what I've already done?

aspnet-hello commented 6 years ago

From @Tratcher on Wednesday, June 10, 2015 6:54:33 AM

PRs are good, but I don't think we're ready for that just yet. So far you've been commenting on my prototype in a feature branch. We haven't even decided on the feature set yet. Care to propose an outline of features?

aspnet-hello commented 6 years ago

From @darrelmiller on Wednesday, June 10, 2015 6:57:17 AM

Oh, maybe I misunderstood. I thought the idea was to implement HTTP caching based on RFC 7234. That was the feature set I was expecting.

aspnet-hello commented 6 years ago

From @Tratcher on Wednesday, June 10, 2015 7:07:03 AM

Not all of 7234 applies to server side caches, and even stuff that does can get pretty complicated. I don't think we'll attempt to support the full RFC, especially not in the first release. I think we want to focus on the most useful, performance enhancing elements first, and wait on some of the more complicated items (e.g. range requests, content negotiation, etc..). Hence the need for a breakdown and prioritization of features before we get too far into development.

aspnet-hello commented 6 years ago

From @darrelmiller on Wednesday, June 10, 2015 7:35:10 AM

Got it. Here would be my suggestions:

Minimum features:

Cache responses to: GET, HEAD, POST Support request directives : no-store, no-cache, max-age, min-fresh, max-stale Support response directives: public, private (without fields), s-max-age, must-revalidate, proxy-revalidate, no-cache (without fields), Expires Support storing different variants based on vary header. Add Age header to responses served from cache. Invalidation based on PUT/POST/DELETE/PATCH and Location/Content-Location

Possible features:

Request directive : only-if-cached Response directive: no-cache (with fields), private (with fields) Support heuristic based caching Warning header Responding to conditional GETs using cached responses. Convert unconditional requests to conditional requests if validator is present Normalization of vary header values Support authentication header to allow serving of cached authenticated responses

aspnet-hello commented 6 years ago

From @Tratcher on Wednesday, June 10, 2015 8:29:16 AM

Ambitious. How would you break it down if I asked you to split your minimum set into minimum and intermediate? E.g. from my research so far caching POSTs is far less common than GETs. Supporting different variants based on the vary header looks like one of the most complex portions of the implementation. I was considering a preliminary step of storing only a single variant, while still honoring the vary header. I saw a lot of sites set vary, but there was only a single variant sent most of the time.

Also, why support private on the server? I would expect that to be redundant with the client's cache.

aspnet-hello commented 6 years ago

From @darrelmiller on Wednesday, June 10, 2015 8:54:35 AM

Dropping caching POST responses would be an easy first cut. Most of the request directives are fairly easy to deal with as they mostly just affect checking freshness values. Calculating the age the easy way is easy, however, calculating the "corrected age" I found somewhat mind bending and never got round to implementing that.

You could try just supporting a single variant. However, I made an error in the way I handled variants initially and when I came to fix it, I had to do a major re-work that involved changing the way I accessed the persistence store and what needed to be persisted. Now I know how to do variants properly it isn't that hard, as long as you do a blind comparison of accept headers and don't try and do any fancy normalization.

WinInet caching made the mistake of not doing vary properly in the first place and we are stuck with it years later. I think a lot more people would use vary if it could be relied on to work properly.

The server cache needs to recognize the private directive to know that it is not allowed to cache the content if the private directive is there. Including public does not override private.

aspnet-hello commented 6 years ago

From @darrelmiller on Wednesday, June 10, 2015 9:05:06 AM

The way I implemented the vary algorithm goes like this:

The interesting part is that because there may be multiple cache entries with the same key, you can't use a simple key/value store. There needs to be some kind of index to match keys to cache entries. If you start by supporting just a single variant, then a key/value store works just fine. But changing this after the fact is not trivial.

aspnet-hello commented 6 years ago

From @Tratcher on Wednesday, June 10, 2015 10:03:32 AM

Yeah, I was hoping to use MemoryCache as the backing store, but storing multiple variants makes that harder. It might still work if in each key you store a list of variants.

Feel free to take my prototype branch and start working on the minimal feature set. Preferably we can review and merge it in iterations.

aspnet-hello commented 6 years ago

From @darrelmiller on Wednesday, June 10, 2015 10:36:24 AM

Yes. That's what I have done with my in-memory store.

I created a fork, I'll try adding some stuff.

aspnet-hello commented 6 years ago

From @grumpydev on Wednesday, June 10, 2015 11:24:23 AM

I've considered a similar thing for Nancy caching a few times, but I'm not convinced there's much utility here for a few reasons:

Just my 2p - if this is implemented though in my opinion it has to support Vary, ideally a fairly complete implementation of it, but at a minimum support vary accept - we've seen plenty of things, from wininet to chromium, not support this properly and it's caused pain for years afterwards.

mikaelm12 commented 6 years ago

Response Caching Middleware has been done and was released in 1.1 👍