Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for "cache-control:no-cache" in request #196

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from

Conversation

abpatel
Copy link

@abpatel abpatel commented Jan 28, 2017

  1. Added Support for "cache-control:no-cache" in request
  2. Updated CacheOutputUntil in client side unit test
  3. Updated NUnit version and added test runner

2)Updated CacheOutputUntil in client side unit test
3) Updated NUnit version and added test runner
@@ -223,6 +231,11 @@ public override void OnActionExecuting(HttpActionContext actionContext)
var responseMediaType = actionExecutedContext.Request.Properties[CurrentRequestMediaType] as MediaTypeHeaderValue ?? GetExpectedMediaType(httpConfig, actionExecutedContext.ActionContext);
var cachekey = cacheKeyGenerator.MakeCacheKey(actionExecutedContext.ActionContext, responseMediaType, ExcludeQueryStringFromCacheKey);

if (IsNoCacheHeaderInRequest(actionExecutedContext.ActionContext))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this shouldn't be here - once a no cache request has been sent by any client, it shouldn't invalidate cache for all clients

@filipw
Copy link
Owner

filipw commented Mar 14, 2017

thanks, can you please incorporate the requested changes?

@@ -242,12 +255,12 @@ public override void OnActionExecuting(HttpActionContext actionContext)
_webApiCache.Add(baseKey, string.Empty, cacheTime.AbsoluteExpiration);
_webApiCache.Add(cachekey, content, cacheTime.AbsoluteExpiration, baseKey);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this effectively invalidate the cache for all clients anyways since we are adding(replacing) the cache entry for the same exact cache key that we are explicitly removing in Line 234 ? Since the cache is public and not a per client private cache, do you see any use cases wherein it's harmful to have the cache invalidated for a specific key "globally", if requested so by one client by specifying "cache-control:no-cache"

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this code would only run if the request wasn't cached in the first place.
I don't think you should invalidate the cache for everyone by any client call, since that would defeat the purpose of caching at all. I could then write a malicious client that just busts all the cache on the server all the time, which is definitely not good

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it also run if we are bypassing the cache in 'OnActionExecuting' by checking if the client requested 'no-cache'?
Should we then not cache the fresh response at all for that, specific client call and just return the data we retrieve from executing the requested action and let the existing (stale) cached response be as is, so that it eventually expires per the expiration policy that was set on the action?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I do understand your point, since we already have the response at this point, we might as well use it to update the cache.

My main concern is that, in reality, cache-control headers are not really intended for the server, they are really intended for clients + intermediaries (such as proxies). So they instruct clients how to handle responses in terms of caching, and they instruct proxies how to deal with requests.

Building a convoluted caching logic on the server side based on them is not necessarily a good idea, or I am not sure everyone would be happy to have their cache getting busted at any point by and client from anywhere in the world. If you rely on caching to do performance lift for you, this is a performance black hole.

Maybe a better idea is to have this feature controlled by a flag on the caching attribute, something like [CacheOutput(AllowCacheBypass = true)]

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with all your points. When I read the http spec on 'no-cache', it was light on details on whether it is specific to client side only. Although majority of cases are for client side use, there is nothing in the spec that precludes using it on the server.
Perhaps the easiest option then, is to bypass the cache completely and let it expire on it own per the expiration period. I think the additional attribute makes sense and we can use that in combination with the presence or absence of no-cache.sound good?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One other thought is to use something like
[CacheOutput(AllowPrivate=true, AllowCacheBypass=true)] which enables a per client cache if the client passes in a 'cache-control: private' header, we could then allow for using 'no-cache' if the client chooses to refresh their cache, we then invalidate their private cache, invoke the action, cache the new data (as private again) and return back. This way the client cannot affect any other client and a malicious client would constantly be invalidating only their cache at the expense of the server spinning cpu cycles to invalidate. Thoughts?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@filipw Any thoughts on this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to jump into this discussion without invitation, but I thought on something similar: when a client issues a no-cache is actually saying "bypass whatever you have cached", but not saying that "you should remove your cached data".
I implemented something similar in a project and our approach was much simpler: if the client sent a no-cache it was exactly the same as if the controller said the results shouldn't be cached. And to implement so we overloaded the IsCachingAllowed, adding a condition similar to what's evaluated in @abpatel IsNoCacheHeaderInRequest.
If you like, I can create a PR with that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more the merrier. :-)... That's something I've considered as well, but the issue IMHO that you run into is that at what point would the client know NOT to use no-cache anymore? I imagine the client would have to do some sort of time calculation based on max-age to know when the old cached value expires so that it can stop sending no-cache, and reap the benefits of caching instead of passing through the cache everytime

@Kilowhisky
Copy link

Any update on this? I'm pretty sure this is causing 502 errors for my clients calling my service through a proxy.

@abpatel
Copy link
Author

abpatel commented Mar 17, 2017 via email

@dmalanij
Copy link
Contributor

dmalanij commented Apr 6, 2017

Just to keep things ordered, I assume this PR tackles #190
Sorry, can't help my OCD 😃

@abpatel
Copy link
Author

abpatel commented Apr 6, 2017

@dmalanij: yes, if this pans out as expected it will tackle #190

patel added 2 commits April 7, 2017 19:27
2) Add unit test to ensure that no cache conrol / etag headers are sent in respone when no-cache sent in request
@abpatel
Copy link
Author

abpatel commented Apr 8, 2017

@filipw For now, I've removed the cache invalidation when no-cache is present in the request headers.
This basically is now a pass-through and cache-control/etag is not sent back to the client.
Can you review and merge this ASAP. I really need this ability in this package.

@Kilowhisky
Copy link

Update?

@abpatel
Copy link
Author

abpatel commented May 26, 2017

@filipw can you merge the PR for this please?

@@ -175,6 +181,8 @@ public override void OnActionExecuting(HttpActionContext actionContext)

if (!_webApiCache.Contains(cachekey)) return;

if (IsNoCacheHeaderInRequest(actionContext)) return;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you move this up? there is no need to go through cache key generator if this is the case.
I'd put it right after IsCachingAllowed()

@@ -223,6 +234,11 @@ public override void OnActionExecuting(HttpActionContext actionContext)
var responseMediaType = actionExecutedContext.Request.Properties[CurrentRequestMediaType] as MediaTypeHeaderValue ?? GetExpectedMediaType(httpConfig, actionExecutedContext.ActionContext);
var cachekey = cacheKeyGenerator.MakeCacheKey(actionExecutedContext.ActionContext, responseMediaType, ExcludeQueryStringFromCacheKey);

//if (IsNoCacheHeaderInRequest(actionExecutedContext.ActionContext))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please remove the commented code?

@filipw
Copy link
Owner

filipw commented May 26, 2017

@abpatel sorry for the delay - I just left two small comments, can you address them please

@Kilowhisky
Copy link

Not sure how to address these code changes on this pull request so i made another one #207

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

Successfully merging this pull request may close these issues.

4 participants