// 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)); }
// 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); } )); }