-
Notifications
You must be signed in to change notification settings - Fork 252
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
base: dev
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,7 +60,7 @@ public int SharedTimeSpan | |
get // required for property visibility | ||
{ | ||
if (!_sharedTimeSpan.HasValue) | ||
throw new Exception("should not be called without value set"); | ||
throw new Exception("should not be called without value set"); | ||
return _sharedTimeSpan.Value; | ||
} | ||
set { _sharedTimeSpan = value; } | ||
|
@@ -80,7 +80,7 @@ public int SharedTimeSpan | |
/// Class used to generate caching keys | ||
/// </summary> | ||
public Type CacheKeyGenerator { get; set; } | ||
|
||
// cache repository | ||
private IApiOutputCache _webApiCache; | ||
|
||
|
@@ -106,7 +106,7 @@ protected virtual bool IsCachingAllowed(HttpActionContext actionContext, bool an | |
return false; | ||
} | ||
|
||
return actionContext.Request.Method == HttpMethod.Get; | ||
return actionContext.Request.Method == HttpMethod.Get; | ||
} | ||
|
||
protected virtual void EnsureCacheTimeQuery() | ||
|
@@ -156,12 +156,18 @@ protected virtual MediaTypeHeaderValue GetExpectedMediaType(HttpConfiguration co | |
return responseMediaType; | ||
} | ||
|
||
private bool IsNoCacheHeaderInRequest(HttpActionContext actionContext) | ||
{ | ||
var cacheControl = actionContext.Request.Headers.CacheControl; | ||
return cacheControl != null && cacheControl.NoCache; | ||
} | ||
|
||
public override void OnActionExecuting(HttpActionContext actionContext) | ||
{ | ||
if (actionContext == null) throw new ArgumentNullException("actionContext"); | ||
|
||
if (!IsCachingAllowed(actionContext, AnonymousOnly)) return; | ||
|
||
var config = actionContext.Request.GetConfiguration(); | ||
|
||
EnsureCacheTimeQuery(); | ||
|
@@ -175,6 +181,8 @@ public override void OnActionExecuting(HttpActionContext actionContext) | |
|
||
if (!_webApiCache.Contains(cachekey)) return; | ||
|
||
if (IsNoCacheHeaderInRequest(actionContext)) return; | ||
|
||
if (actionContext.Request.Headers.IfNoneMatch != null) | ||
{ | ||
var etag = _webApiCache.Get<string>(cachekey + Constants.EtagKey); | ||
|
@@ -223,6 +231,11 @@ public override async Task OnActionExecutedAsync(HttpActionExecutedContext actio | |
var responseMediaType = actionExecutedContext.Request.Properties[CurrentRequestMediaType] as MediaTypeHeaderValue ?? GetExpectedMediaType(httpConfig, actionExecutedContext.ActionContext); | ||
var cachekey = cacheKeyGenerator.MakeCacheKey(actionExecutedContext.ActionContext, responseMediaType, ExcludeQueryStringFromCacheKey); | ||
|
||
if (IsNoCacheHeaderInRequest(actionExecutedContext.ActionContext)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
{ | ||
_webApiCache.Remove(cachekey); | ||
} | ||
|
||
if (!string.IsNullOrWhiteSpace(cachekey) && !(_webApiCache.Contains(cachekey))) | ||
{ | ||
SetEtag(actionExecutedContext.Response, CreateEtag(actionExecutedContext, cachekey, cacheTime)); | ||
|
@@ -242,12 +255,12 @@ public override async Task OnActionExecutedAsync(HttpActionExecutedContext actio | |
_webApiCache.Add(baseKey, string.Empty, cacheTime.AbsoluteExpiration); | ||
_webApiCache.Add(cachekey, content, cacheTime.AbsoluteExpiration, baseKey); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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'? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One other thought is to use something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @filipw Any thoughts on this? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
||
|
||
_webApiCache.Add(cachekey + Constants.ContentTypeKey, | ||
contentType, | ||
cacheTime.AbsoluteExpiration, baseKey); | ||
|
||
|
||
_webApiCache.Add(cachekey + Constants.EtagKey, | ||
etag, | ||
cacheTime.AbsoluteExpiration, baseKey); | ||
|
@@ -263,12 +276,12 @@ protected virtual void ApplyCacheHeaders(HttpResponseMessage response, CacheTime | |
if (cacheTime.ClientTimeSpan > TimeSpan.Zero || MustRevalidate || Private) | ||
{ | ||
var cachecontrol = new CacheControlHeaderValue | ||
{ | ||
MaxAge = cacheTime.ClientTimeSpan, | ||
SharedMaxAge = cacheTime.SharedTimeSpan, | ||
MustRevalidate = MustRevalidate, | ||
Private = Private | ||
}; | ||
{ | ||
MaxAge = cacheTime.ClientTimeSpan, | ||
SharedMaxAge = cacheTime.SharedTimeSpan, | ||
MustRevalidate = MustRevalidate, | ||
Private = Private | ||
}; | ||
|
||
response.Headers.CacheControl = cachecontrol; | ||
} | ||
|
@@ -293,4 +306,4 @@ private static void SetEtag(HttpResponseMessage message, string etag) | |
} | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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()