Пример #1
0
        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);
        }
Пример #2
0
 private void Trace(string message)
 {
     if (string.IsNullOrEmpty(message))
     {
         return;
     }
     TraceWriter?.WriteLine(message);
 }
Пример #3
0
        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);
        }
Пример #6
0
        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
            });
        }
Пример #7
0
        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);
        }
Пример #8
0
        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);
                }
            }
                );
        }
Пример #10
0
        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);
        }
Пример #11
0
        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);
        }
Пример #12
0
        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);
        }
Пример #13
0
        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);
        }
Пример #14
0
 private void TraceLine(object o)
 {
     WriteIndentation();
     TraceWriter.WriteLine(o);
 }
Пример #15
0
 protected void TraceInformation(string message)
 {
     TraceWriter.WriteLine(message);
 }
Пример #16
0
 protected void TraceInformation(string format, params object[] args)
 {
     TraceWriter.WriteLine(format, args);
 }
Пример #17
0
        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;
                }
            };
        }
Пример #18
0
        // 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));
        }
Пример #19
0
 protected void TraceError(string message)
 {
     HasError = true;
     TraceWriter.WriteLine(message);
 }
Пример #20
0
        // 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);
            }
                       ));
        }
Пример #21
0
 protected void TraceError(string format, params object[] args)
 {
     HasError = true;
     TraceWriter.WriteLine(format, args);
 }
Пример #22
0
        // 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);
            }));
        }