internal static void UpdateCachedResponse(CacheKey cacheKey, HttpResponseMessage cachedResponse, HttpResponseMessage serverResponse, ICacheStore store) { TraceWriter.WriteLine("CachingHandler.UpdateCachedResponse - response: " + serverResponse.Headers, 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.UpdateCachedResponse - CacheControl: " + serverResponse.Headers.CacheControl, TraceLevel.Verbose); cachedResponse.Headers.CacheControl = serverResponse.Headers.CacheControl; } else { TraceWriter.WriteLine( "CachingHandler.UpdateCachedResponse - CacheControl missing from server. Applying sliding expiration. Date => " + DateTimeOffset.UtcNow, TraceLevel.Verbose); } cachedResponse.Headers.Date = DateTimeOffset.UtcNow; // very important store.AddOrUpdate(cacheKey, cachedResponse); }
private void Trace(string message) { if (string.IsNullOrEmpty(message)) { return; } TraceWriter?.WriteLine(message); }
private static void CheckForCacheCowHeader(HttpResponseMessage responseMessage) { var header = responseMessage.Headers.GetCacheCowHeader(); if (header != null) { TraceWriter.WriteLine("!!WARNING!! response stored with CacheCowHeader!!", TraceLevel.Warning); } }
public Task<HttpResponseMessage> DeserializeToResponseAsync(Stream stream) { var response = new HttpResponseMessage(); response.Content = new StreamContent(stream); response.Content.Headers.Add("Content-Type", "application/http;msgtype=response"); TraceWriter.WriteLine("before ReadAsHttpResponseMessageAsync", TraceLevel.Verbose); return response.Content.ReadAsHttpResponseMessageAsync(); }
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 AddOrUpdate(CacheKey key, HttpResponseMessage response) { var fileName = key.EnsureFolderAndGetFileName(_dataRoot); if (File.Exists(fileName)) { TraceWriter.WriteLine("Must remove file", TraceLevel.Verbose); TryRemove(key); } var ms = new MemoryStream(); _serializer.SerializeAsync(TaskHelpers.FromResult(response), ms).Wait(); ms.Position = 0; using (var fs = GetFile(fileName, FileMode.Create)) { TraceWriter.WriteLine("Before serialise", TraceLevel.Verbose); ms.CopyTo(fs); TraceWriter.WriteLine("After serialise", TraceLevel.Verbose); } var info = new FileInfo(fileName); // Update database _database.Cache .Insert(new CacheItem { Domain = key.Domain, Hash = Convert.ToBase64String(key.Hash), LastAccessed = DateTime.Now, LastUpdated = (response.Content != null) && response.Content.Headers.LastModified.HasValue ? response.Content.Headers.LastModified.Value.UtcDateTime : DateTime.Now , Size = info.Length }); TraceWriter.WriteLine("After db update", TraceLevel.Verbose); // tell quota manager _quotaManager.ItemAdded(new CacheItemMetadata { Domain = key.Domain, Key = key.Hash, LastAccessed = DateTime.Now, Size = info.Length }); }
public async Task <HttpResponseMessage> DeserializeToResponseAsync(Stream stream) { var response = new HttpResponseMessage(); response.Content = new StreamContent(stream); response.Content.Headers.Add("Content-Type", "application/http;msgtype=response"); TraceWriter.WriteLine("before ReadAsHttpResponseMessageAsync", TraceLevel.Verbose); var responseMessage = await response.Content.ReadAsHttpResponseMessageAsync(); if (responseMessage.Content != null && _bufferContent) { await responseMessage.Content.LoadIntoBufferAsync(); } return(responseMessage); }
internal static void UpdateCachedResponse(CacheKey cacheKey, HttpResponseMessage cachedResponse, HttpResponseMessage serverResponse, ICacheStore store) { TraceWriter.WriteLine("CachingHandler.UpdateCachedResponse - 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.UpdateCachedResponse - CacheControl: " + serverResponse.Headers.CacheControl.ToString(), TraceLevel.Verbose); cachedResponse.Headers.CacheControl = serverResponse.Headers.CacheControl; cachedResponse.Headers.Date = DateTimeOffset.UtcNow; // very important store.AddOrUpdate(cacheKey, cachedResponse); } }
public Task SerializeAsync(Task<HttpResponseMessage> response, Stream stream) { return response.Then(r => { if (r.Content != null) { TraceWriter.WriteLine("SerializeAsync - before load", TraceLevel.Verbose); return r.Content.LoadIntoBufferAsync() .Then(() => { TraceWriter.WriteLine("SerializeAsync - after load", TraceLevel.Verbose); var httpMessageContent = new HttpMessageContent(r); // All in-memory and CPU-bound so no need to async return httpMessageContent.ReadAsByteArrayAsync(); }) .Then(buffer => { TraceWriter.WriteLine("SerializeAsync - after ReadAsByteArrayAsync", TraceLevel.Verbose); return Task.Factory.FromAsync(stream.BeginWrite, stream.EndWrite, buffer, 0, buffer.Length, null, TaskCreationOptions.AttachedToParent); } ); ; } else { TraceWriter.WriteLine("Content NULL - before load", TraceLevel.Verbose); var httpMessageContent = new HttpMessageContent(r); // All in-memory and CPU-bound so no need to async var buffer = httpMessageContent.ReadAsByteArrayAsync().Result; return Task.Factory.FromAsync(stream.BeginWrite, stream.EndWrite, buffer, 0, buffer.Length, null, TaskCreationOptions.AttachedToParent); } } ); }
private bool TryRemove(CacheItemMetadata metadata, bool tellQuotaManager) { var fileName = metadata.EnsureFolderAndGetFileName(_dataRoot); if (File.Exists(fileName)) { File.Delete(fileName); TraceWriter.WriteLine("After delete file {0}", TraceLevel.Verbose, fileName); _database.Cache.DeleteByHash(Convert.ToBase64String(metadata.Key)); TraceWriter.WriteLine("After db update. File name: {0}", TraceLevel.Verbose, fileName); if (tellQuotaManager) { _quotaManager.ItemRemoved(metadata); } return(true); } return(false); }
public bool TryGetValue(CacheKey key, out HttpResponseMessage response) { response = null; string fileName = key.EnsureFolderAndGetFileName(_dataRoot); if (File.Exists(fileName)) { var ms = new MemoryStream(); using (var fs = GetFile(fileName, FileMode.Open)) { TraceWriter.WriteLine("TryGetValue - before DeserializeToResponseAsync", TraceLevel.Verbose); fs.CopyTo(ms); ms.Position = 0; } response = _serializer.DeserializeToResponseAsync(ms).Result; TraceWriter.WriteLine("TryGetValue - After DeserializeToResponseAsync", TraceLevel.Verbose); if (response.Content != null) { var task = response.Content.LoadIntoBufferAsync(); task.Wait(); TraceWriter.WriteLine("TryGetValue - After wait", TraceLevel.Verbose); } _database.Cache .UpdateByHash(new { Hash = Convert.ToBase64String(key.Hash), LastAccessed = DateTime.Now }); TraceWriter.WriteLine("After updating Last Accessed", TraceLevel.Verbose); } return(response != null); }
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); }
public async Task SerializeAsync(HttpResponseMessage response, Stream stream) { if (response.Content != null) { TraceWriter.WriteLine("SerializeAsync - before load", TraceLevel.Verbose); if (_bufferContent) { await response.Content.LoadIntoBufferAsync(); } TraceWriter.WriteLine("SerializeAsync - after load", TraceLevel.Verbose); } else { TraceWriter.WriteLine("Content NULL - before load", TraceLevel.Verbose); } var httpMessageContent = new HttpMessageContent(response); var buffer = await httpMessageContent.ReadAsByteArrayAsync(); TraceWriter.WriteLine("SerializeAsync - after ReadAsByteArrayAsync", TraceLevel.Verbose); stream.Write(buffer, 0, buffer.Length); }
private void TraceLine(object o) { WriteIndentation(); TraceWriter.WriteLine(o); }
protected void TraceInformation(string message) { TraceWriter.WriteLine(message); }
protected void TraceInformation(string format, params object[] args) { TraceWriter.WriteLine(format, args); }
public CachingHandler(ICacheStore cacheStore, IVaryHeaderStore varyHeaderStore) { _cacheStore = cacheStore; UseConditionalPutPatchDelete = true; MustRevalidateByDefault = true; VaryHeaderStore = varyHeaderStore; DefaultVaryHeaders = new string[] { "Accept" }; ResponseValidator = (response) => { // 13.4 //Unless specifically constrained by a cache-control (section 14.9) directive, a caching system MAY always store // a successful response (see section 13.8) as a cache entry, MAY return it without validation if it // is fresh, and MAY return it after successful validation. If there is neither a cache validator nor an // explicit expiration time associated with a response, we do not expect it to be cached, but certain caches MAY violate this expectation // (for example, when little or no network connectivity is available). // 14.9.1 // If the no-cache directive does not specify a field-name, then a cache MUST NOT use the response to satisfy a subsequent request without // successful revalidation with the origin server. This allows an origin server to prevent caching // even by caches that have been configured to return stale responses to client requests. //If the no-cache directive does specify one or more field-names, then a cache MAY use the response // to satisfy a subsequent request, subject to any other restrictions on caching. However, the specified // field-name(s) MUST NOT be sent in the response to a subsequent request without successful revalidation // with the origin server. This allows an origin server to prevent the re-use of certain header fields in a response, while still allowing caching of the rest of the response. if (!response.StatusCode.IsIn(_cacheableStatuses)) { return(ResponseValidationResult.NotCacheable); } if (!response.IsSuccessStatusCode || response.Headers.CacheControl == null || response.Headers.CacheControl.NoStore) // || response.Headers.CacheControl.NoCache was removed. See issue { return(ResponseValidationResult.NotCacheable); } if (response.Headers.Date == null) { TraceWriter.WriteLine("Response date is NULL", TraceLevel.Warning); } response.Headers.Date = response.Headers.Date ?? DateTimeOffset.UtcNow; // this also helps in cache creation var dateTimeOffset = response.Headers.Date; TraceWriter.WriteLine( String.Format("CachedResponse date was => {0} - compared to UTC.Now => {1}", dateTimeOffset, DateTimeOffset.UtcNow), TraceLevel.Verbose); if (response.Content == null) { return(ResponseValidationResult.NotCacheable); } if (response.Headers.CacheControl.MaxAge == null && response.Headers.CacheControl.SharedMaxAge == null && response.Content.Headers.Expires == null) { return(ResponseValidationResult.NotCacheable); } if (response.Headers.CacheControl.NoCache) { return(ResponseValidationResult.MustRevalidate); } // here we use if (response.Content.Headers.Expires != null && response.Content.Headers.Expires < DateTimeOffset.UtcNow) { return(response.Headers.CacheControl.ShouldRevalidate(MustRevalidateByDefault) ? ResponseValidationResult.MustRevalidate : ResponseValidationResult.Stale); } if (response.Headers.CacheControl.MaxAge != null && DateTimeOffset.UtcNow > response.Headers.Date.Value.Add(response.Headers.CacheControl.MaxAge.Value)) { return(response.Headers.CacheControl.ShouldRevalidate(MustRevalidateByDefault) ? ResponseValidationResult.MustRevalidate : ResponseValidationResult.Stale); } if (response.Headers.CacheControl.SharedMaxAge != null && DateTimeOffset.UtcNow > response.Headers.Date.Value.Add(response.Headers.CacheControl.SharedMaxAge.Value)) { return(response.Headers.CacheControl.ShouldRevalidate(MustRevalidateByDefault) ? ResponseValidationResult.MustRevalidate : ResponseValidationResult.Stale); } return(ResponseValidationResult.OK); }; _ignoreRequestRules = (request) => { if (request.Method.IsCacheIgnorable()) { return(true); } // client can tell CachingHandler not to do caching for a particular request if (request.Headers.CacheControl != null) { if (request.Headers.CacheControl.NoStore) { return(true); } } return(false); }; ResponseStoragePreparationRules = (response) => { // 14.9.3 // If a response includes both an Expires header and a max-age directive, // the max-age directive overrides the Expires header, even if the Expires header is more restrictive. if (response.Content.Headers.Expires != null && (response.Headers.CacheControl.MaxAge != null || response.Headers.CacheControl.SharedMaxAge != null)) { response.Content.Headers.Expires = null; } }; }
// 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)); }
protected void TraceError(string message) { HasError = true; TraceWriter.WriteLine(message); }
// 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); } )); }
protected void TraceError(string format, params object[] args) { HasError = true; TraceWriter.WriteLine(format, args); }
// 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); })); }