public async Task Should_call_reborn_on_existing_phoenix_and_try_refresh_cache_when_cache_item_is_stale() { // Arrange var store = Substitute.For<IAsyncCacheStore>(); store.StoreId.Returns(1000); var objCacheItem = new WebApiCacheItem { MaxAge = 5, StaleWhileRevalidate = 5, StoreId = 1000, CreatedTime = DateTime.UtcNow.AddSeconds(-5).AddMilliseconds(-1), }; var existingPhoenix = Substitute.For<WebApiPhoenix>(_invocation, objCacheItem, _request); existingPhoenix.When(x => x.Reborn()).Do(c => { Global.Cache.PhoenixFireCage.Remove(objCacheItem.Key); }); store.GetAsync(Arg.Any<string>()).Returns(c => { objCacheItem.Key = c.Arg<string>(); Global.Cache.PhoenixFireCage[objCacheItem.Key] = existingPhoenix; return Task.FromResult((object)objCacheItem); }); Global.CacheStoreProvider.RegisterAsyncStore(store); var att = new Flatwhite.WebApi.OutputCacheAttribute { MaxAge = 5, CacheStoreId = 1000, StaleWhileRevalidate = 5 }; // Action await att.OnActionExecutingAsync(_actionContext, CancellationToken.None); // Assert existingPhoenix.Received(1).Reborn(); }
public void Should_dispose_existing_phoenix() { var key = "theCacheKey" + Guid.NewGuid(); // Arrange var objCacheItem = new WebApiCacheItem { MaxAge = 5, StaleWhileRevalidate = 5, StoreId = 1000, CreatedTime = DateTime.UtcNow.AddSeconds(-5).AddMilliseconds(-1), Key = key }; var existingPhoenix = Substitute.For<WebApiPhoenix>(_invocation, objCacheItem, _request); var att = new OutputCacheAttributeWithPublicMethods {MaxAge = 5, CacheStoreId = 1000, StaleWhileRevalidate = 5}; Global.Cache.PhoenixFireCage[key] = existingPhoenix; // Action att.CreatePhoenixPublic(_invocation, objCacheItem, _request); // Assert Assert.That(Global.Cache.PhoenixFireCage[key] is WebApiPhoenix); existingPhoenix.Received(1).Dispose(); }
public async Task FireAsync_should_send_a_request_to_original_endpoint_when_loopback_is_not_set() { // Arrange var currentCacheItem = new WebApiCacheItem { CreatedTime = DateTime.UtcNow, MaxAge = 2, StaleWhileRevalidate = 3, StaleIfError = 4, Key = "1" }; var invocation = Substitute.For<_IInvocation>(); using (var phoenix = new WebApiPhoenixWithPublicMethods(invocation, currentCacheItem, _requestMessage)) { phoenix.HttpClient = _client; // Action var state = await phoenix.FireAsyncPublic(); // Assert Assert.IsTrue(state is InActivePhoenix); await _client .Received(1) .SendAsync(Arg.Is<HttpRequestMessage>(msg => msg.Properties.Count == 0 && msg.Headers.CacheControl.Extensions.Any(e => e.Name == WebApiExtensions.__cacheControl_flatwhite_force_refresh) && msg.RequestUri.ToString() == "http://localhost/api/method/id") , HttpCompletionOption.ResponseHeadersRead); } }
private static void RefreshTheCache(WebApiCacheItem cacheItem, _IInvocation invocation, HttpRequestMessage request) { //Question: Should we create the phoenix only on the server that created it the first place? if (!Global.Cache.PhoenixFireCage.ContainsKey(cacheItem.Key)) { Global.Cache.PhoenixFireCage[cacheItem.Key] = new WebApiPhoenix(invocation, cacheItem, request); } Global.Cache.PhoenixFireCage[cacheItem.Key].Reborn(); }
private async Task SaveTheResultToCache(HttpActionExecutedContext actionExecutedContext, string storedKey, IAsyncCacheStore cacheStore) { var responseContent = actionExecutedContext.Response.Content; if (responseContent != null) { var cacheItem = new WebApiCacheItem { Key = storedKey, Content = await responseContent.ReadAsByteArrayAsync().ConfigureAwait(false), ResponseHeaders = responseContent.Headers, ResponseMediaType = responseContent.Headers.ContentType.MediaType, ResponseCharSet = responseContent.Headers.ContentType.CharSet, StoreId = cacheStore.StoreId, StaleWhileRevalidate = StaleWhileRevalidate, MaxAge = MaxAge, CreatedTime = DateTime.UtcNow, IgnoreRevalidationRequest = IgnoreRevalidationRequest, StaleIfError = StaleIfError, AutoRefresh = AutoRefresh, Path = $"{actionExecutedContext.Request.Method} {actionExecutedContext.Request.RequestUri.PathAndQuery}" }; var invocation = GetInvocation(actionExecutedContext.ActionContext); if (AutoRefresh) { // Create if not there DisposeOldPhoenixAndCreateNew(invocation, cacheItem, actionExecutedContext.Request); } if (StaleWhileRevalidate > 0) // Only auto refresh when revalidate if this value > 0, creating a Phoenix is optional { var context = GetInvocationContext(actionExecutedContext.ActionContext); var strategy = (ICacheStrategy)actionExecutedContext.Request.Properties[Global.__flatwhite_outputcache_strategy]; var changeMonitors = strategy.GetChangeMonitors(invocation, context); foreach (var mon in changeMonitors) { mon.CacheMonitorChanged += state => { TryToRefreshCacheWhileRevalidate(storedKey); }; } } actionExecutedContext.Response.Headers.ETag = new EntityTagHeaderValue($"\"{cacheItem.Key}-{cacheItem.Checksum}\""); var absoluteExpiration = DateTime.UtcNow.AddSeconds(MaxAge + Math.Max(StaleWhileRevalidate, StaleIfError)); await Task.WhenAll(new[] { //Save url base cache key that can map to the real cache key which will be used by EvaluateServerCacheHandler cacheStore.SetAsync(actionExecutedContext.Request.GetUrlBaseCacheKey(), cacheItem.Key, absoluteExpiration), //Save the actual cache cacheStore.SetAsync(cacheItem.Key, cacheItem, absoluteExpiration) }).ConfigureAwait(false); } }
private void DisposeOldPhoenixAndCreateNew(_IInvocation invocation, WebApiCacheItem cacheItem, HttpRequestMessage request) { //Question: Should we do it only on the box that created the phoenix the first place? Phoenix phoenix; if (Global.Cache.PhoenixFireCage.TryGetValue(cacheItem.Key, out phoenix)) { phoenix?.Dispose(); } Global.Cache.PhoenixFireCage[cacheItem.Key] = new WebApiPhoenix(invocation, cacheItem, request); }
/// <summary> /// Create the phoenix object which can refresh the cache itself if StaleWhileRevalidate > 0 /// </summary> /// <param name="invocation"></param> /// <param name="cacheItem"></param> /// <param name="request"></param> /// <returns></returns> private void CreatePhoenix(_IInvocation invocation, WebApiCacheItem cacheItem, HttpRequestMessage request) { if (cacheItem.StaleWhileRevalidate <= 0 || request.Method != HttpMethod.Get) { return; } if (Global.Cache.PhoenixFireCage.ContainsKey(cacheItem.Key)) { Global.Cache.PhoenixFireCage[cacheItem.Key].Dispose(); } Global.Cache.PhoenixFireCage[cacheItem.Key] = new WebApiPhoenix(invocation, cacheItem, request); }
public async Task Should_return_new_etag_if_cache_item_found_but_doesnt_match_checksum(string cacheChecksum, HttpStatusCode resultCode) { // Arrange var cacheControl = new CacheControlHeaderValue { MaxStale = true, MaxStaleLimit = TimeSpan.FromSeconds(15), MinFresh = TimeSpan.FromSeconds(20) }; var oldCacheItem = new WebApiCacheItem { CreatedTime = DateTime.UtcNow.AddSeconds(-11), MaxAge = 10, StaleWhileRevalidate = 5, IgnoreRevalidationRequest = true, ResponseCharSet = "UTF8", ResponseMediaType = "text/json", Content = new byte[0], Key = "fw-0-HASHEDKEY", Checksum = cacheChecksum }; var request = new HttpRequestMessage { Method = new HttpMethod("GET"), RequestUri = new Uri("http://localhost") }; request.Headers.Add("If-None-Match", "\"fw-0-HASHEDKEY-OLDCHECKSUM\""); var builder = new CacheResponseBuilder(); var handler = new EtagHeaderHandler(builder); await Global.CacheStoreProvider.GetAsyncCacheStore().SetAsync("fw-0-HASHEDKEY", oldCacheItem, DateTimeOffset.Now.AddDays(1)).ConfigureAwait(false); // Action Global.Cache.PhoenixFireCage["fw-0-HASHEDKEY"] = new WebApiPhoenix(NSubstitute.Substitute.For<_IInvocation>(), new CacheInfo(), oldCacheItem, request); var response = await handler.HandleAsync(cacheControl, request, CancellationToken.None).ConfigureAwait(false); Assert.AreEqual(resultCode, response.StatusCode); if (resultCode == HttpStatusCode.OK) { Assert.AreEqual($"\"fw-0-HASHEDKEY-{cacheChecksum}\"", response.Headers.ETag.Tag); } else { Assert.IsNull(response.Headers.ETag); } }
/// <summary> /// Create the phoenix object which can refresh the cache itself if StaleWhileRevalidate > 0 /// </summary> /// <param name="invocation"></param> /// <param name="cacheItem"></param> /// <param name="request"></param> /// <returns></returns> private void CreatePhoenix(_IInvocation invocation, WebApiCacheItem cacheItem, HttpRequestMessage request) { if (cacheItem.StaleWhileRevalidate <= 0 || request.Method != HttpMethod.Get) { return; } Phoenix phoenix; if (Global.Cache.PhoenixFireCage.TryGetValue(cacheItem.Key, out phoenix)) { phoenix?.Dispose(); } Global.Cache.PhoenixFireCage[cacheItem.Key] = new WebApiPhoenix(invocation, cacheItem, request); }
/// <summary> /// Initializes a WebApiPhoenix /// </summary> /// <param name="invocation"></param> /// <param name="cacheItem">This should the the WebApiCacheItem instance</param> /// <param name="requestMessage"></param> public WebApiPhoenix(_IInvocation invocation, WebApiCacheItem cacheItem, HttpRequestMessage requestMessage) : base(invocation, cacheItem) { _cacheItem = cacheItem; _clonedRequestMessage = new HttpRequestMessage { RequestUri = requestMessage.RequestUri, Method = requestMessage.Method, Version = requestMessage.Version }; if (!string.IsNullOrWhiteSpace(WebApiExtensions._fwConfig.LoopbackAddress)) { _clonedRequestMessage.RequestUri = new Uri($"{WebApiExtensions._fwConfig.LoopbackAddress}{_clonedRequestMessage.RequestUri.PathAndQuery}"); } _clonedRequestMessage.Content = null; foreach (var h in requestMessage.Headers) { _clonedRequestMessage.Headers.Add(h.Key, h.Value); } _clonedRequestMessage.Headers.CacheControl = requestMessage.Headers.CacheControl ?? new CacheControlHeaderValue(); }
public void Should_not_create_phoenix_for_http_method_not_GET() { var key = "theCacheKey" + Guid.NewGuid(); // Arrange var objCacheItem = new WebApiCacheItem { MaxAge = 5, StaleWhileRevalidate = 5, StoreId = 1000, CreatedTime = DateTime.UtcNow.AddSeconds(-5).AddMilliseconds(-1), Key = key }; _request.Method = HttpMethod.Post; var att = new OutputCacheAttributeWithPublicMethods { MaxAge = 5, CacheStoreId = 1000, StaleWhileRevalidate = 5 }; // Action att.CreatePhoenixPublic(_invocation, objCacheItem, _request); // Assert Assert.That(!Global.Cache.PhoenixFireCage.ContainsKey(key)); }
public void Should_return_304_if_etag_matched() { // Arrange var cacheControl = new CacheControlHeaderValue { MaxStale = true, MaxStaleLimit = TimeSpan.FromSeconds(15), MinFresh = TimeSpan.FromSeconds(20) }; var cacheItem = new WebApiCacheItem { CreatedTime = DateTime.UtcNow.AddSeconds(-11), MaxAge = 10, StaleWhileRevalidate = 5, IgnoreRevalidationRequest = true, ResponseCharSet = "UTF8", ResponseMediaType = "text/json", Content = new byte[0], Key = "CacheKey" + Guid.NewGuid() }; Global.Cache.PhoenixFireCage[cacheItem.Key] = new Phoenix(NSubstitute.Substitute.For<_IInvocation>(), new CacheInfo()); var request = new HttpRequestMessage(); request.Properties[WebApiExtensions.__webApi_etag_matched] = true; var svc = new CacheResponseBuilder { }; // Action var response = svc.GetResponse(cacheControl, cacheItem, request); // Assert Assert.AreEqual(HttpStatusCode.NotModified, response.StatusCode); Assert.AreEqual("Cache freshness lifetime not qualified", response.Headers.GetValues("X-Flatwhite-Warning").First()); Assert.AreEqual("Response is Stale", response.Headers.GetValues("X-Flatwhite-Warning").Last()); Assert.AreEqual($"110 - \"Response is Stale\"", response.Headers.GetValues("Warning").Last()); }
/// <summary> /// Provide a single method to try to build a <see cref="HttpResponseHeaders" /> from <see cref="CacheControlHeaderValue" /> and <see cref="HttpRequestMessage" /> /// </summary> public virtual HttpResponseMessage GetResponse(CacheControlHeaderValue cacheControl, WebApiCacheItem cacheItem, HttpRequestMessage request) { if (cacheControl != null && cacheControl.Extensions != null && cacheControl.Extensions.Any(x => x.Name == WebApiExtensions.__cacheControl_flatwhite_force_refresh) && request.IsLocal()) { return(null); } if (cacheControl != null && cacheControl.OnlyIfCached && cacheItem == null) { var errorResponse = new HttpResponseMessage { StatusCode = HttpStatusCode.GatewayTimeout }; errorResponse.Headers.Add("X-Flatwhite-Message", "no cache available"); return(errorResponse); } if (cacheItem == null) { return(null); } var ageInSeconds = cacheItem.Age; var responseCacheControl = new CacheControlHeaderValue { MaxAge = TimeSpan.FromSeconds(Math.Max(cacheItem.MaxAge - ageInSeconds, 0)), }; var cacheNotQualified = false; //http://stackoverflow.com/questions/1046966/whats-the-difference-between-cache-control-max-age-0-and-no-cache bool stale = cacheControl?.MaxAge?.TotalSeconds > 0 && cacheControl.MaxAge.Value.TotalSeconds < ageInSeconds; if (cacheItem.IsStale()) { stale = true; Global.Logger.Info($"Stale key \"{cacheItem.Key}\", age: \"{ageInSeconds}\", store: \"{cacheItem.StoreId}\", request: {request.RequestUri.PathAndQuery}"); if (cacheItem.StaleWhileRevalidate > 0 && cacheControl != null && cacheControl.MaxStale && cacheControl.MaxStaleLimit.HasValue && cacheControl.MaxStaleLimit.Value.TotalSeconds > (ageInSeconds - cacheItem.MaxAge)) { // https://tools.ietf.org/html/rfc5861 responseCacheControl.Extensions.Add(new NameValueHeaderValue("stale-while-revalidate", cacheItem.StaleWhileRevalidate.ToString())); } /* It's responsibility is building the response, it should not worry about refresh or anything * if (!Global.Cache.PhoenixFireCage.ContainsKey(cacheItem.Key)) * { * // No phoenix yet, let the OutputCacheFilter created the phoenix and call the builder again * return null; * } * * if (!cacheItem.AutoRefresh) * { * Global.Cache.PhoenixFireCage[cacheItem.Key].Reborn(); * } */ } var response = request.CreateResponse(); if (cacheControl?.MinFresh?.TotalSeconds > ageInSeconds) { response.Headers.Add("X-Flatwhite-Warning", "Cache freshness lifetime not qualified"); cacheNotQualified = true; } if ((stale || cacheNotQualified) && !cacheItem.IgnoreRevalidationRequest) { return(null); } if (stale) { request.Properties[WebApiExtensions.__webApi_cache_is_stale] = true; response.Headers.Add("X-Flatwhite-Warning", "Response is Stale"); //https://tools.ietf.org/html/rfc7234#page-31 response.Headers.Add("Warning", "110 - \"Response is Stale\""); } response.Headers.Age = TimeSpan.FromSeconds(ageInSeconds); response.Headers.CacheControl = responseCacheControl; if (request.Properties.ContainsKey(WebApiExtensions.__webApi_etag_matched)) { response.StatusCode = HttpStatusCode.NotModified; } else { response.StatusCode = HttpStatusCode.OK; response.Content = new ByteArrayContent(cacheItem.Content); response.Content.Headers.ContentType = new MediaTypeHeaderValue(cacheItem.ResponseMediaType) { CharSet = cacheItem.ResponseCharSet }; response.Headers.ETag = new EntityTagHeaderValue($"\"{cacheItem.Key}-{cacheItem.Checksum}\""); } return(response); }
public void Should_return_null_if_stale() { // Arrange var cacheControl = new CacheControlHeaderValue { MaxStale = true, MaxStaleLimit = TimeSpan.FromSeconds(15) }; var cacheItem = new WebApiCacheItem { CreatedTime = DateTime.UtcNow.AddSeconds(-11), MaxAge = 10, StaleWhileRevalidate = 5, IgnoreRevalidationRequest = false, ResponseCharSet = "UTF8", ResponseMediaType = "text/json", Content = new byte[0], Key = "CacheKey" + Guid.NewGuid() }; Global.Cache.PhoenixFireCage[cacheItem.Key] = new Phoenix(NSubstitute.Substitute.For<_IInvocation>(), new CacheInfo()); var request = new HttpRequestMessage(); var svc = new CacheResponseBuilder { }; // Action var response = svc.GetResponse(cacheControl, cacheItem, request); // Assert Assert.IsNull(response); }
/// <summary> /// Store the response to cache store, add CacheControl and Etag to response /// </summary> /// <param name="actionExecutedContext"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken) { if (ShouldIgnoreCache(actionExecutedContext.Request.Headers.CacheControl, actionExecutedContext.Request)) { return; } if ((actionExecutedContext.ActionContext.Response == null || !actionExecutedContext.ActionContext.Response.IsSuccessStatusCode) && StaleIfError == 0) { return; // Early return } var cacheControl = actionExecutedContext.Request.Headers.CacheControl; if (cacheControl?.Extensions != null && cacheControl.Extensions.Any(x => x.Name == WebApiExtensions.__cacheControl_flatwhite_force_refresh)) { var entry = cacheControl.Extensions.First(x => x.Name == WebApiExtensions.__cacheControl_flatwhite_force_refresh); cacheControl.Extensions.Remove(entry); } ApplyCacheHeaders(actionExecutedContext.ActionContext.Response, actionExecutedContext.Request); var storedKey = (string)actionExecutedContext.Request.Properties[Global.__flatwhite_outputcache_key]; var cacheStore = (IAsyncCacheStore)actionExecutedContext.Request.Properties[Global.__flatwhite_outputcache_store]; if (actionExecutedContext.ActionContext.Response == null || !actionExecutedContext.ActionContext.Response.IsSuccessStatusCode) { var cacheItem = await cacheStore.GetAsync(storedKey).ConfigureAwait(false) as WebApiCacheItem; if (cacheItem != null && StaleIfError > 0) { var builder = (ICacheResponseBuilder)actionExecutedContext.Request.Properties[WebApiExtensions.__webApi_outputcache_response_builder]; var response = builder.GetResponse(actionExecutedContext.Request.Headers.CacheControl, cacheItem, actionExecutedContext.Request); if (response != null) { //NOTE: Override error response actionExecutedContext.Response = response; } return; } } var responseContent = actionExecutedContext.Response.Content; if (responseContent != null) { var cacheItem = new WebApiCacheItem { Key = storedKey, Content = await responseContent.ReadAsByteArrayAsync().ConfigureAwait(false), ResponseMediaType = responseContent.Headers.ContentType.MediaType, ResponseCharSet = responseContent.Headers.ContentType.CharSet, StoreId = cacheStore.StoreId, StaleWhileRevalidate = StaleWhileRevalidate, MaxAge = MaxAge, CreatedTime = DateTime.UtcNow, IgnoreRevalidationRequest = IgnoreRevalidationRequest, StaleIfError = StaleIfError, AutoRefresh = AutoRefresh }; var strategy = (ICacheStrategy)actionExecutedContext.Request.Properties[Global.__flatwhite_outputcache_strategy]; var invocation = GetInvocation(actionExecutedContext.ActionContext); var context = GetInvocationContext(actionExecutedContext.ActionContext); var changeMonitors = strategy.GetChangeMonitors(invocation, context); CreatePhoenix(invocation, cacheItem, actionExecutedContext.Request); foreach (var mon in changeMonitors) { mon.CacheMonitorChanged += state => { RefreshCache(storedKey); }; } actionExecutedContext.Response.Headers.ETag = new EntityTagHeaderValue($"\"{cacheItem.Key}-{cacheItem.Checksum}\""); var absoluteExpiration = DateTime.UtcNow.AddSeconds(MaxAge + Math.Max(StaleWhileRevalidate, StaleIfError)); await cacheStore.SetAsync(cacheItem.Key, cacheItem, absoluteExpiration).ConfigureAwait(false); } }
/// <summary> /// Initializes a WebApiPhoenix /// </summary> /// <param name="invocation"></param> /// <param name="cacheItem">This should the the WebApiCacheItem instance</param> /// <param name="originalRequestMessage"></param> public WebApiPhoenix(_IInvocation invocation, WebApiCacheItem cacheItem, HttpRequestMessage originalRequestMessage) : base(invocation, cacheItem) { _cacheItem = cacheItem; _originalRequestMessage = originalRequestMessage; }
/// <summary> /// Provide a single method to try to build a <see cref="HttpResponseHeaders" /> from <see cref="CacheControlHeaderValue" /> and <see cref="HttpRequestMessage" /> /// </summary> public virtual HttpResponseMessage GetResponse(CacheControlHeaderValue cacheControl, WebApiCacheItem cacheItem, HttpRequestMessage request) { if (cacheControl != null && cacheControl.OnlyIfCached && cacheItem == null) { var errorResponse = new HttpResponseMessage {StatusCode = HttpStatusCode.GatewayTimeout}; errorResponse.Headers.Add("X-Flatwhite-Message", "no cache available"); return errorResponse; } if (cacheItem == null) { return null; } var age = cacheItem.Age; var responseCacheControl = new CacheControlHeaderValue { MaxAge = TimeSpan.FromSeconds(Math.Max(cacheItem.MaxAge - age, 0)), }; var cacheNotQualified = false; bool stale = cacheControl?.MaxAge?.TotalSeconds > 0 && cacheControl.MaxAge.Value.TotalSeconds < age; if (cacheItem.MaxAge < age) { stale = true; if (cacheItem.StaleWhileRevalidate > 0 && cacheControl != null && cacheControl.MaxStale && cacheControl.MaxStaleLimit.HasValue && cacheControl.MaxStaleLimit.Value.TotalSeconds > (age - cacheItem.MaxAge)) { // https://tools.ietf.org/html/rfc5861 responseCacheControl.Extensions.Add(new NameValueHeaderValue("stale-while-revalidate", cacheItem.StaleWhileRevalidate.ToString())); } if (!Global.Cache.PhoenixFireCage.ContainsKey(cacheItem.Key)) { // No phoenix yet, let the OutputCacheFilter created the phoenix and call the builder again return null; } if (!cacheItem.AutoRefresh) { Global.Cache.PhoenixFireCage[cacheItem.Key].Reborn(); } } var response = request.CreateResponse(); if (cacheControl?.MinFresh?.TotalSeconds > age) { response.Headers.Add("X-Flatwhite-Warning", "Cache freshness lifetime not qualified"); cacheNotQualified = true; } if ((stale || cacheNotQualified) && !cacheItem.IgnoreRevalidationRequest) { return null; } if (stale) { request.Properties[WebApiExtensions.__webApi_cache_is_stale] = true; response.Headers.Add("X-Flatwhite-Warning", "Response is Stale"); //https://tools.ietf.org/html/rfc7234#page-31 response.Headers.Add("Warning", $"110 - \"Response is Stale\""); } response.Headers.Age = TimeSpan.FromSeconds(age); response.Headers.CacheControl = responseCacheControl; if (request.Properties.ContainsKey(WebApiExtensions.__webApi_etag_matched)) { response.StatusCode = HttpStatusCode.NotModified; } else { response.StatusCode = HttpStatusCode.OK; response.Content = new ByteArrayContent(cacheItem.Content); response.Content.Headers.ContentType = new MediaTypeHeaderValue(cacheItem.ResponseMediaType) { CharSet = cacheItem.ResponseCharSet }; response.Headers.ETag = new EntityTagHeaderValue($"\"{cacheItem.Key}-{cacheItem.Checksum}\""); } return response; }
public void CreatePhoenixPublic(_IInvocation invocation, WebApiCacheItem cacheItem, HttpRequestMessage request) { var methodInfo = typeof(Flatwhite.WebApi.OutputCacheAttribute).GetMethod("CreatePhoenix", BindingFlags.Instance | BindingFlags.NonPublic); methodInfo.Invoke(this, new object[] { invocation, cacheItem, request }); }
public async Task Should_create_phoenix_and_try_refresh_cache_when_cache_item_is_stale() { // Arrange var store = Substitute.For<IAsyncCacheStore>(); store.StoreId.Returns(1000); var objCacheItem = new WebApiCacheItem { MaxAge = 5, StaleWhileRevalidate = 5, StoreId = 1000, CreatedTime = DateTime.UtcNow.AddSeconds(-5).AddMilliseconds(-1), }; store.GetAsync(Arg.Any<string>()).Returns(c => { objCacheItem.Key = c.Arg<string>(); return Task.FromResult((object)objCacheItem); }); Global.CacheStoreProvider.RegisterAsyncStore(store); var att = new Flatwhite.WebApi.OutputCacheAttribute { MaxAge = 5, CacheStoreId = 1000, StaleWhileRevalidate = 5 }; // Action await att.OnActionExecutingAsync(_actionContext, CancellationToken.None); // Assert Assert.IsTrue(Global.Cache.PhoenixFireCage.ContainsKey(objCacheItem.Key)); }
public void should_execute_the_controller_method_and_return_CacheItem(string actionMethodName, int contentLength) { // Arrange var currentCacheItem = new WebApiCacheItem(); var invocation = Substitute.For<_IInvocation>(); invocation.Arguments.Returns(new object[0]); invocation.Method.Returns(controllerType.GetMethod(actionMethodName, BindingFlags.Instance | BindingFlags.Public)); var phoenix = new WebApiPhoenix(invocation, CacheInfo, currentCacheItem, new HttpRequestMessage(), new JsonMediaTypeFormatter()); // Action MethodInfo dynMethod = typeof(WebApiPhoenix).GetMethod("InvokeAndGetBareResult", BindingFlags.NonPublic | BindingFlags.Instance); var result = dynMethod.Invoke(phoenix, new object[] { _controllerIntance }); dynMethod = typeof(WebApiPhoenix).GetMethod("GetCacheItem", BindingFlags.NonPublic | BindingFlags.Instance); var cacheItem = (WebApiCacheItem)dynMethod.Invoke(phoenix, new[] { result }); // Assert if (result == null) { Assert.IsTrue(actionMethodName == "Void" || actionMethodName == "VoidAsync"); } else { Assert.AreEqual(contentLength, cacheItem.Content.Length); } }
public void Should_return_null_if_cache_not_mature_as_min_fresh_request() { // Arrange var cacheControl = new CacheControlHeaderValue { MinFresh = TimeSpan.FromSeconds(100) }; var cacheItem = new WebApiCacheItem { CreatedTime = DateTime.UtcNow.AddSeconds(-20), MaxAge = 1000, StaleWhileRevalidate = 5, IgnoreRevalidationRequest = false }; var request = new HttpRequestMessage(); var svc = new CacheResponseBuilder { }; // Action var response = svc.GetResponse(cacheControl, cacheItem, request); // Assert Assert.IsNull(response); }
public WebApiPhoenixWithPublicMethods(_IInvocation invocation, WebApiCacheItem cacheItem, HttpRequestMessage originalRequestMessage) : base(invocation, cacheItem, originalRequestMessage) { }
/// <summary> /// Create the phoenix object which can refresh the cache itself if StaleWhileRevalidate > 0 /// </summary> /// <param name="invocation"></param> /// <param name="cacheItem"></param> /// <param name="request"></param> /// <param name="mediaTypeFormatter">The formater that was used to create the reasponse at the first invocation</param> /// <returns></returns> private void CreatePhoenix(_IInvocation invocation, WebApiCacheItem cacheItem, HttpRequestMessage request, MediaTypeFormatter mediaTypeFormatter) { var cacheInfo = new CacheInfo { CacheKey = cacheItem.Key, CacheStoreId = cacheItem.StoreId, CacheDuration = MaxAge, StaleWhileRevalidate = StaleWhileRevalidate, AutoRefresh = AutoRefresh }; var phoenix = new WebApiPhoenix(invocation, cacheInfo, cacheItem, request, mediaTypeFormatter); if (Global.Cache.PhoenixFireCage.ContainsKey(cacheItem.Key)) { Global.Cache.PhoenixFireCage[cacheItem.Key].Dispose(); } Global.Cache.PhoenixFireCage[cacheItem.Key] = phoenix; }
public async Task Phoenix_action_should_display_all_phoenix_in_cache() { Global.Cache.PhoenixFireCage.Add("item1", Substitute.For<Phoenix>(Substitute.For<_IInvocation>(), new CacheItem { Data = "data", Key = "item1" })); Global.Cache.PhoenixFireCage.Add("item2", Substitute.For<Phoenix>(Substitute.For<_IInvocation>(), new CacheItem { Data = new MediaTypeHeaderValue("text/json"), Key = "item2" })); Global.Cache.PhoenixFireCage.Add("item5", Substitute.For<Phoenix>(Substitute.For<_IInvocation>(), new CacheItem())); Global.Cache.PhoenixFireCage.Add("item6", Substitute.For<Phoenix>(Substitute.For<_IInvocation>(), new CacheItem())); var syncStore = Substitute.For<ICacheStore>(); syncStore.Get(Arg.Any<string>()).Returns(c => new CacheItem { Key = c.Arg<string>(), Data = "data" }); var asyncStore = Substitute.For<IAsyncCacheStore>(); asyncStore.GetAsync(Arg.Any<string>()).Returns(c => { object obj = new WebApiCacheItem { Key = c.Arg<string>(), Content = new byte[0] }; return Task.FromResult(obj); }); var provider = Substitute.For<ICacheStoreProvider>(); provider.GetCacheStore(Arg.Any<int>()).Returns(syncStore); provider.GetAsyncCacheStore(Arg.Any<int>()).Returns(asyncStore); var controller = new FlatwhiteStatusController(provider); // Action var result = await controller.Phoenix(); var jsonResult = (JsonResult<List<FlatwhiteStatusController.CacheItemStatus>>)result; // Assert Assert.AreEqual(4, jsonResult.Content.Count); }