public void Get_Must_Revalidate_Etag_NotModified() { // setup var request = new HttpRequestMessage(HttpMethod.Get, DummyUrl); var responseFromCache = GetOkMessage(true); responseFromCache.Headers.ETag = new EntityTagHeaderValue(ETagValue); responseFromCache.Content.Headers.Expires = DateTime.Now.Subtract(TimeSpan.FromSeconds(10)); var responseFromServer = new HttpResponseMessage(HttpStatusCode.NotModified); _messageHandler.Response = responseFromServer; _cacheStore.Setup(x => x.GetValueAsync(It.IsAny <CacheKey>())).ReturnsAsync(responseFromCache); _cacheStore.Setup(x => x.AddOrUpdateAsync(It.IsAny <CacheKey>(), It.IsAny <HttpResponseMessage>())) .Returns(Task.FromResult(true)); // run var task = _client.SendAsync(request); var responseReturned = task.Result; var header = responseReturned.Headers.Single(x => x.Key == CacheCowHeader.Name); CacheCowHeader cacheCowHeader = null; CacheCowHeader.TryParse(header.Value.First(), out cacheCowHeader); // verify Assert.NotNull(cacheCowHeader); Assert.Equal(ETagValue, request.Headers.IfNoneMatch.First().Tag); Assert.Equal(responseFromCache, responseReturned); Assert.Equal(true, cacheCowHeader.CacheValidationApplied); }
public void Get_Stale_ApplyValidation_NotModified() { // setup var request = new HttpRequestMessage(HttpMethod.Get, DummyUrl); var responseFromCache = GetOkMessage(); responseFromCache.Content.Headers.Expires = DateTimeOffset.Now.AddDays(-1); responseFromCache.Content.Headers.LastModified = DateTimeOffset.Now.AddDays(-2); var responseFromServer = GetOkMessage(); responseFromServer.StatusCode = HttpStatusCode.NotModified; _messageHandler.Response = responseFromServer; _cacheStore.Expect(x => x.TryGetValue(Arg <CacheKey> .Is.Anything, out Arg <HttpResponseMessage> .Out(responseFromCache).Dummy)).Return(true); _mockRepository.ReplayAll(); // run var task = _client.SendAsync(request); var responseReturned = task.Result; var header = responseReturned.Headers.Single(x => x.Key == CacheCowHeader.Name); CacheCowHeader cacheCowHeader = null; CacheCowHeader.TryParse(header.Value.First(), out cacheCowHeader); // verify _mockRepository.VerifyAll(); Assert.IsNotNull(cacheCowHeader); Assert.AreSame(responseFromCache, responseReturned); Assert.AreEqual(true, cacheCowHeader.WasStale); Assert.AreEqual(true, cacheCowHeader.CacheValidationApplied); }
/// <summary> /// Needs to run before action runs /// </summary> /// <param name="context"></param> /// <returns>Whether continue or not. If false then the action MUST not run</returns> public async Task <bool> Before(HttpContext context) { _cacheCowHeader = new CacheCowHeader(); _cacheValidated = null; _isRequestCacheable = _validator.IsCacheable(context.Request); _cacheValidationStatus = context.Request.GetCacheValidationStatus(); if (_cacheValidationStatus != CacheValidationStatus.None) { var timedETag = await _cacheDirectiveProvider.QueryAsync(context); _cacheCowHeader.QueryMadeAndSuccessful = timedETag != null; _cacheValidated = ApplyCacheValidation(timedETag, _cacheValidationStatus, context); _cacheCowHeader.ValidationApplied = true; if (_cacheValidated ?? false) { _cacheCowHeader.ShortCircuited = true; _cacheCowHeader.ValidationMatched = HttpMethods.IsGet(context.Request.Method); // NOTE: In GET match result in short-circuit and in PUT the opposite context.Response.Headers.Add(CacheCowHeader.Name, _cacheCowHeader.ToString()); // the response would have been set and no need to run the rest of the pipeline return(false); } } _stream = context.Response.Body; context.Response.Body = new MemoryStream(); return(true); }
public void Get_NoMustRevalidate_NoMustRevalidateByDefault_Expires_GetFromCache() { // setup var request = new HttpRequestMessage(HttpMethod.Get, DummyUrl); var lastModified = DateTimeOffset.UtcNow.AddHours(-1); lastModified = lastModified.AddMilliseconds(1000 - lastModified.Millisecond); var responseFromCache = GetOkMessage(false); // NOTE !! _cachingHandler.MustRevalidateByDefault = false; // NOTE!! responseFromCache.Content.Headers.LastModified = lastModified; var responseFromServer = GetOkMessage(); responseFromCache.Content.Headers.Expires = DateTime.Now.Subtract(TimeSpan.FromSeconds(10)); _messageHandler.Response = responseFromServer; _cacheStore.Setup(x => x.GetValueAsync(It.IsAny <CacheKey>())).ReturnsAsync(responseFromCache); // run var task = _client.SendAsync(request); var responseReturned = task.Result; var header = responseReturned.Headers.Single(x => x.Key == CacheCowHeader.Name); CacheCowHeader cacheCowHeader = null; CacheCowHeader.TryParse(header.Value.First(), out cacheCowHeader); // verify Assert.NotNull(cacheCowHeader); Assert.Equal(responseFromCache, responseReturned); Assert.Equal(true, cacheCowHeader.WasStale); }
public void Get_OK_But_Not_In_Cache_To_Insert_In_Cache() { // setup var request = new HttpRequestMessage(HttpMethod.Get, DummyUrl); var response = GetOkMessage(); _messageHandler.Response = response; _cacheStore.Expect(x => x.TryGetValue(Arg <CacheKey> .Is.Anything, out Arg <HttpResponseMessage> .Out(null).Dummy)).Return(false); _cacheStore.Expect(x => x.AddOrUpdate(Arg <CacheKey> .Is.Anything, Arg <HttpResponseMessage> .Is.Same(response))); _mockRepository.ReplayAll(); // run var task = _client.SendAsync(request); var responseReturned = task.Result; var header = responseReturned.Headers.Single(x => x.Key == CacheCowHeader.Name); CacheCowHeader cacheCowHeader = null; CacheCowHeader.TryParse(header.Value.First(), out cacheCowHeader); // verify _mockRepository.VerifyAll(); Assert.IsNotNull(cacheCowHeader); Assert.AreEqual(true, cacheCowHeader.DidNotExist); }
public void Get_Must_Revalidate_Etag_NotModified() { // setup var request = new HttpRequestMessage(HttpMethod.Get, DummyUrl); var responseFromCache = GetOkMessage(true); responseFromCache.Headers.ETag = new EntityTagHeaderValue(ETagValue); responseFromCache.Content.Headers.Expires = DateTime.Now.Subtract(TimeSpan.FromSeconds(10)); var responseFromServer = new HttpResponseMessage(HttpStatusCode.NotModified); _messageHandler.Response = responseFromServer; _cacheStore.Expect(x => x.TryGetValue(Arg <CacheKey> .Is.Anything, out Arg <HttpResponseMessage> .Out(responseFromCache).Dummy)).Return(true); _mockRepository.ReplayAll(); // run var task = _client.SendAsync(request); var responseReturned = task.Result; var header = responseReturned.Headers.Single(x => x.Key == CacheCowHeader.Name); CacheCowHeader cacheCowHeader = null; CacheCowHeader.TryParse(header.Value.First(), out cacheCowHeader); // verify _mockRepository.VerifyAll(); Assert.IsNotNull(cacheCowHeader); Assert.AreEqual(ETagValue, request.Headers.IfNoneMatch.First().Tag); Assert.AreSame(responseFromCache, responseReturned); Assert.AreEqual(true, cacheCowHeader.CacheValidationApplied); }
public void Get_Must_Revalidate_NoCache_InRequest() { // setup var request = new HttpRequestMessage(HttpMethod.Get, DummyUrl); request.Headers.CacheControl = new CacheControlHeaderValue { NoCache = true }; var responseFromCache = GetOkMessage(); var responseFromServer = GetOkMessage(); _messageHandler.Response = responseFromServer; _cacheStore.Setup(x => x.GetValueAsync(It.IsAny <CacheKey>())).ReturnsAsync(responseFromCache); _cacheStore.Setup(x => x.AddOrUpdateAsync(It.IsAny <CacheKey>(), It.Is <HttpResponseMessage>(r => r == responseFromServer))).Returns(Task.FromResult(true)); // run var task = _client.SendAsync(request); var responseReturned = task.Result; var header = responseReturned.Headers.Single(x => x.Key == CacheCowHeader.Name); CacheCowHeader cacheCowHeader = null; CacheCowHeader.TryParse(header.Value.First(), out cacheCowHeader); // verify Assert.NotNull(cacheCowHeader); Assert.Equal(responseFromServer, responseReturned); Assert.Equal(true, cacheCowHeader.CacheValidationApplied); }
public void Get_Must_Revalidate_Expires_NotModified() { // setup var request = new HttpRequestMessage(HttpMethod.Get, DummyUrl); var lastModified = DateTimeOffset.UtcNow.AddHours(-1); lastModified = lastModified.AddMilliseconds(1000 - lastModified.Millisecond); var responseFromCache = GetOkMessage(true); responseFromCache.Content.Headers.LastModified = lastModified; responseFromCache.Content.Headers.Expires = DateTime.Now.Subtract(TimeSpan.FromSeconds(10)); var responseFromServer = new HttpResponseMessage(HttpStatusCode.NotModified); _messageHandler.Response = responseFromServer; _cacheStore.Expect(x => x.TryGetValue(Arg <CacheKey> .Is.Anything, out Arg <HttpResponseMessage> .Out(responseFromCache).Dummy)).Return(true); _cacheStore.Expect(x => x.AddOrUpdate(Arg <CacheKey> .Is.Anything, Arg <HttpResponseMessage> .Is.Anything)); _mockRepository.ReplayAll(); // run var task = _client.SendAsync(request); var responseReturned = task.Result; var header = responseReturned.Headers.Single(x => x.Key == CacheCowHeader.Name); CacheCowHeader cacheCowHeader = null; CacheCowHeader.TryParse(header.Value.First(), out cacheCowHeader); // verify _mockRepository.VerifyAll(); Assert.IsNotNull(cacheCowHeader); Assert.AreEqual(lastModified.ToString(), request.Headers.IfModifiedSince.Value.ToString()); Assert.AreSame(responseFromCache, responseReturned); Assert.AreEqual(true, cacheCowHeader.CacheValidationApplied); }
public async override Task OnActionExecutingAsync(HttpActionContext context, CancellationToken cancellationToken) { await base.OnActionExecutingAsync(context, cancellationToken); var cacheabilityValidator = (ICacheabilityValidator)context.ControllerContext.Configuration.DependencyResolver.GetService(typeof(ICacheabilityValidator)) ?? new DefaultCacheabilityValidator(); var cacheDirectiveProvider = context.ControllerContext.Configuration.DependencyResolver.GetCacheDirectiveProvider(ViewModelType); var cacheCowHeader = new CacheCowHeader(); context.Request.Properties[CacheCowHeaderKey] = cacheCowHeader; bool?cacheValidated = null; bool isRequestCacheable = cacheabilityValidator.IsCacheable(context.Request); var cacheValidationStatus = context.Request.GetCacheValidationStatus(); if (cacheValidationStatus != CacheValidationStatus.None) { var timedETag = await cacheDirectiveProvider.QueryAsync(context); cacheCowHeader.QueryMadeAndSuccessful = timedETag != null; cacheValidated = ApplyCacheValidation(timedETag, cacheValidationStatus, new ContextUnifier(context)); context.Request.Properties.Add(CacheValidatedKey, cacheValidated); cacheCowHeader.ValidationApplied = true; if (cacheValidated ?? false) { cacheCowHeader.ShortCircuited = true; cacheCowHeader.ValidationMatched = HttpMethod.Get == context.Request.Method; // NOTE: In GET match result in short-circuit and in PUT the opposite context.Response.Headers.Add(CacheCowHeader.Name, cacheCowHeader.ToString()); // the response would have been set and no need to run the rest of the pipeline return; } } }
public void Get_NotModified_With_Stale_Client_Cache_Shall_Update_Date_Header() { // setup var request = new HttpRequestMessage(HttpMethod.Get, DummyUrl); var responseFromCache = GetOkMessage(false); responseFromCache.Headers.Date = DateTimeOffset.UtcNow.AddHours(-1); responseFromCache.Headers.CacheControl.MaxAge = TimeSpan.FromSeconds(10); var responseFromServer = new HttpResponseMessage(HttpStatusCode.NotModified) { Content = new ByteArrayContent(new byte[256]) }; _messageHandler.Response = responseFromServer; _cacheStore.Setup(x => x.GetValueAsync(It.IsAny <CacheKey>())).ReturnsAsync(responseFromCache); _cacheStore.Setup(x => x.AddOrUpdateAsync(It.IsAny <CacheKey>(), It.Is <HttpResponseMessage>(r => DateTimeOffset.UtcNow - r.Headers.Date.Value <= TimeSpan.FromSeconds(1)))) .Returns(Task.FromResult(0)); // run var responseReturned = _client.SendAsync(request).Result; var header = responseReturned.Headers.Single(x => x.Key == CacheCowHeader.Name); CacheCowHeader cacheCowHeader; CacheCowHeader.TryParse(header.Value.First(), out cacheCowHeader); // verify Assert.NotNull(cacheCowHeader); Assert.Equal(true, cacheCowHeader.CacheValidationApplied); Assert.Equal(true, cacheCowHeader.WasStale); }
public void Get_Must_Revalidate_Expires_Modified() { // setup var request = new HttpRequestMessage(HttpMethod.Get, DummyUrl); var lastModified = DateTimeOffset.UtcNow.AddHours(-1); lastModified = lastModified.AddMilliseconds(1000 - lastModified.Millisecond); var responseFromCache = GetOkMessage(true); responseFromCache.Content.Headers.LastModified = lastModified; var responseFromServer = GetOkMessage(); responseFromCache.Content.Headers.Expires = DateTime.Now.Subtract(TimeSpan.FromSeconds(10)); _messageHandler.Response = responseFromServer; _cacheStore.Setup(x => x.GetValueAsync(It.IsAny <CacheKey>())).ReturnsAsync(responseFromCache); _cacheStore.Setup(x => x.AddOrUpdateAsync(It.IsAny <CacheKey>(), It.Is <HttpResponseMessage>(r => r == responseFromServer))).Returns(Task.FromResult(true)); // run var task = _client.SendAsync(request); var responseReturned = task.Result; var header = responseReturned.Headers.Single(x => x.Key == CacheCowHeader.Name); CacheCowHeader cacheCowHeader = null; CacheCowHeader.TryParse(header.Value.First(), out cacheCowHeader); // verify Assert.NotNull(cacheCowHeader); Assert.Equal(responseFromServer, responseReturned); Assert.Equal(true, cacheCowHeader.CacheValidationApplied); }
public void ParseTest_Successful() { CacheCowHeader header; var result = CacheCowHeader.TryParse("1.0;was-stale=true;not-cacheable=false;retrieved-from-cache=true;", out header); Assert.AreEqual(true, result); Assert.AreEqual("1.0", header.Version); Assert.AreEqual(true, header.WasStale); Assert.AreEqual(true, header.RetrievedFromCache); Assert.AreEqual(false, header.NotCacheable); Assert.AreEqual(null, header.DidNotExist); Assert.AreEqual(null, header.CacheValidationApplied); }
public static HttpResponseMessage AddCacheCowHeader(this HttpResponseMessage response, CacheCowHeader header) { var previousCacheCowHeader = response.Headers.GetCacheCowHeader(); if (previousCacheCowHeader != null) { TraceWriter.WriteLine("WARNING: Already had this header: {0} NOw setting this: {1}", TraceLevel.Warning, previousCacheCowHeader, header); response.Headers.Remove(CacheCowHeader.Name); } response.Headers.Add(CacheCowHeader.Name, header.ToString()); return(response); }
public void ToStringTest_Successful() { var cacheCowHeader = new CacheCowHeader() { CacheValidationApplied = true, DidNotExist = false }; var s = cacheCowHeader.ToString(); Console.WriteLine(s); Assert.IsTrue(s.StartsWith(_version)); Assert.IsTrue(s.IndexOf(CacheCowHeader.ExtensionNames.CacheValidationApplied + "=true") > 0); Assert.IsTrue(s.IndexOf(CacheCowHeader.ExtensionNames.DidNotExist + "=false") > 0); }
private static void DoCacheValidationForGet(HttpRequestMessage request, CacheCowHeader cacheCowHeader, HttpResponseMessage cachedResponse) { cacheCowHeader.CacheValidationApplied = true; cacheCowHeader.WasStale = true; // add headers for a cache validation. First check ETag since is better if (cachedResponse.Headers.ETag != null) { request.Headers.Add(HttpHeaderNames.IfNoneMatch, cachedResponse.Headers.ETag.ToString()); } else if (cachedResponse.Content.Headers.LastModified != null) { request.Headers.Add(HttpHeaderNames.IfModifiedSince, cachedResponse.Content.Headers.LastModified.Value.ToString("r")); } }
public void Get_NotModified_With_Stale_Client_Cache_Shall_Update_Date_Header() { // setup var request = new HttpRequestMessage(HttpMethod.Get, DummyUrl); var responseFromCache = GetOkMessage(false); responseFromCache.Headers.Date = DateTimeOffset.UtcNow.AddHours(-1); responseFromCache.Headers.CacheControl.MaxAge = TimeSpan.FromSeconds(10); var responseFromServer = new HttpResponseMessage(HttpStatusCode.NotModified) { Content = new ByteArrayContent(new byte[256]) }; _messageHandler.Response = responseFromServer; _cacheStore.Expect( x => x.TryGetValue(Arg <CacheKey> .Is.Anything, out Arg <HttpResponseMessage> .Out(responseFromCache).Dummy)) .Return(true); _cacheStore.Expect( x => x.AddOrUpdate(Arg <CacheKey> .Is.Anything, Arg <HttpResponseMessage> .Matches( r => DateTimeOffset.UtcNow - r.Headers.Date.Value <= TimeSpan.FromSeconds(1)))); _mockRepository.ReplayAll(); // run var task = _client.SendAsync(request); var responseReturned = task.Result; var header = responseReturned.Headers.Single(x => x.Key == CacheCowHeader.Name); CacheCowHeader cacheCowHeader; CacheCowHeader.TryParse(header.Value.First(), out cacheCowHeader); // verify _mockRepository.VerifyAll(); Assert.IsNotNull(cacheCowHeader); Assert.AreEqual(true, cacheCowHeader.CacheValidationApplied); Assert.AreEqual(true, cacheCowHeader.WasStale); }
private void DoPutValidation(HttpRequestMessage request, CacheCowHeader cacheCowHeader, HttpResponseMessage cachedResponse) { // add headers for a cache validation. First check ETag since is better if (UseConditionalPut) { cacheCowHeader.CacheValidationApplied = true; if (cachedResponse.Headers.ETag != null) { request.Headers.Add(HttpHeaderNames.IfMatch, cachedResponse.Headers.ETag.ToString()); } else if (cachedResponse.Content.Headers.LastModified != null) { request.Headers.Add(HttpHeaderNames.IfUnmodifiedSince, cachedResponse.Content.Headers.LastModified.Value.ToString("r")); } } }
public void Get_Stale_And_In_Cache_To_Get_From_Cache() { // setup var request = new HttpRequestMessage(HttpMethod.Get, DummyUrl); var response = GetOkMessage(); _messageHandler.Response = response; _cacheStore.Setup(x => x.GetValueAsync(It.IsAny <CacheKey>())).ReturnsAsync(response); // run var task = _client.SendAsync(request); var responseReturned = task.Result; var header = responseReturned.Headers.Single(x => x.Key == CacheCowHeader.Name); CacheCowHeader cacheCowHeader = null; CacheCowHeader.TryParse(header.Value.First(), out cacheCowHeader); // verify Assert.NotNull(cacheCowHeader); Assert.Equal(response, responseReturned); Assert.Equal(true, cacheCowHeader.RetrievedFromCache); }
public void Get_OK_But_Not_In_Cache_To_Insert_In_Cache() { // setup var request = new HttpRequestMessage(HttpMethod.Get, DummyUrl); var response = GetOkMessage(); _messageHandler.Response = response; _cacheStore.Setup(x => x.GetValueAsync(It.IsAny <CacheKey>())).ReturnsAsync((HttpResponseMessage)null); _cacheStore.Setup(x => x.AddOrUpdateAsync(It.IsAny <CacheKey>(), It.Is <HttpResponseMessage>(y => y == response))).Returns(Task.FromResult(response)); // run var task = _client.SendAsync(request); var responseReturned = task.Result; var header = responseReturned.Headers.Single(x => x.Key == CacheCowHeader.Name); CacheCowHeader cacheCowHeader = null; CacheCowHeader.TryParse(header.Value.First(), out cacheCowHeader); // verify Assert.NotNull(cacheCowHeader); Assert.Equal(true, cacheCowHeader.DidNotExist); }
public void Get_Stale_ApplyValidation_NotModified() { // setup var request = new HttpRequestMessage(HttpMethod.Get, DummyUrl); var responseFromCache = GetOkMessage(); var then = DateTimeOffset.UtcNow.AddMilliseconds(-1); responseFromCache.Headers.Date = then; responseFromCache.Content.Headers.Expires = DateTimeOffset.Now.AddDays(-1); responseFromCache.Content.Headers.LastModified = DateTimeOffset.Now.AddDays(-2); var responseFromServer = GetOkMessage(); responseFromServer.StatusCode = HttpStatusCode.NotModified; _messageHandler.Response = responseFromServer; _cacheStore.Setup(x => x.GetValueAsync(It.IsAny <CacheKey>())).ReturnsAsync(responseFromCache); _cacheStore.Setup(x => x.AddOrUpdateAsync(It.IsAny <CacheKey>(), It.Is <HttpResponseMessage>(r => r == responseFromCache))) .Returns(Task.FromResult(false)); // run var task = _client.SendAsync(request); var responseReturned = task.Result; var header = responseReturned.Headers.Single(x => x.Key == CacheCowHeader.Name); CacheCowHeader cacheCowHeader = null; CacheCowHeader.TryParse(header.Value.First(), out cacheCowHeader); // verify Assert.NotNull(cacheCowHeader); Assert.Equal(responseFromCache, responseReturned); Assert.Equal(true, cacheCowHeader.WasStale); Assert.Equal(true, cacheCowHeader.CacheValidationApplied); Assert.NotEqual(then, responseFromCache.Headers.Date); }
// TODO: this method is terribly long. Shorten protected async override Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var cacheCowHeader = new CacheCowHeader(); string uri = request.RequestUri.ToString(); TraceWriter.WriteLine("{0} - Starting SendAsync", TraceLevel.Verbose, request.RequestUri.ToString()); // check if needs to be ignored if (_ignoreRequestRules(request)) { return(await base.SendAsync(request, cancellationToken)); // EXIT !! _________________ } IEnumerable <string> varyHeaders; if (!VaryHeaderStore.TryGetValue(uri, out varyHeaders)) { varyHeaders = DefaultVaryHeaders; } var cacheKey = new CacheKey(uri, request.Headers.Where(x => varyHeaders.Any(y => y.Equals(x.Key, StringComparison.CurrentCultureIgnoreCase))) .SelectMany(z => z.Value) ); // get from cache and verify response HttpResponseMessage cachedResponse; ResponseValidationResult validationResultForCachedResponse = ResponseValidationResult.NotExist; TraceWriter.WriteLine("{0} - Before TryGetValue", TraceLevel.Verbose, request.RequestUri.ToString()); cachedResponse = await _cacheStore.GetValueAsync(cacheKey); cacheCowHeader.DidNotExist = cachedResponse == null; TraceWriter.WriteLine("{0} - After TryGetValue: DidNotExist => {1}", TraceLevel.Verbose, request.RequestUri.ToString(), cacheCowHeader.DidNotExist); if (!cacheCowHeader.DidNotExist.Value) // so if it EXISTS in cache { TraceWriter.WriteLine("{0} - Existed in the cache. CacheControl Headers => {1}", TraceLevel.Verbose, request.RequestUri.ToString(), cachedResponse.Headers.CacheControl.ToString()); cachedResponse.RequestMessage = request; validationResultForCachedResponse = ResponseValidator(cachedResponse); } TraceWriter.WriteLine("{0} - After ResponseValidator => {1}", TraceLevel.Verbose, request.RequestUri, validationResultForCachedResponse); // PUT/PATCH/DELETE validation if (request.Method.IsPutPatchOrDelete() && validationResultForCachedResponse.IsIn( ResponseValidationResult.OK, ResponseValidationResult.MustRevalidate)) { ApplyPutPatchDeleteValidationHeaders(request, cacheCowHeader, cachedResponse); return(await base.SendAsync(request, cancellationToken)); // EXIT !! _____________________________ } // here onward is only GET only. See if cache OK and if it is then return if (validationResultForCachedResponse == ResponseValidationResult.OK) { cacheCowHeader.RetrievedFromCache = true; return(cachedResponse.AddCacheCowHeader(cacheCowHeader)); // EXIT !! ____________________________ } // if stale else if (validationResultForCachedResponse == ResponseValidationResult.Stale) { cacheCowHeader.WasStale = true; var isFreshOrStaleAcceptable = IsFreshOrStaleAcceptable(cachedResponse, request); if (isFreshOrStaleAcceptable.HasValue && isFreshOrStaleAcceptable.Value) // similar to OK { // TODO: CONSUME AND RELEASE Response !!! return(cachedResponse.AddCacheCowHeader(cacheCowHeader)); // EXIT !! ____________________________ } else { validationResultForCachedResponse = ResponseValidationResult.MustRevalidate; // revalidate } } // cache validation for GET else if (validationResultForCachedResponse == ResponseValidationResult.MustRevalidate) { ApplyGetCacheValidationHeaders(request, cacheCowHeader, cachedResponse); } // _______________________________ RESPONSE only GET ___________________________________________ var serverResponse = await base.SendAsync(request, cancellationToken); // HERE IS LATE FOR APPLYING EXCEPTION POLICY !!! TraceWriter.WriteLine("{0} - After getting response", TraceLevel.Verbose, request.RequestUri.ToString()); if (request.Method != HttpMethod.Get) // only interested here if it is a GET - this line really never called - only GET gets here { return(serverResponse); } // in case of MustRevalidate with result 304 if (validationResultForCachedResponse == ResponseValidationResult.MustRevalidate && serverResponse.StatusCode == HttpStatusCode.NotModified) { TraceWriter.WriteLine("{0} - Got 304 from the server and ResponseValidationResult.MustRevalidate", TraceLevel.Verbose, request.RequestUri.ToString()); cachedResponse.RequestMessage = request; cacheCowHeader.RetrievedFromCache = true; TraceWriter.WriteLine("{0} - NotModified", TraceLevel.Verbose, request.RequestUri.ToString()); await UpdateCachedResponseAsync(cacheKey, cachedResponse, serverResponse, _cacheStore); ConsumeAndDisposeResponse(serverResponse); return(cachedResponse.AddCacheCowHeader(cacheCowHeader).CopyOtherCacheCowHeaders(serverResponse)); // EXIT !! _______________ } var validationResult = ResponseValidator(serverResponse); switch (validationResult) { case ResponseValidationResult.MustRevalidate: case ResponseValidationResult.OK: TraceWriter.WriteLine("{0} - ResponseValidationResult.OK or MustRevalidate", TraceLevel.Verbose, request.RequestUri.ToString()); // prepare ResponseStoragePreparationRules(serverResponse); // re-create cacheKey with real server accept // if there is a vary header, store it if (serverResponse.Headers.Vary != null) { varyHeaders = serverResponse.Headers.Vary.Select(x => x).ToArray(); IEnumerable <string> temp; if (!VaryHeaderStore.TryGetValue(uri, out temp)) { VaryHeaderStore.AddOrUpdate(uri, varyHeaders); } } // create real cacheKey with correct Vary headers cacheKey = new CacheKey(uri, request.Headers.Where(x => varyHeaders.Any(y => y.Equals(x.Key, StringComparison.CurrentCultureIgnoreCase))) .SelectMany(z => z.Value) ); // store the cache CheckForCacheCowHeader(serverResponse); await _cacheStore.AddOrUpdateAsync(cacheKey, serverResponse); TraceWriter.WriteLine("{0} - After AddOrUpdate", TraceLevel.Verbose, request.RequestUri.ToString()); break; default: TraceWriter.WriteLine("{0} - ResponseValidationResult. Other", TraceLevel.Verbose, request.RequestUri.ToString()); TraceWriter.WriteLine("{0} - Before TryRemove", TraceLevel.Verbose, request.RequestUri.ToString()); await _cacheStore.TryRemoveAsync(cacheKey); TraceWriter.WriteLine("{0} - After TryRemoveAsync", TraceLevel.Verbose, request.RequestUri.ToString()); cacheCowHeader.NotCacheable = true; break; } TraceWriter.WriteLine("{0} - Before returning response", TraceLevel.Verbose, request.RequestUri.ToString()); return(serverResponse.AddCacheCowHeader(cacheCowHeader)); }
public static HttpResponseMessage AddCacheCowHeader(this HttpResponseMessage response, CacheCowHeader header) { response.Headers.Add(CacheCowHeader.Name, header.ToString()); return(response); }
// TODO: this method is terribly long. Shorten protected override Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var cacheCowHeader = new CacheCowHeader(); var uri = request.RequestUri.ToString(); TraceWriter.WriteLine("{0} - Starting SendAsync", TraceLevel.Verbose, request.RequestUri.ToString()); // check if needs to be ignored if (_ignoreRequestRules(request)) { return(base.SendAsync(request, cancellationToken)); // EXIT !! _________________ } IEnumerable <string> varyHeaders; if (!VaryHeaderStore.TryGetValue(uri, out varyHeaders)) { varyHeaders = DefaultVaryHeaders; } var cacheKey = new CacheKey(uri, request.Headers.Where(x => varyHeaders.Any(y => y.Equals(x.Key, StringComparison.CurrentCultureIgnoreCase))) .SelectMany(z => z.Value) ); // get from cache and verify response HttpResponseMessage cachedResponse; var validationResultForCachedResponse = ResponseValidationResult.NotExist; try { TraceWriter.WriteLine("{0} - Before TryGetValue", TraceLevel.Verbose, request.RequestUri.ToString()); cacheCowHeader.DidNotExist = !_cacheStore.TryGetValue(cacheKey, out cachedResponse); TraceWriter.WriteLine("{0} - After TryGetValue: DidNotExist => {1}", TraceLevel.Verbose, request.RequestUri.ToString(), cacheCowHeader.DidNotExist); if (!cacheCowHeader.DidNotExist.Value) // so if it EXISTS in cache { TraceWriter.WriteLine("{0} - Existed in the cache. CacheControl Headers => {1}", TraceLevel.Verbose, request.RequestUri.ToString(), cachedResponse.Headers.CacheControl.ToString()); cachedResponse.RequestMessage = request; validationResultForCachedResponse = ResponseValidator(cachedResponse); } TraceWriter.WriteLine("{0} - After ResponseValidator => {1}", TraceLevel.Verbose, request.RequestUri, validationResultForCachedResponse); // PUT validation if ((request.Method == HttpMethod.Put) && validationResultForCachedResponse.IsIn( ResponseValidationResult.OK, ResponseValidationResult.MustRevalidate)) { DoPutValidation(request, cacheCowHeader, cachedResponse); return(base.SendAsync(request, cancellationToken)); // EXIT !! _____________________________ } // here onward is only GET only. See if cache OK and if it is then return if (validationResultForCachedResponse == ResponseValidationResult.OK) { cacheCowHeader.RetrievedFromCache = true; return(TaskHelpers.FromResult(cachedResponse.AddCacheCowHeader(cacheCowHeader))); // EXIT !! ____________________________ } // if stale if (validationResultForCachedResponse == ResponseValidationResult.Stale) { cacheCowHeader.WasStale = true; var isFreshOrStaleAcceptable = IsFreshOrStaleAcceptable(cachedResponse, request); if (isFreshOrStaleAcceptable.HasValue && isFreshOrStaleAcceptable.Value) // similar to OK { return(TaskHelpers.FromResult(cachedResponse.AddCacheCowHeader(cacheCowHeader))); } validationResultForCachedResponse = ResponseValidationResult.MustRevalidate; // revalidate } // cache validation for GET else if (validationResultForCachedResponse == ResponseValidationResult.MustRevalidate) { DoCacheValidationForGet(request, cacheCowHeader, cachedResponse); } } catch (Exception ex) { if (ExceptionHandler == null) { throw; } ExceptionHandler(ex); Trace.TraceWarning("Exception was swallowed in CacheCow: " + ex); return(base.SendAsync(request, cancellationToken)); } // _______________________________ RESPONSE only GET ___________________________________________ return(base.SendAsync(request, cancellationToken) .ContinueWith( tt => { // HERE IS LATE FOR APPLYING EXCEPTION POLICY !!! var serverResponse = tt.Result; TraceWriter.WriteLine("{0} - After getting response", TraceLevel.Verbose, request.RequestUri.ToString()); if (request.Method != HttpMethod.Get) { // only interested here if it is a GET - this line really never called - only GET gets here return serverResponse; } // in case of MustRevalidate with result 304 if ((validationResultForCachedResponse == ResponseValidationResult.MustRevalidate) && (serverResponse.StatusCode == HttpStatusCode.NotModified)) { TraceWriter.WriteLine( "{0} - Got 304 from the server and ResponseValidationResult.MustRevalidate", TraceLevel.Verbose, request.RequestUri.ToString()); cachedResponse.RequestMessage = request; cacheCowHeader.RetrievedFromCache = true; TraceWriter.WriteLine("{0} - NotModified", TraceLevel.Verbose, request.RequestUri.ToString()); UpdateCachedResponse(cacheKey, cachedResponse, serverResponse, _cacheStore); ConsumeAndDisposeResponse(serverResponse); return cachedResponse.AddCacheCowHeader(cacheCowHeader); // EXIT !! _______________ } var validationResult = ResponseValidator(serverResponse); switch (validationResult) { case ResponseValidationResult.MustRevalidate: case ResponseValidationResult.OK: TraceWriter.WriteLine("{0} - ResponseValidationResult.OK or MustRevalidate", TraceLevel.Verbose, request.RequestUri.ToString()); // prepare ResponseStoragePreparationRules(serverResponse); TraceWriter.WriteLine("{0} - Before AddOrUpdate", TraceLevel.Verbose, request.RequestUri.ToString()); // re-create cacheKey with real server accept // if there is a vary header, store it if (serverResponse.Headers.Vary != null) { varyHeaders = serverResponse.Headers.Vary.Select(x => x).ToArray(); IEnumerable <string> temp; if (!VaryHeaderStore.TryGetValue(uri, out temp)) { VaryHeaderStore.AddOrUpdate(uri, varyHeaders); } } // create real cacheKey with correct Vary headers cacheKey = new CacheKey(uri, request.Headers.Where(x => varyHeaders.Any(y => y.Equals(x.Key, StringComparison.CurrentCultureIgnoreCase))) .SelectMany(z => z.Value) ); // store the cache _cacheStore.AddOrUpdate(cacheKey, serverResponse); TraceWriter.WriteLine("{0} - After AddOrUpdate", TraceLevel.Verbose, request.RequestUri.ToString()); break; default: TraceWriter.WriteLine("{0} - ResponseValidationResult. Other", TraceLevel.Verbose, request.RequestUri.ToString()); TraceWriter.WriteLine("{0} - Before TryRemove", TraceLevel.Verbose, request.RequestUri.ToString()); _cacheStore.TryRemove(cacheKey); TraceWriter.WriteLine("{0} - After AddOrUpdate", TraceLevel.Verbose, request.RequestUri.ToString()); cacheCowHeader.NotCacheable = true; break; } TraceWriter.WriteLine("{0} - Before returning response", TraceLevel.Verbose, request.RequestUri.ToString()); return serverResponse.AddCacheCowHeader(cacheCowHeader); })); }
/// <summary> /// /// </summary> /// <param name="context"></param> /// <param name="next"></param> /// <returns></returns> public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) { var cacheCowHeader = new CacheCowHeader(); bool?cacheValidated = null; bool isRequestCacheable = _validator.IsCacheable(context.HttpContext.Request); var cacheValidationStatus = context.HttpContext.Request.GetCacheValidationStatus(); if (cacheValidationStatus != CacheValidationStatus.None) { var timedETag = await CacheDirectiveProvider.QueryAsync(context); cacheValidated = ApplyCacheValidation(timedETag, cacheValidationStatus, context); cacheCowHeader.ValidationApplied = true; if (cacheValidated ?? false) { cacheCowHeader.ShortCircuited = true; cacheCowHeader.ValidationMatched = true; context.HttpContext.Response.Headers.Add(CacheCowHeader.Name, cacheCowHeader.ToString()); // the response would have been set and no need to run the rest of the pipeline return; } } context.HttpContext.Items[StreamName] = context.HttpContext.Response.Body; context.HttpContext.Response.Body = new MemoryStream(); var execCtx = await next(); // _______________________________________________________________________________ var ms = context.HttpContext.Response.Body as MemoryStream; bool mustReflush = ms != null && ms.Length > 0; context.HttpContext.Response.Body = (Stream)context.HttpContext.Items[StreamName]; try { if (HttpMethods.IsGet(context.HttpContext.Request.Method)) { context.HttpContext.Response.Headers.Add(HeaderNames.Vary, string.Join(";", CacheDirectiveProvider.GetVaryHeaders(context.HttpContext))); var cacheControl = CacheDirectiveProvider.GetCacheControl(context.HttpContext, this.ConfiguredExpiry); var isResponseCacheable = _validator.IsCacheable(context.HttpContext.Response); if (!cacheControl.NoStore && isResponseCacheable) // _______ is cacheable { var or = execCtx.Result as ObjectResult; TimedEntityTagHeaderValue tet = null; if (or != null && or.Value != null) { tet = CacheDirectiveProvider.Extract(or.Value); } if (cacheValidated == null && // could not validate tet != null && cacheValidationStatus != CacheValidationStatus.None) // can only do GET validation, PUT is already impacted backend stores { cacheValidated = ApplyCacheValidation(tet, cacheValidationStatus, context); cacheCowHeader.ValidationApplied = true; // the response would have been set and no need to run the rest of the pipeline if (cacheValidated ?? false) { cacheCowHeader.ValidationMatched = true; context.HttpContext.Response.Headers.Add(CacheCowHeader.Name, cacheCowHeader.ToString()); return; } } if (tet != null) { context.HttpContext.Response.ApplyTimedETag(tet); } } if (!isRequestCacheable || !isResponseCacheable) { context.HttpContext.Response.MakeNonCacheable(); } else { context.HttpContext.Response.Headers[HttpHeaderNames.CacheControl] = cacheControl.ToString(); } context.HttpContext.Response.Headers.Add(CacheCowHeader.Name, cacheCowHeader.ToString()); } } finally { if (mustReflush) { ms.Position = 0; ms.CopyTo(context.HttpContext.Response.Body); } } }
// TODO: this method is terribly long. Shorten protected override Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var cacheCowHeader = new CacheCowHeader(); string uri = request.RequestUri.ToString(); TraceWriter.WriteLine("{0} - Starting", TraceLevel.Verbose, request.RequestUri.ToString()); // check if needs to be ignored if (_ignoreRequestRules(request)) { return(base.SendAsync(request, cancellationToken)); // EXIT !! _________________ } IEnumerable <string> varyHeaders; if (!VaryHeaderStore.TryGetValue(uri, out varyHeaders)) { varyHeaders = DefaultVaryHeaders; } var cacheKey = new CacheKey(uri, request.Headers.Where(x => varyHeaders.Any(y => y.Equals(x.Key, StringComparison.CurrentCultureIgnoreCase))) .SelectMany(z => z.Value) ); // get from cache and verify response HttpResponseMessage cachedResponse; ResponseValidationResult validationResultForCachedResponse = ResponseValidationResult.NotExist; TraceWriter.WriteLine("{0} - Before TryGetValue", TraceLevel.Verbose, request.RequestUri.ToString()); cacheCowHeader.DidNotExist = !_cacheStore.TryGetValue(cacheKey, out cachedResponse); TraceWriter.WriteLine("{0} - After TryGetValue", TraceLevel.Verbose, request.RequestUri.ToString()); if (!cacheCowHeader.DidNotExist.Value) // so if it EXISTS in cache { cachedResponse.RequestMessage = request; validationResultForCachedResponse = ResponseValidator(cachedResponse); } TraceWriter.WriteLine("{0} - After ResponseValidator {1}", TraceLevel.Verbose, request.RequestUri, validationResultForCachedResponse); // PUT validation if (request.Method == HttpMethod.Put && validationResultForCachedResponse.IsIn( ResponseValidationResult.OK, ResponseValidationResult.MustRevalidate)) { // add headers for a cache validation. First check ETag since is better if (UseConditionalPut) { cacheCowHeader.CacheValidationApplied = true; if (cachedResponse.Headers.ETag != null) { request.Headers.Add(HttpHeaderNames.IfMatch, cachedResponse.Headers.ETag.ToString()); } else if (cachedResponse.Content.Headers.LastModified != null) { request.Headers.Add(HttpHeaderNames.IfUnmodifiedSince, cachedResponse.Content.Headers.LastModified.Value.ToString("r")); } } return(base.SendAsync(request, cancellationToken)); // EXIT !! _____________________________ } // here onward is only GET only. See if cache OK and if it is then return if (validationResultForCachedResponse == ResponseValidationResult.OK) { cacheCowHeader.RetrievedFromCache = true; return(TaskHelpers.FromResult(cachedResponse.AddCacheCowHeader(cacheCowHeader))); // EXIT !! ____________________________ } // if stale else if (validationResultForCachedResponse == ResponseValidationResult.Stale) { cacheCowHeader.WasStale = true; var isFreshOrStaleAcceptable = IsFreshOrStaleAcceptable(cachedResponse, request); if (isFreshOrStaleAcceptable.HasValue && isFreshOrStaleAcceptable.Value) // similar to OK { // TODO: CONSUME AND RELEASE Response !!! return(TaskHelpers.FromResult(cachedResponse.AddCacheCowHeader(cacheCowHeader))); // EXIT !! ____________________________ } else { validationResultForCachedResponse = ResponseValidationResult.MustRevalidate; // revalidate } } // cache validation for GET else if (validationResultForCachedResponse == ResponseValidationResult.MustRevalidate) { cacheCowHeader.CacheValidationApplied = true; cacheCowHeader.WasStale = true; // add headers for a cache validation. First check ETag since is better if (cachedResponse.Headers.ETag != null) { request.Headers.Add(HttpHeaderNames.IfNoneMatch, cachedResponse.Headers.ETag.ToString()); } else if (cachedResponse.Content.Headers.LastModified != null) { request.Headers.Add(HttpHeaderNames.IfModifiedSince, cachedResponse.Content.Headers.LastModified.Value.ToString("r")); } } // _______________________________ RESPONSE only GET ___________________________________________ return(base.SendAsync(request, cancellationToken) .ContinueWith( tt => { var serverResponse = tt.Result; TraceWriter.WriteLine("{0} - After getting response", TraceLevel.Verbose, request.RequestUri.ToString()); if (request.Method != HttpMethod.Get) // only interested here if it is a GET - this line really never called - only GET gets here { return serverResponse; } // in case of MustRevalidate with result 304 if (validationResultForCachedResponse == ResponseValidationResult.MustRevalidate && serverResponse.StatusCode == HttpStatusCode.NotModified) { cachedResponse.RequestMessage = request; cacheCowHeader.RetrievedFromCache = true; TraceWriter.WriteLine("{0} - NotModified", TraceLevel.Verbose, request.RequestUri.ToString()); ConsumeAndDisposeResponse(serverResponse); return cachedResponse.AddCacheCowHeader(cacheCowHeader); // EXIT !! _______________ } var validationResult = ResponseValidator(serverResponse); switch (validationResult) { case ResponseValidationResult.MustRevalidate: case ResponseValidationResult.OK: TraceWriter.WriteLine("{0} - ResponseValidationResult.OK or MustRevalidate", TraceLevel.Verbose, request.RequestUri.ToString()); // prepare ResponseStoragePreparationRules(serverResponse); TraceWriter.WriteLine("{0} - Before AddOrUpdate", TraceLevel.Verbose, request.RequestUri.ToString()); // store the cache _cacheStore.AddOrUpdate(cacheKey, serverResponse); TraceWriter.WriteLine("{0} - Before AddOrUpdate", TraceLevel.Verbose, request.RequestUri.ToString()); // if there is a vary header, store it if (serverResponse.Headers.Vary != null) { VaryHeaderStore.AddOrUpdate(uri, serverResponse.Headers.Vary.Select(x => x).ToArray()); } break; default: TraceWriter.WriteLine("{0} - ResponseValidationResult. Other", TraceLevel.Verbose, request.RequestUri.ToString()); TraceWriter.WriteLine("{0} - Before TryRemove", TraceLevel.Verbose, request.RequestUri.ToString()); _cacheStore.TryRemove(cacheKey); TraceWriter.WriteLine("{0} - After AddOrUpdate", TraceLevel.Verbose, request.RequestUri.ToString()); cacheCowHeader.NotCacheable = true; break; } TraceWriter.WriteLine("{0} - Before returning response", TraceLevel.Verbose, request.RequestUri.ToString()); return serverResponse.AddCacheCowHeader(cacheCowHeader); } )); }