internal Func <HttpRequestMessage, Task <HttpResponseMessage> > PutIfUnmodifiedSince() { return(request => { if (request.Method != HttpMethod.Put) { return null; } DateTimeOffset?ifUnmodifiedSince = request.Headers.IfUnmodifiedSince; if (ifUnmodifiedSince == null) { return null; } DateTimeOffset modifiedInQuestion = ifUnmodifiedSince.Value; IEnumerable <KeyValuePair <string, IEnumerable <string> > > headers = request.Headers.Where(h => _varyByHeaders.Any(v => v.Equals(h.Key, StringComparison.CurrentCultureIgnoreCase))); string resource = UriTrimmer(request.RequestUri); EntityTagKey entityTagKey = EntityTagKeyGenerator(resource, headers); TimedEntityTagHeaderValue actualEtag; bool isModified = false; if (_entityTagStore.TryGetValue(entityTagKey, out actualEtag)) { isModified = actualEtag.LastModified > modifiedInQuestion; } return isModified ? new HttpResponseMessage(HttpStatusCode.PreconditionFailed).ToTask() : null; }); }
internal Func <HttpRequestMessage, Task <HttpResponseMessage> > PutIfMatch() { return(request => { if (request.Method != HttpMethod.Put) { return null; } ICollection <EntityTagHeaderValue> matchTags = request.Headers.IfMatch; if (matchTags == null || matchTags.Count == 0) { return null; } IEnumerable <KeyValuePair <string, IEnumerable <string> > > headers = request.Headers.Where(h => _varyByHeaders.Any(v => v.Equals(h.Key, StringComparison.CurrentCultureIgnoreCase))); string resource = UriTrimmer(request.RequestUri); EntityTagKey entityTagKey = EntityTagKeyGenerator(resource, headers); TimedEntityTagHeaderValue actualEtag; bool matchFound = false; if (_entityTagStore.TryGetValue(entityTagKey, out actualEtag)) { if (matchTags.Any(etag => etag.Tag == actualEtag.Tag)) { matchFound = true; } } return matchFound ? null : new HttpResponseMessage(HttpStatusCode.PreconditionFailed).ToTask(); }); }
/// <summary> /// This invalidates the resource based on routePattern /// for methods POST, PUT and DELETE. /// It also removes for all linked URLs /// </summary> /// <param name="entityTagKey"></param> /// <param name="request"></param> /// <param name="response"></param> /// <returns></returns> internal Action InvalidateCache( EntityTagKey entityTagKey, HttpRequestMessage request, HttpResponseMessage response) { return (() => { if (!request.Method.Method.IsIn("PUT", "DELETE", "POST")) { return; } string uri = UriTrimmer(request.RequestUri); // remove pattern _entityTagStore.RemoveAllByRoutePattern(entityTagKey.RoutePattern); // remove all related URIs IEnumerable <string> linkedUrls = LinkedRoutePatternProvider(uri, request.Method); foreach (string linkedUrl in linkedUrls) { _entityTagStore.RemoveAllByRoutePattern(linkedUrl); } }); }
public void AddOrUpdate(EntityTagKey key, TimedEntityTagHeaderValue eTag) { _eTagCache.AddOrUpdate(key, eTag, (theKey, oldValue) => eTag); _routePatternCache.AddOrUpdate(key.RoutePattern, new HashSet<EntityTagKey> { key }, (routePattern, hashSet) => { hashSet.Add(key); return hashSet; }); }
protected void ExecuteCacheAdditionRules(EntityTagKey entityTagKey, HttpRequestMessage request, HttpResponseMessage response, IEnumerable <KeyValuePair <string, IEnumerable <string> > > varyHeaders) { new[] { AddCaching(entityTagKey, request, response, varyHeaders), // general caching } .Chain()(); }
protected void ExecuteCacheInvalidationRules(EntityTagKey entityTagKey, HttpRequestMessage request, HttpResponseMessage response) { new[] { InvalidateCache(entityTagKey, request, response), // general invalidation PostInvalidationRule(entityTagKey, request, response) } .Chain()(); }
/// <summary> /// Adds caching for GET and PUT if /// cache control provided is not null /// With PUT, since cache has been alreay invalidated, /// we provide the new ETag (old one has been cleared in invalidation phase) /// </summary> /// <param name="entityTagKey"></param> /// <param name="request"></param> /// <param name="response"></param> /// <param name="varyHeaders"></param> /// <returns></returns> internal Action AddCaching( EntityTagKey entityTagKey, HttpRequestMessage request, HttpResponseMessage response, IEnumerable <KeyValuePair <string, IEnumerable <string> > > varyHeaders) { return (() => { CacheControlHeaderValue cacheControlHeaderValue = CacheControlHeaderProvider(request); if (cacheControlHeaderValue == null) { return; } TimedEntityTagHeaderValue eTagValue; string uri = UriTrimmer(request.RequestUri); // in case of GET and no ETag // in case of PUT, we should return the new ETag of the resource // NOTE: No need to check if it is in the cache. If it were, it would not get // here if (request.Method == HttpMethod.Get || request.Method == HttpMethod.Put) { // create new ETag only if it does not already exist if (!_entityTagStore.TryGetValue(entityTagKey, out eTagValue)) { eTagValue = new TimedEntityTagHeaderValue(ETagValueGenerator(uri, varyHeaders)); _entityTagStore.AddOrUpdate(entityTagKey, eTagValue); } // set ETag response.Headers.ETag = eTagValue.ToEntityTagHeaderValue(); // set last-modified if (AddLastModifiedHeader && response.Content != null && !response.Content.Headers.Any(x => x.Key.Equals(HttpHeaderNames.LastModified, StringComparison.CurrentCultureIgnoreCase))) { response.Content.Headers.Add(HttpHeaderNames.LastModified, eTagValue.LastModified.ToString("r")); } // set Vary if (AddVaryHeader && _varyByHeaders != null && _varyByHeaders.Length > 0) { response.Headers.Add(HttpHeaderNames.Vary, _varyByHeaders); } response.Headers.TryAddWithoutValidation(HttpHeaderNames.CacheControl, cacheControlHeaderValue.ToString()); } }); }
public void AddOrUpdate(EntityTagKey key, TimedEntityTagHeaderValue eTag) { _eTagCache.AddOrUpdate(key, eTag, (theKey, oldValue) => eTag); _routePatternCache.AddOrUpdate(key.RoutePattern, new HashSet <EntityTagKey> { key }, (routePattern, hashSet) => { hashSet.Add(key); return(hashSet); }); }
internal Func <HttpRequestMessage, Task <HttpResponseMessage> > GetIfMatchNoneMatch() { return((request) => { if (request.Method != HttpMethod.Get) { return null; } ICollection <EntityTagHeaderValue> noneMatchTags = request.Headers.IfNoneMatch; ICollection <EntityTagHeaderValue> matchTags = request.Headers.IfMatch; if (matchTags.Count == 0 && noneMatchTags.Count == 0) { return null; // no etag } if (matchTags.Count > 0 && noneMatchTags.Count > 0) // both if-match and if-none-match exist { return new HttpResponseMessage(HttpStatusCode.BadRequest).ToTask(); } bool isNoneMatch = noneMatchTags.Count > 0; ICollection <EntityTagHeaderValue> etags = isNoneMatch ? noneMatchTags : matchTags; string resource = UriTrimmer(request.RequestUri); IEnumerable <KeyValuePair <string, IEnumerable <string> > > headers = request.Headers.Where(h => _varyByHeaders.Any(v => v.Equals(h.Key, StringComparison.CurrentCultureIgnoreCase))); EntityTagKey entityTagKey = EntityTagKeyGenerator(resource, headers); // compare the Etag with the one in the cache // do conditional get. TimedEntityTagHeaderValue actualEtag = null; bool matchFound = false; if (_entityTagStore.TryGetValue(entityTagKey, out actualEtag)) { if (etags.Any(etag => etag.Tag == actualEtag.Tag)) { matchFound = true; } } return matchFound ^ isNoneMatch ? null : new NotModifiedResponse(actualEtag.ToEntityTagHeaderValue()).ToTask(); }); }
internal Func <HttpRequestMessage, Task <HttpResponseMessage> > GetIfModifiedUnmodifiedSince() { return((request) => { if (request.Method != HttpMethod.Get && request.Headers != null) { return null; } DateTimeOffset?ifModifiedSince = request.Headers.IfModifiedSince; DateTimeOffset?ifUnmodifiedSince = request.Headers.IfUnmodifiedSince; if (ifModifiedSince == null && ifUnmodifiedSince == null) { return null; // no etag } if (ifModifiedSince != null && ifUnmodifiedSince != null) // both exist { return new HttpResponseMessage(HttpStatusCode.BadRequest).ToTask(); } bool ifModified = (ifUnmodifiedSince == null); DateTimeOffset modifiedInQuestion = ifModified ? ifModifiedSince.Value : ifUnmodifiedSince.Value; IEnumerable <KeyValuePair <string, IEnumerable <string> > > headers = request.Headers.Where(h => _varyByHeaders.Any(v => v.Equals(h.Key, StringComparison.CurrentCultureIgnoreCase))); string resource = UriTrimmer(request.RequestUri); EntityTagKey entityTagKey = EntityTagKeyGenerator(resource, headers); TimedEntityTagHeaderValue actualEtag; bool isModified = false; if (_entityTagStore.TryGetValue(entityTagKey, out actualEtag)) { isModified = actualEtag.LastModified > modifiedInQuestion; } return isModified ^ ifModified ? new NotModifiedResponse(actualEtag.ToEntityTagHeaderValue()).ToTask() : null; }); }
/// <summary> /// This is a scenario where we have a POST to a resource /// and it needs to invalidate the cache to that resource /// and all its linked URLs /// /// For example: /// POST /api/cars => invalidate /api/cars /// also it might invalidate /api/cars/fastest in which case /// /api/cars/fastest must be one of the linked URLs /// </summary> /// <param name="entityTagKey">entityTagKey</param> /// <param name="request">request</param> /// <param name="response">response</param> /// <returns>returns the function to execute</returns> internal Action PostInvalidationRule( EntityTagKey entityTagKey, HttpRequestMessage request, HttpResponseMessage response) { return(() => { if (request.Method != HttpMethod.Post) { return; } // if location header is set (for newly created resource), invalidate cache for it // this normally should not be necessary as the item is new and should not be in the cache // but releasing a non-existent item from cache should not have a big overhead if (response.Headers.Location != null) { _entityTagStore.RemoveAllByRoutePattern(response.Headers.Location.ToString()); } }); }
internal Func <Task <HttpResponseMessage>, HttpResponseMessage> GetCachingContinuation(HttpRequestMessage request) { return(task => { HttpResponseMessage response = task.Result; int statusCode = (int)response.StatusCode; if (statusCode >= 300 || statusCode < 200) // only if successful carry on processing { return response; } string uri = UriTrimmer(request.RequestUri); IEnumerable <KeyValuePair <string, IEnumerable <string> > > varyHeaders = request.Headers.Where(h => _varyByHeaders.Any(v => v.Equals(h.Key, StringComparison.CurrentCultureIgnoreCase))); EntityTagKey eTagKey = EntityTagKeyGenerator(uri, varyHeaders); ExecuteCacheInvalidationRules(eTagKey, request, response); ExecuteCacheAdditionRules(eTagKey, request, response, varyHeaders); return response; }); }
protected void ExecuteCacheAdditionRules(EntityTagKey entityTagKey, HttpRequestMessage request, HttpResponseMessage response, IEnumerable<KeyValuePair<string, IEnumerable<string>>> varyHeaders) { new[] { AddCaching(entityTagKey, request, response, varyHeaders), // general caching } .Chain()(); }
/// <summary> /// This is a scenario where we have a POST to a resource /// and it needs to invalidate the cache to that resource /// and all its linked URLs /// /// For example: /// POST /api/cars => invalidate /api/cars /// also it might invalidate /api/cars/fastest in which case /// /api/cars/fastest must be one of the linked URLs /// </summary> /// <param name="entityTagKey">entityTagKey</param> /// <param name="request">request</param> /// <param name="response">response</param> /// <returns>returns the function to execute</returns> internal Action PostInvalidationRule( EntityTagKey entityTagKey, HttpRequestMessage request, HttpResponseMessage response) { return () => { if (request.Method != HttpMethod.Post) { return; } // if location header is set (for newly created resource), invalidate cache for it // this normally should not be necessary as the item is new and should not be in the cache // but releasing a non-existent item from cache should not have a big overhead if (response.Headers.Location != null) { _entityTagStore.RemoveAllByRoutePattern(response.Headers.Location.ToString()); } }; }
/// <summary> /// This invalidates the resource based on routePattern /// for methods POST, PUT and DELETE. /// It also removes for all linked URLs /// </summary> /// <param name="entityTagKey"></param> /// <param name="request"></param> /// <param name="response"></param> /// <returns></returns> internal Action InvalidateCache( EntityTagKey entityTagKey, HttpRequestMessage request, HttpResponseMessage response) { return () => { if (!request.Method.Method.IsIn("PUT", "DELETE", "POST")) { return; } string uri = UriTrimmer(request.RequestUri); // remove pattern _entityTagStore.RemoveAllByRoutePattern(entityTagKey.RoutePattern); // remove all related URIs IEnumerable<string> linkedUrls = LinkedRoutePatternProvider(uri, request.Method); foreach (string linkedUrl in linkedUrls) { _entityTagStore.RemoveAllByRoutePattern(linkedUrl); } }; }
/// <summary> /// Adds caching for GET and PUT if /// cache control provided is not null /// With PUT, since cache has been alreay invalidated, /// we provide the new ETag (old one has been cleared in invalidation phase) /// </summary> /// <param name="entityTagKey"></param> /// <param name="request"></param> /// <param name="response"></param> /// <param name="varyHeaders"></param> /// <returns></returns> internal Action AddCaching( EntityTagKey entityTagKey, HttpRequestMessage request, HttpResponseMessage response, IEnumerable<KeyValuePair<string, IEnumerable<string>>> varyHeaders) { return () => { CacheControlHeaderValue cacheControlHeaderValue = CacheControlHeaderProvider(request); if (cacheControlHeaderValue == null) { return; } TimedEntityTagHeaderValue eTagValue; string uri = UriTrimmer(request.RequestUri); // in case of GET and no ETag // in case of PUT, we should return the new ETag of the resource // NOTE: No need to check if it is in the cache. If it were, it would not get // here if (request.Method == HttpMethod.Get || request.Method == HttpMethod.Put) { // create new ETag only if it does not already exist if (!_entityTagStore.TryGetValue(entityTagKey, out eTagValue)) { eTagValue = new TimedEntityTagHeaderValue(ETagValueGenerator(uri, varyHeaders)); _entityTagStore.AddOrUpdate(entityTagKey, eTagValue); } // set ETag response.Headers.ETag = eTagValue.ToEntityTagHeaderValue(); // set last-modified if (AddLastModifiedHeader && response.Content != null && !response.Content.Headers.Any(x => x.Key.Equals(HttpHeaderNames.LastModified, StringComparison.CurrentCultureIgnoreCase))) { response.Content.Headers.Add(HttpHeaderNames.LastModified, eTagValue.LastModified.ToString("r")); } // set Vary if (AddVaryHeader && _varyByHeaders != null && _varyByHeaders.Length > 0) { response.Headers.Add(HttpHeaderNames.Vary, _varyByHeaders); } response.Headers.TryAddWithoutValidation(HttpHeaderNames.CacheControl, cacheControlHeaderValue.ToString()); } }; }
public bool TryRemove(EntityTagKey key) { TimedEntityTagHeaderValue entityTagHeaderValue; return _eTagCache.TryRemove(key, out entityTagHeaderValue); }
public bool TryGetValue(EntityTagKey key, out TimedEntityTagHeaderValue eTag) { return(_eTagCache.TryGetValue(key, out eTag)); }
public bool TryRemove(EntityTagKey key) { TimedEntityTagHeaderValue entityTagHeaderValue; return(_eTagCache.TryRemove(key, out entityTagHeaderValue)); }
public bool TryGetValue(EntityTagKey key, out TimedEntityTagHeaderValue eTag) { return _eTagCache.TryGetValue(key, out eTag); }