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