public static void TestCacheInvalidationForPost(string method) { // setup var locationUrl = new Uri("http://api/SomeLocationUrl"); var mocks = new MockRepository(); var request = new HttpRequestMessage(new HttpMethod(method), TestUrl); string routePattern = "http://myserver/api/stuffs/*"; var entityTagStore = mocks.StrictMock<IEntityTagStore>(); var linkedUrls = new[] { "url1", "url2" }; var cachingHandler = new CachingHandler(entityTagStore) { LinkedRoutePatternProvider = (url, mthd) => linkedUrls }; var entityTagKey = new EntityTagKey(TestUrl, new string[0], routePattern); var response = new HttpResponseMessage(); response.Headers.Location = locationUrl; var invalidateCacheForPost = cachingHandler.PostInvalidationRule(entityTagKey, request, response); if(method == "POST") { entityTagStore.Expect(x => x.RemoveAllByRoutePattern(locationUrl.ToString())).Return(1); } mocks.ReplayAll(); // run invalidateCacheForPost(); // verify mocks.VerifyAll(); }
public static void TestCacheInvalidation(string method) { // setup var mocks = new MockRepository(); var request = new HttpRequestMessage(new HttpMethod(method), TestUrl); string routePattern = "http://myserver/api/stuffs/*"; var entityTagStore = mocks.StrictMock<IEntityTagStore>(); var linkedUrls = new []{"url1", "url2"}; var cachingHandler = new CachingHandler(entityTagStore) { LinkedRoutePatternProvider = (url, mthd) => linkedUrls }; var entityTagKey = new EntityTagKey(TestUrl, new string[0], routePattern); var response = new HttpResponseMessage(); var invalidateCache = cachingHandler.InvalidateCache(entityTagKey, request, response); entityTagStore.Expect(x => x.RemoveAllByRoutePattern(routePattern)).Return(1); entityTagStore.Expect(x => x.RemoveAllByRoutePattern(linkedUrls[0])).Return(0); entityTagStore.Expect(x => x.RemoveAllByRoutePattern(linkedUrls[1])).Return(0); mocks.ReplayAll(); // run invalidateCache(); // verify mocks.VerifyAll(); }
public static void TestCachingContinuation( string method, bool existsInStore, bool addVaryHeader, bool addLastModifiedHeader, bool alreadyHasLastModified, string[] varyByHeader) { // setup var mocks = new MockRepository(); var request = new HttpRequestMessage(new HttpMethod(method), TestUrl); request.Headers.Add(HttpHeaderNames.Accept, "text/xml"); request.Headers.Add(HttpHeaderNames.AcceptLanguage, "en-GB"); var entityTagStore = mocks.StrictMock<IEntityTagStore>(); var cachingHandler = new CachingHandler(entityTagStore, varyByHeader) { AddLastModifiedHeader = addLastModifiedHeader, AddVaryHeader = addVaryHeader }; var entityTagHeaderValue = new TimedEntityTagHeaderValue("\"12345678\""); var entityTagKey = new EntityTagKey(TestUrl, new string[0]); cachingHandler.EntityTagKeyGenerator = (x, y) => entityTagKey; cachingHandler.ETagValueGenerator = (x, y) => new EntityTagHeaderValue(entityTagHeaderValue.Tag); entityTagStore.Expect(x => x.TryGet(Arg<EntityTagKey>.Matches(etg => etg.ResourceUri == TestUrl), out Arg<TimedEntityTagHeaderValue>.Out(entityTagHeaderValue).Dummy)).Return(existsInStore); if (!existsInStore || request.Method == HttpMethod.Put || request.Method == HttpMethod.Post) { entityTagStore.Expect( x => x.AddOrUpdate(Arg<EntityTagKey>.Matches(etk => etk == entityTagKey), Arg<TimedEntityTagHeaderValue>.Matches(ethv => ethv.Tag == entityTagHeaderValue.Tag))); } mocks.ReplayAll(); var response = new HttpResponseMessage(); response.Content = new ByteArrayContent(new byte[0]); if(alreadyHasLastModified) response.Content.Headers.Add(HttpHeaderNames.LastModified, DateTimeOffset.Now.ToString("r")); var cachingContinuation = cachingHandler.GetCachingContinuation(request); var taskCompletionSource = new TaskCompletionSource<HttpResponseMessage>(); taskCompletionSource.SetResult(response); // run var httpResponseMessage = cachingContinuation(taskCompletionSource.Task); // test if(addLastModifiedHeader) { Assert.That(httpResponseMessage.Content.Headers.Any(x=>x.Key == HttpHeaderNames.LastModified), "LastModified does not exist"); } if(!addLastModifiedHeader && !alreadyHasLastModified) { Assert.That(!httpResponseMessage.Content.Headers.Any(x => x.Key == HttpHeaderNames.LastModified), "LastModified exists"); } }
/// <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 var linkedUrls = LinkedRoutePatternProvider(uri, request.Method); foreach (var 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 ExecuteCacheInvalidationRules(EntityTagKey entityTagKey, HttpRequestMessage request, HttpResponseMessage response) { new[] { InvalidateCache(entityTagKey, request, response), // general invalidation PostInvalidationRule(entityTagKey, request, response) } .Chain()(); }
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> /// 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 (() => { var 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); }); }
/// <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()); } }); }
public bool TryRemove(EntityTagKey key) { TimedEntityTagHeaderValue entityTagHeaderValue; return _eTagCache.TryRemove(key, out entityTagHeaderValue); }
protected void ExecuteCacheAdditionRules(EntityTagKey entityTagKey, HttpRequestMessage request, HttpResponseMessage response, IEnumerable<KeyValuePair<string, IEnumerable<string>>> varyHeaders) { new[] { AddCaching(entityTagKey, request, response, varyHeaders), // general caching } .Chain()(); }
public void AddOrUpdate(EntityTagKey key, TimedEntityTagHeaderValue eTag) { _eTagCache.AddOrUpdate(key, eTag, (theKey, oldValue) => eTag); }
public bool TryGet(EntityTagKey key, out TimedEntityTagHeaderValue eTag) { return _eTagCache.TryGetValue(key, out eTag); }
protected void ExecuteCacheInvalidationRules(EntityTagKey entityTagKey, HttpRequestMessage request, HttpResponseMessage response) { new[] { InvalidateCache(entityTagKey, request, response), // general invalidation PostInvalidationRule(entityTagKey, request, response) } .Chain()(); }
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 static void AddCaching(string method, bool existsInStore, bool addVaryHeader, bool addLastModifiedHeader, bool alreadyHasLastModified, string[] varyByHeader) { // setup var mocks = new MockRepository(); var request = new HttpRequestMessage(new HttpMethod(method), TestUrl); request.Headers.Add(HttpHeaderNames.Accept, "text/xml"); request.Headers.Add(HttpHeaderNames.AcceptLanguage, "en-GB"); var entityTagStore = mocks.StrictMock<IEntityTagStore>(); var entityTagHeaderValue = new TimedEntityTagHeaderValue("\"12345678\""); var cachingHandler = new CachingHandler(entityTagStore, varyByHeader) { AddLastModifiedHeader = addLastModifiedHeader, AddVaryHeader = addVaryHeader, ETagValueGenerator = (x,y) => entityTagHeaderValue }; var entityTagKey = new EntityTagKey(TestUrl, new[] {"text/xml", "en-GB"}, TestUrl + "/*"); entityTagStore.Expect(x => x.TryGetValue(Arg<EntityTagKey>.Matches(etg => etg.ResourceUri == TestUrl), out Arg<TimedEntityTagHeaderValue>.Out(entityTagHeaderValue).Dummy)).Return(existsInStore); if (!existsInStore) { entityTagStore.Expect( x => x.AddOrUpdate(Arg<EntityTagKey>.Matches(etk => etk == entityTagKey), Arg<TimedEntityTagHeaderValue>.Matches(ethv => ethv.Tag == entityTagHeaderValue.Tag))); } var response = new HttpResponseMessage(); response.Content = new ByteArrayContent(new byte[0]); if (alreadyHasLastModified) response.Content.Headers.Add(HttpHeaderNames.LastModified, DateTimeOffset.Now.ToString("r")); var cachingContinuation = cachingHandler.AddCaching(entityTagKey, request, response, request.Headers); mocks.ReplayAll(); // run cachingContinuation(); // verify // test kast modified only if it is GET and PUT if (addLastModifiedHeader && method.IsIn("PUT", "GET")) { Assert.That(response.Content.Headers.Any(x => x.Key == HttpHeaderNames.LastModified), "LastModified does not exist"); } if (!addLastModifiedHeader && !alreadyHasLastModified) { Assert.That(!response.Content.Headers.Any(x => x.Key == HttpHeaderNames.LastModified), "LastModified exists"); } mocks.VerifyAll(); }
/// <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> /// 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 () => { var 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()); } }; }
/// <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 var linkedUrls = LinkedRoutePatternProvider(uri, request.Method); foreach (var linkedUrl in linkedUrls) _entityTagStore.RemoveAllByRoutePattern(linkedUrl); }; }