diff --git a/src/Ocelot/Cache/DefaultCacheKeyGenerator.cs b/src/Ocelot/Cache/DefaultCacheKeyGenerator.cs index ac79fc5c5..97c3845f5 100644 --- a/src/Ocelot/Cache/DefaultCacheKeyGenerator.cs +++ b/src/Ocelot/Cache/DefaultCacheKeyGenerator.cs @@ -28,19 +28,41 @@ public async ValueTask GenerateRequestCacheKey(DownstreamRequest downstr } } - if (!options.EnableContentHashing || !downstreamRequest.HasContent) + if (!options.EnableHeadersHashing && !options.EnableContentHashing && !downstreamRequest.HasContent) { return MD5Helper.GenerateMd5(builder.ToString()); } - var requestContentString = await ReadContentAsync(downstreamRequest); - builder.Append(Delimiter) - .Append(requestContentString); + if (options.EnableContentHashing) + { + var requestContentString = await ReadContentAsync(downstreamRequest); + builder.Append(Delimiter) + .Append(requestContentString); + } + + if (options.EnableHeadersHashing) + { + var requestHeadersString = await ReadHeadersAsync(downstreamRequest); + builder.Append(Delimiter) + .Append(requestHeadersString); + } + + if (options.CleanableHashingRegexes.Any()) + { + return MD5Helper.GenerateMd5(HashingClean(builder.ToString(), options.CleanableHashingRegexes)); + } return MD5Helper.GenerateMd5(builder.ToString()); } + private static string HashingClean(string input, List patterns) => + patterns.Aggregate(input, (current, pattern) => Regex.Replace(current, pattern, string.Empty, RegexOptions.Singleline)); + private static Task ReadContentAsync(DownstreamRequest downstream) => downstream.HasContent ? downstream?.Request?.Content?.ReadAsStringAsync() ?? Task.FromResult(string.Empty) : Task.FromResult(string.Empty); + + private static Task ReadHeadersAsync(DownstreamRequest downstream) => downstream.HasContent + ? Task.FromResult(string.Join(":", downstream?.Headers.Select(h => h.Key + "=" + string.Join(",", h.Value)))) + : Task.FromResult(string.Empty); } diff --git a/src/Ocelot/Configuration/CacheOptions.cs b/src/Ocelot/Configuration/CacheOptions.cs index dc3c19116..7458815b1 100644 --- a/src/Ocelot/Configuration/CacheOptions.cs +++ b/src/Ocelot/Configuration/CacheOptions.cs @@ -19,13 +19,17 @@ internal CacheOptions() { } /// Time-to-live seconds. If not speciefied, zero value is used by default. /// The region of caching. /// The header name to control cached value. - /// The switcher for content hashing. If not speciefied, false value is used by default. - public CacheOptions(int? ttlSeconds, string region, string header, bool? enableContentHashing) + /// The switcher for content hashing. If not speciefied, false value is used by default. + /// The switcher for headers hashing. If not speciefied, false value is used by default. + /// The list of regex patterns to clean the hash. If not speciefied, an empty list is used by default. + public CacheOptions(int? ttlSeconds, string region, string header, bool? enableContentHashing, bool? enableHeadersHashing, List? cleanableHashingRegexes) { TtlSeconds = ttlSeconds ?? 0; Region = region; Header = header; EnableContentHashing = enableContentHashing ?? false; + EnableHeadersHashing = enableHeadersHashing ?? false; + CleanableHashingRegexes = cleanableHashingRegexes ?? new(); } /// Time-to-live seconds. @@ -39,4 +43,14 @@ public CacheOptions(int? ttlSeconds, string region, string header, bool? enableC /// Default value is . No hashing by default. /// if hashing is enabled, otherwise it is . public bool EnableContentHashing { get; } + + /// Enables MD5 hash calculation of the of the object. + /// Default value is . No hashing by default. + /// if hashing is enabled, otherwise it is . + public bool EnableHeadersHashing { get; } + + /// The list of regex patterns to clean the hash. + /// Default value is an empty list. No cleaning by default. + /// A of values. + public List CleanableHashingRegexes { get; } } diff --git a/src/Ocelot/Configuration/Creator/CacheOptionsCreator.cs b/src/Ocelot/Configuration/Creator/CacheOptionsCreator.cs index 6d1c8f2b4..28a629610 100644 --- a/src/Ocelot/Configuration/Creator/CacheOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/CacheOptionsCreator.cs @@ -10,8 +10,10 @@ public CacheOptions Create(FileCacheOptions options, FileGlobalConfiguration glo var header = options.Header ?? global?.CacheOptions.Header; var ttlSeconds = options.TtlSeconds ?? global?.CacheOptions.TtlSeconds; var enableContentHashing = options.EnableContentHashing ?? global?.CacheOptions.EnableContentHashing; + var enableHeadersHashing = options.EnableHeadersHashing ?? global?.CacheOptions.EnableHeadersHashing; + var cleanableHashingRegexes = options.CleanableHashingRegexes ?? global?.CacheOptions.CleanableHashingRegexes; - return new CacheOptions(ttlSeconds, region, header, enableContentHashing); + return new CacheOptions(ttlSeconds, region, header, enableContentHashing, enableHeadersHashing, cleanableHashingRegexes); } protected virtual string GetRegion(string region, string upstreamPathTemplate, IList upstreamHttpMethod) diff --git a/src/Ocelot/Configuration/File/FileCacheOptions.cs b/src/Ocelot/Configuration/File/FileCacheOptions.cs index 42b793390..5e81bd0cb 100644 --- a/src/Ocelot/Configuration/File/FileCacheOptions.cs +++ b/src/Ocelot/Configuration/File/FileCacheOptions.cs @@ -10,6 +10,8 @@ public FileCacheOptions(FileCacheOptions from) TtlSeconds = from.TtlSeconds; Header = from.Header; EnableContentHashing = from.EnableContentHashing; + EnableHeadersHashing = from.EnableHeadersHashing; + CleanableHashingRegexes = from.CleanableHashingRegexes; } /// Using where T is to have as default value and allowing global configuration usage. @@ -23,4 +25,14 @@ public FileCacheOptions(FileCacheOptions from) /// If then use global configuration with by default. /// if content hashing is enabled; otherwise, . public bool? EnableContentHashing { get; set; } + + /// Using where T is to have as default value and allowing global configuration usage. + /// If then use global configuration with by default. + /// if headers hashing is enabled; otherwise, . + public bool? EnableHeadersHashing { get; set; } + + /// Using where T is to have as default value and allowing global configuration usage. + /// If then use global configuration with empty list by default. + /// The list of regular expressions for cleanable hashing. + public List? CleanableHashingRegexes { get; set; } } diff --git a/test/Ocelot.UnitTests/Cache/DefaultCacheKeyGeneratorTests.cs b/test/Ocelot.UnitTests/Cache/DefaultCacheKeyGeneratorTests.cs index 50c7de6c9..91b1689ef 100644 --- a/test/Ocelot.UnitTests/Cache/DefaultCacheKeyGeneratorTests.cs +++ b/test/Ocelot.UnitTests/Cache/DefaultCacheKeyGeneratorTests.cs @@ -35,7 +35,7 @@ public void should_generate_cache_key_with_request_content() const string noHeader = null; const string content = nameof(should_generate_cache_key_with_request_content); var cachekey = MD5Helper.GenerateMd5($"{verb}-{url}-{content}"); - CacheOptions options = new CacheOptions(100, "region", noHeader, true); + CacheOptions options = new CacheOptions(100, "region", noHeader, true, false, null); this.Given(x => x.GivenDownstreamRoute(options)) .And(x => GivenHasContent(content)) @@ -59,7 +59,7 @@ public void should_generate_cache_key_without_request_content() [Fact] public void should_generate_cache_key_with_cache_options_header() { - CacheOptions options = new CacheOptions(100, "region", headerName, false); + CacheOptions options = new CacheOptions(100, "region", headerName, false, false, null); var cachekey = MD5Helper.GenerateMd5($"{verb}-{url}-{header}"); this.Given(x => x.GivenDownstreamRoute(options)) @@ -72,7 +72,7 @@ public void should_generate_cache_key_with_cache_options_header() public void should_generate_cache_key_happy_path() { const string content = nameof(should_generate_cache_key_happy_path); - CacheOptions options = new CacheOptions(100, "region", headerName, true); + CacheOptions options = new CacheOptions(100, "region", headerName, true, false, null); var cachekey = MD5Helper.GenerateMd5($"{verb}-{url}-{header}-{content}"); this.Given(x => x.GivenDownstreamRoute(options)) diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index ec8698cdc..dc2ec0837 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -106,7 +106,7 @@ private void GivenTheDownstreamRouteIs() var route = new RouteBuilder() .WithDownstreamRoute(new DownstreamRouteBuilder() .WithIsCached(true) - .WithCacheOptions(new CacheOptions(100, "kanken", null, false)) + .WithCacheOptions(new CacheOptions(100, "kanken", null, false, false, null)) .WithUpstreamHttpMethod(new List { "Get" }) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) diff --git a/test/Ocelot.UnitTests/CacheManager/OutputCacheMiddlewareRealCacheTests.cs b/test/Ocelot.UnitTests/CacheManager/OutputCacheMiddlewareRealCacheTests.cs index 8ec49b102..ffd2b55dc 100644 --- a/test/Ocelot.UnitTests/CacheManager/OutputCacheMiddlewareRealCacheTests.cs +++ b/test/Ocelot.UnitTests/CacheManager/OutputCacheMiddlewareRealCacheTests.cs @@ -77,7 +77,7 @@ private void GivenTheDownstreamRouteIs() { var route = new DownstreamRouteBuilder() .WithIsCached(true) - .WithCacheOptions(new CacheOptions(100, "kanken", null, false)) + .WithCacheOptions(new CacheOptions(100, "kanken", null, false, false, null)) .WithUpstreamHttpMethod(new List { "Get" }) .Build(); diff --git a/test/Ocelot.UnitTests/Configuration/DownstreamRouteExtensionsTests.cs b/test/Ocelot.UnitTests/Configuration/DownstreamRouteExtensionsTests.cs index 0c3b3bcc3..554e68366 100644 --- a/test/Ocelot.UnitTests/Configuration/DownstreamRouteExtensionsTests.cs +++ b/test/Ocelot.UnitTests/Configuration/DownstreamRouteExtensionsTests.cs @@ -29,7 +29,7 @@ public DownstreamRouteExtensionsTests() null, null, default, - new CacheOptions(0, null, null, null), + new CacheOptions(0, null, null, null, null, null), new LoadBalancerOptions(null, null, 0), new RateLimitOptions(false, null, null, false, null, null, null, 0), new Dictionary(), diff --git a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs index 507aca35d..76d38611a 100644 --- a/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RoutesCreatorTests.cs @@ -181,7 +181,7 @@ private void GivenTheDependenciesAreSetUpCorrectly() _qoso = new QoSOptionsBuilder().Build(); _rlo = new RateLimitOptionsBuilder().Build(); - _cacheOptions = new CacheOptions(0, "vesty", null, false); + _cacheOptions = new CacheOptions(0, "vesty", null, false, false, null); _hho = new HttpHandlerOptionsBuilder().Build(); _ht = new HeaderTransformations(new List(), new List(), new List(), new List()); _dhp = new List();