internal async static Task UpdateCachedResponseAsync(CacheKey cacheKey, HttpResponseMessage cachedResponse, HttpResponseMessage serverResponse, ICacheStore store) { TraceWriter.WriteLine("CachingHandler.UpdateCachedResponseAsync - response: " + serverResponse.Headers.ToString(), TraceLevel.Verbose); // update only if server had a cachecontrol. // TODO: merge CacheControl headers instead of replace if (serverResponse.Headers.CacheControl != null && (!serverResponse.Headers.CacheControl.NoCache)) // added to cover issue #139 { TraceWriter.WriteLine("CachingHandler.UpdateCachedResponseAsync - CacheControl: " + serverResponse.Headers.CacheControl.ToString(), TraceLevel.Verbose); cachedResponse.Headers.CacheControl = serverResponse.Headers.CacheControl; } else { TraceWriter.WriteLine("CachingHandler.UpdateCachedResponseAsync - CacheControl missing from server. Applying sliding expiration. Date => " + DateTimeOffset.UtcNow, TraceLevel.Verbose); } cachedResponse.Headers.Date = DateTimeOffset.UtcNow; // very important CheckForCacheCowHeader(cachedResponse); await store.AddOrUpdateAsync(cacheKey, cachedResponse); }
// 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)); }