public async Task FinalizeCacheHeadersAsync_UpdateCachedVaryByRules_IfEquivalentToPrevious()
        {
            var cache      = new TestResponseCache();
            var sink       = new TestSink();
            var middleware = TestUtils.CreateTestMiddleware(testSink: sink, cache: cache);
            var context    = TestUtils.CreateTestContext();

            context.HttpContext.Response.Headers[HeaderNames.Vary] = new StringValues(new[] { "headerA", "HEADERB" });
            context.HttpContext.Features.Set <IResponseCachingFeature>(new ResponseCachingFeature()
            {
                VaryByQueryKeys = new StringValues(new[] { "queryB", "QUERYA" })
            });
            var cachedVaryByRules = new CachedVaryByRules()
            {
                VaryByKeyPrefix = FastGuid.NewGuid().IdString,
                Headers         = new StringValues(new[] { "HEADERA", "HEADERB" }),
                QueryKeys       = new StringValues(new[] { "QUERYA", "QUERYB" })
            };

            context.CachedVaryByRules = cachedVaryByRules;

            await middleware.FinalizeCacheHeadersAsync(context);

            // An update to the cache is always made but the entry should be the same
            Assert.Equal(1, cache.SetCount);
            Assert.Same(cachedVaryByRules, context.CachedVaryByRules);
            TestUtils.AssertLoggedMessages(
                sink.Writes,
                LoggedMessage.VaryByRulesUpdated);
        }
Example #2
0
        public void ResponseCachingKeyProvider_CreateStorageVaryKey_ReturnsCachedVaryByGuid_IfVaryByRulesIsEmpty()
        {
            var cacheKeyProvider = TestUtils.CreateTestKeyProvider();
            var context          = TestUtils.CreateTestContext();

            context.CachedVaryByRules = new CachedVaryByRules()
            {
                VaryByKeyPrefix = FastGuid.NewGuid().IdString
            };

            Assert.Equal($"{context.CachedVaryByRules.VaryByKeyPrefix}", cacheKeyProvider.CreateStorageVaryByKey(context));
        }
Example #3
0
        public void ResponseCachingKeyProvider_CreateStorageVaryKey_IncludesQueryKeys_QueryKeyCaseInsensitive_UseQueryKeyCasing()
        {
            var cacheKeyProvider = TestUtils.CreateTestKeyProvider();
            var context          = TestUtils.CreateTestContext();

            context.HttpContext.Request.QueryString = new QueryString("?queryA=ValueA&queryB=ValueB");
            context.CachedVaryByRules = new CachedVaryByRules()
            {
                VaryByKeyPrefix = FastGuid.NewGuid().IdString,
                QueryKeys       = new string[] { "QueryA", "QueryC" }
            };

            Assert.Equal($"{context.CachedVaryByRules.VaryByKeyPrefix}{KeyDelimiter}Q{KeyDelimiter}QueryA=ValueA{KeyDelimiter}QueryC=",
                         cacheKeyProvider.CreateStorageVaryByKey(context));
        }
Example #4
0
        public void ResponseCachingKeyProvider_CreateStorageVaryKey_QueryKeysValuesAreSorted()
        {
            var cacheKeyProvider = TestUtils.CreateTestKeyProvider();
            var context          = TestUtils.CreateTestContext();

            context.HttpContext.Request.QueryString = new QueryString("?QueryA=ValueB&QueryA=ValueA");
            context.CachedVaryByRules = new CachedVaryByRules()
            {
                VaryByKeyPrefix = FastGuid.NewGuid().IdString,
                QueryKeys       = new string[] { "*" }
            };

            // To support case insensitivity, all query keys are converted to upper case.
            // Explicit query keys uses the casing specified in the setting.
            Assert.Equal($"{context.CachedVaryByRules.VaryByKeyPrefix}{KeyDelimiter}Q{KeyDelimiter}QUERYA=ValueA{KeySubDelimiter}ValueB",
                         cacheKeyProvider.CreateStorageVaryByKey(context));
        }
Example #5
0
        public void ResponseCachingKeyProvider_CreateStorageVaryKey_IncludesListedHeadersAndQueryKeys()
        {
            var cacheKeyProvider = TestUtils.CreateTestKeyProvider();
            var context          = TestUtils.CreateTestContext();

            context.HttpContext.Request.Headers["HeaderA"] = "ValueA";
            context.HttpContext.Request.Headers["HeaderB"] = "ValueB";
            context.HttpContext.Request.QueryString        = new QueryString("?QueryA=ValueA&QueryB=ValueB");
            context.CachedVaryByRules = new CachedVaryByRules()
            {
                VaryByKeyPrefix = FastGuid.NewGuid().IdString,
                Headers         = new string[] { "HeaderA", "HeaderC" },
                QueryKeys       = new string[] { "QueryA", "QueryC" }
            };

            Assert.Equal($"{context.CachedVaryByRules.VaryByKeyPrefix}{KeyDelimiter}H{KeyDelimiter}HeaderA=ValueA{KeyDelimiter}HeaderC={KeyDelimiter}Q{KeyDelimiter}QueryA=ValueA{KeyDelimiter}QueryC=",
                         cacheKeyProvider.CreateStorageVaryByKey(context));
        }
Example #6
0
 private static string GenerateGuidString(FastGuid guid)
 {
     return(string.Create(13, guid.IdValue, (buffer, value) =>
     {
         char[] encode32Chars = s_encode32Chars;
         buffer[12] = encode32Chars[value & 31];
         buffer[11] = encode32Chars[(value >> 5) & 31];
         buffer[10] = encode32Chars[(value >> 10) & 31];
         buffer[9] = encode32Chars[(value >> 15) & 31];
         buffer[8] = encode32Chars[(value >> 20) & 31];
         buffer[7] = encode32Chars[(value >> 25) & 31];
         buffer[6] = encode32Chars[(value >> 30) & 31];
         buffer[5] = encode32Chars[(value >> 35) & 31];
         buffer[4] = encode32Chars[(value >> 40) & 31];
         buffer[3] = encode32Chars[(value >> 45) & 31];
         buffer[2] = encode32Chars[(value >> 50) & 31];
         buffer[1] = encode32Chars[(value >> 55) & 31];
         buffer[0] = encode32Chars[(value >> 60) & 31];
     }));
 }
Example #7
0
        /// <summary>
        /// Finalize cache headers.
        /// </summary>
        /// <param name="context"></param>
        /// <returns><c>true</c> if a vary by entry needs to be stored in the cache; otherwise <c>false</c>.</returns>
        private bool OnFinalizeCacheHeaders(ResponseCachingContext context)
        {
            if (_policyProvider.IsResponseCacheable(context))
            {
                var storeVaryByEntry = false;
                context.ShouldCacheResponse = true;

                // Create the cache entry now
                var response      = context.HttpContext.Response;
                var varyHeaders   = new StringValues(response.Headers.GetCommaSeparatedValues(HeaderNames.Vary));
                var varyQueryKeys = new StringValues(context.HttpContext.Features.Get <Microsoft.AspNetCore.ResponseCaching.IResponseCachingFeature>()?.VaryByQueryKeys);
                context.CachedResponseValidFor = context.ResponseSharedMaxAge ??
                                                 context.ResponseMaxAge ??
                                                 (context.ResponseExpires - context.ResponseTime.Value) ??
                                                 DefaultExpirationTimeSpan;

                // Generate a base key if none exist
                if (string.IsNullOrEmpty(context.BaseKey))
                {
                    context.BaseKey = _keyProvider.CreateBaseKey(context);
                }

                // Check if any vary rules exist
                if (!StringValues.IsNullOrEmpty(varyHeaders) || !StringValues.IsNullOrEmpty(varyQueryKeys))
                {
                    // Normalize order and casing of vary by rules
                    var normalizedVaryHeaders   = GetOrderCasingNormalizedStringValues(varyHeaders);
                    var normalizedVaryQueryKeys = GetOrderCasingNormalizedStringValues(varyQueryKeys);

                    // Update vary rules if they are different
                    if (context.CachedVaryByRules == null ||
                        !StringValues.Equals(context.CachedVaryByRules.QueryKeys, normalizedVaryQueryKeys) ||
                        !StringValues.Equals(context.CachedVaryByRules.Headers, normalizedVaryHeaders))
                    {
                        context.CachedVaryByRules = new CachedVaryByRules
                        {
                            VaryByKeyPrefix = FastGuid.NewGuid().IdString,
                            Headers         = normalizedVaryHeaders,
                            QueryKeys       = normalizedVaryQueryKeys
                        };
                    }

                    // Always overwrite the CachedVaryByRules to update the expiry information
                    _logger.LogVaryByRulesUpdated(normalizedVaryHeaders, normalizedVaryQueryKeys);
                    storeVaryByEntry = true;

                    context.StorageVaryKey = _keyProvider.CreateStorageVaryByKey(context);
                }

                // Ensure date header is set
                if (!context.ResponseDate.HasValue)
                {
                    context.ResponseDate = context.ResponseTime.Value;
                    // Setting the date on the raw response headers.
                    context.HttpContext.Response.Headers[HeaderNames.Date] = HeaderUtilities.FormatDate(context.ResponseDate.Value);
                }

                // Store the response on the state
                context.CachedResponse = new CachedResponse
                {
                    Created    = context.ResponseDate.Value,
                    StatusCode = context.HttpContext.Response.StatusCode,
                    Headers    = new HeaderDictionary()
                };

                foreach (var header in context.HttpContext.Response.Headers)
                {
                    if (!string.Equals(header.Key, HeaderNames.Age, StringComparison.OrdinalIgnoreCase))
                    {
                        context.CachedResponse.Headers[header.Key] = header.Value;
                    }
                }

                return(storeVaryByEntry);
            }

            context.ResponseCachingStream.DisableBuffering();
            return(false);
        }