Skip to content

Commit 6e9a975

Browse files
thiagoloureiroggnaegiraman-m
authored
#2054 #2059 Manage EnableContentHashing setting by global CacheOptions (#2058)
* EnableContentHashing not being considered from appsettings * Adding CacheOptionsCreator, Injected IRegionCreator as Singleton. Should still add some acceptance tests that are definitely missing! * Adding caching global configuration since we messed up, ignoring an important breaking change with EnableContentHashing set to false by default * Adding some further acceptance tests, validating EnableContentHashing, validating global config too. * removing some debug content * TtlSeconds must be set * updating documentation * Update docs/features/caching.rst Co-authored-by: Raman Maksimchuk <[email protected]> * Update docs/features/caching.rst Co-authored-by: Raman Maksimchuk <[email protected]> * Removing RegionCreator, moving service collection extension method to dependencyInjection\Features etc. * adding unit tests for FileCacheOptions * some more null tests... * slight refactoring, updating ICacheOptionsCreator signature * some more design refactoring * Update src/Ocelot/Configuration/Creator/CacheOptionsCreator.cs Co-authored-by: Raman Maksimchuk <[email protected]> * Code review by @raman-m * Rename `FileCacheOptions` -> `CacheOptions` * Subtly transition to `CacheOptions`, ensuring compatibility with `FileCacheOptions` to avoid a breaking change * Not obsolete --------- Co-authored-by: Guillaume Gnaegi <[email protected]> Co-authored-by: Raman Maksimchuk <[email protected]>
1 parent aef3e6b commit 6e9a975

24 files changed

+490
-186
lines changed

docs/features/caching.rst

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ Finally, in order to use caching on a route in your Route configuration add this
3939
"FileCacheOptions": {
4040
"TtlSeconds": 15,
4141
"Region": "europe-central",
42-
"Header": "Authorization"
42+
"Header": "OC-Caching-Control",
43+
"EnableContentHashing": false // my route has GET verb only, assigning 'true' for requests with body: POST, PUT etc.
4344
}
4445
4546
In this example **TtlSeconds** is set to 15 which means the cache will expire after 15 seconds.
@@ -48,10 +49,38 @@ The **Region** represents a region of caching.
4849
Additionally, if a header name is defined in the **Header** property, that header value is looked up by the key (header name) in the ``HttpRequest`` headers,
4950
and if the header is found, its value will be included in caching key. This causes the cache to become invalid due to the header value changing.
5051

52+
``EnableContentHashing`` option
53+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
54+
55+
In version `23.0`_, the new property **EnableContentHashing** has been introduced. Previously, the request body was utilized to compute the cache key.
56+
However, due to potential performance issues arising from request body hashing, it has been disabled by default.
57+
Clearly, this constitutes a breaking change and presents challenges for users who require cache key calculations that consider the request body (e.g., for the POST method).
58+
To address this issue, it is recommended to enable the option either at the route level or globally in the :ref:`cch-global-configuration` section:
59+
60+
.. code-block:: json
61+
62+
"CacheOptions": {
63+
// ...
64+
"EnableContentHashing": true
65+
}
66+
67+
.. _cch-global-configuration:
68+
69+
Global Configuration
70+
--------------------
71+
72+
The positive update is that copying Route-level properties for each route is no longer necessary, as version `23.3`_ allows for setting their values in the ``GlobalConfiguration``.
73+
This convenience extends to **Header** and **Region** as well.
74+
However, an alternative is still being sought for **TtlSeconds**, which must be explicitly set for each route to enable caching.
75+
76+
Notes
77+
-----
78+
5179
If you look at the example `here <https://github.com/ThreeMammals/Ocelot/blob/main/test/Ocelot.ManualTest/Program.cs>`_ you can see how the cache manager is setup and then passed into the Ocelot ``AddCacheManager`` configuration method.
5280
You can use any settings supported by the **CacheManager** package and just pass them in.
5381

54-
Anyway, Ocelot currently supports caching on the URL of the downstream service and setting a TTL in seconds to expire the cache. You can also clear the cache for a region by calling Ocelot's administration API.
82+
Anyway, Ocelot currently supports caching on the URL of the downstream service and setting a TTL in seconds to expire the cache.
83+
You can also clear the cache for a region by calling Ocelot's administration API.
5584

5685
Your Own Caching
5786
----------------
@@ -68,3 +97,6 @@ If you want to add your own caching method, implement the following interfaces a
6897
Please dig into the Ocelot source code to find more.
6998
We would really appreciate it if anyone wants to implement `Redis <https://redis.io/>`_, `Memcached <http://www.memcached.org/>`_ etc.
7099
Please, open a new `Show and tell <https://github.com/ThreeMammals/Ocelot/discussions/categories/show-and-tell>`_ thread in `Discussions <https://github.com/ThreeMammals/Ocelot/discussions>`_ space of the repository.
100+
101+
.. _23.0: https://github.com/ThreeMammals/Ocelot/releases/tag/23.0.0
102+
.. _23.3: https://github.com/ThreeMammals/Ocelot/releases/tag/23.3.0

src/Ocelot/Cache/AspMemoryCache.cs renamed to src/Ocelot/Cache/DefaultMemoryCache.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
namespace Ocelot.Cache
44
{
5-
public class AspMemoryCache<T> : IOcelotCache<T>
5+
public class DefaultMemoryCache<T> : IOcelotCache<T>
66
{
77
private readonly IMemoryCache _memoryCache;
88
private readonly Dictionary<string, List<string>> _regions;
99

10-
public AspMemoryCache(IMemoryCache memoryCache)
10+
public DefaultMemoryCache(IMemoryCache memoryCache)
1111
{
1212
_memoryCache = memoryCache;
1313
_regions = new Dictionary<string, List<string>>();

src/Ocelot/Cache/IRegionCreator.cs

Lines changed: 0 additions & 9 deletions
This file was deleted.

src/Ocelot/Cache/RegionCreator.cs

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/Ocelot/Configuration/Builder/DownstreamRouteBuilder.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class DownstreamRouteBuilder
1919
private List<ClaimToThing> _claimToDownstreamPath;
2020
private string _requestIdHeaderKey;
2121
private bool _isCached;
22-
private CacheOptions _fileCacheOptions;
22+
private CacheOptions _cacheOptions;
2323
private string _downstreamScheme;
2424
private LoadBalancerOptions _loadBalancerOptions;
2525
private QoSOptions _qosOptions;
@@ -87,7 +87,7 @@ public DownstreamRouteBuilder WithUpstreamPathTemplate(UpstreamPathTemplate inpu
8787

8888
public DownstreamRouteBuilder WithUpstreamHttpMethod(List<string> input)
8989
{
90-
_upstreamHttpMethod = (input.Count == 0) ? new List<HttpMethod>() : input.Select(x => new HttpMethod(x.Trim())).ToList();
90+
_upstreamHttpMethod = input.Count == 0 ? new List<HttpMethod>() : input.Select(x => new HttpMethod(x.Trim())).ToList();
9191
return this;
9292
}
9393

@@ -147,7 +147,7 @@ public DownstreamRouteBuilder WithIsCached(bool input)
147147

148148
public DownstreamRouteBuilder WithCacheOptions(CacheOptions input)
149149
{
150-
_fileCacheOptions = input;
150+
_cacheOptions = input;
151151
return this;
152152
}
153153

@@ -276,7 +276,7 @@ public DownstreamRoute Build()
276276
_downstreamScheme,
277277
_requestIdHeaderKey,
278278
_isCached,
279-
_fileCacheOptions,
279+
_cacheOptions,
280280
_loadBalancerOptions,
281281
_rateLimitOptions,
282282
_routeClaimRequirement,
Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,42 @@
11
using Ocelot.Request.Middleware;
22

3-
namespace Ocelot.Configuration
4-
{
5-
public class CacheOptions
6-
{
7-
internal CacheOptions() { }
3+
namespace Ocelot.Configuration;
84

9-
public CacheOptions(int ttlSeconds, string region, string header)
10-
{
11-
TtlSeconds = ttlSeconds;
12-
Region = region;
13-
Header = header;
14-
}
15-
16-
public CacheOptions(int ttlSeconds, string region, string header, bool enableContentHashing)
17-
{
18-
TtlSeconds = ttlSeconds;
19-
Region = region;
20-
Header = header;
21-
EnableContentHashing = enableContentHashing;
22-
}
5+
public class CacheOptions
6+
{
7+
internal CacheOptions() { }
238

24-
public int TtlSeconds { get; }
25-
public string Region { get; }
26-
public string Header { get; }
27-
28-
/// <summary>
29-
/// Enables MD5 hash calculation of the <see cref="HttpRequestMessage.Content"/> of the <see cref="DownstreamRequest.Request"/> object.
30-
/// </summary>
31-
/// <remarks>
32-
/// Default value is <see langword="false"/>. No hashing by default.
33-
/// </remarks>
34-
/// <value>
35-
/// <see langword="true"/> if hashing is enabled, otherwise it is <see langword="false"/>.
36-
/// </value>
37-
public bool EnableContentHashing { get; }
9+
/// <summary>
10+
/// Initializes a new instance of the <see cref="CacheOptions"/> class.
11+
/// </summary>
12+
/// <remarks>
13+
/// Internal defaults:
14+
/// <list type="bullet">
15+
/// <item>The default value for <see cref="EnableContentHashing"/> is <see langword="false"/>, but it is set to null for route-level configuration to allow global configuration usage.</item>
16+
/// <item>The default value for <see cref="TtlSeconds"/> is 0.</item>
17+
/// </list>
18+
/// </remarks>
19+
/// <param name="ttlSeconds">Time-to-live seconds. If not speciefied, zero value is used by default.</param>
20+
/// <param name="region">The region of caching.</param>
21+
/// <param name="header">The header name to control cached value.</param>
22+
/// <param name="enableContentHashing">The switcher for content hashing. If not speciefied, false value is used by default.</param>
23+
public CacheOptions(int? ttlSeconds, string region, string header, bool? enableContentHashing)
24+
{
25+
TtlSeconds = ttlSeconds ?? 0;
26+
Region = region;
27+
Header = header;
28+
EnableContentHashing = enableContentHashing ?? false;
3829
}
39-
}
30+
31+
/// <summary>Time-to-live seconds.</summary>
32+
/// <remarks>Default value is 0. No caching by default.</remarks>
33+
/// <value>An <see cref="int"/> value of seconds.</value>
34+
public int TtlSeconds { get; }
35+
public string Region { get; }
36+
public string Header { get; }
37+
38+
/// <summary>Enables MD5 hash calculation of the <see cref="HttpRequestMessage.Content"/> of the <see cref="DownstreamRequest.Request"/> object.</summary>
39+
/// <remarks>Default value is <see langword="false"/>. No hashing by default.</remarks>
40+
/// <value><see langword="true"/> if hashing is enabled, otherwise it is <see langword="false"/>.</value>
41+
public bool EnableContentHashing { get; }
42+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using Ocelot.Configuration.File;
2+
3+
namespace Ocelot.Configuration.Creator;
4+
5+
public class CacheOptionsCreator : ICacheOptionsCreator
6+
{
7+
public CacheOptions Create(FileCacheOptions options, FileGlobalConfiguration global, string upstreamPathTemplate, IList<string> upstreamHttpMethods)
8+
{
9+
var region = GetRegion(options.Region ?? global?.CacheOptions.Region, upstreamPathTemplate, upstreamHttpMethods);
10+
var header = options.Header ?? global?.CacheOptions.Header;
11+
var ttlSeconds = options.TtlSeconds ?? global?.CacheOptions.TtlSeconds;
12+
var enableContentHashing = options.EnableContentHashing ?? global?.CacheOptions.EnableContentHashing;
13+
14+
return new CacheOptions(ttlSeconds, region, header, enableContentHashing);
15+
}
16+
17+
protected virtual string GetRegion(string region, string upstreamPathTemplate, IList<string> upstreamHttpMethod)
18+
{
19+
if (!string.IsNullOrEmpty(region))
20+
{
21+
return region;
22+
}
23+
24+
var methods = string.Join(string.Empty, upstreamHttpMethod);
25+
return $"{methods}{upstreamPathTemplate.Replace("/", string.Empty)}";
26+
}
27+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Ocelot.Configuration.File;
2+
3+
namespace Ocelot.Configuration.Creator;
4+
5+
/// <summary>
6+
/// This interface is used to create cache options.
7+
/// </summary>
8+
public interface ICacheOptionsCreator
9+
{
10+
/// <summary>
11+
/// Creates cache options based on the file cache options, upstream path template and upstream HTTP methods.</summary>
12+
/// <remarks>Upstream path template and upstream HTTP methods are used to get the region name.</remarks>
13+
/// <param name="options">The file cache options.</param>
14+
/// <param name="global">The global configuration.</param>
15+
/// <param name="upstreamPathTemplate">The upstream path template as string.</param>
16+
/// <param name="upstreamHttpMethods">The upstream http methods as a list of strings.</param>
17+
/// <returns>The generated cache options.</returns>
18+
CacheOptions Create(FileCacheOptions options, FileGlobalConfiguration global, string upstreamPathTemplate, IList<string> upstreamHttpMethods);
19+
}

src/Ocelot/Configuration/Creator/RouteOptionsCreator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public RouteOptions Create(FileRoute fileRoute)
1717
&& (!string.IsNullOrEmpty(authOpts.AuthenticationProviderKey)
1818
|| authOpts.AuthenticationProviderKeys?.Any(k => !string.IsNullOrWhiteSpace(k)) == true);
1919
var isAuthorized = fileRoute.RouteClaimsRequirement?.Any() == true;
20+
21+
// TODO: This sounds more like a hack, it might be better to refactor this at some point.
2022
var isCached = fileRoute.FileCacheOptions.TtlSeconds > 0;
2123
var enableRateLimiting = fileRoute.RateLimitOptions?.EnableRateLimiting == true;
2224
var useServiceDiscovery = !string.IsNullOrEmpty(fileRoute.ServiceName);

src/Ocelot/Configuration/Creator/RoutesCreator.cs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using Ocelot.Cache;
2-
using Ocelot.Configuration.Builder;
1+
using Ocelot.Configuration.Builder;
32
using Ocelot.Configuration.File;
43

54
namespace Ocelot.Configuration.Creator
@@ -14,7 +13,7 @@ public class RoutesCreator : IRoutesCreator
1413
private readonly IQoSOptionsCreator _qosOptionsCreator;
1514
private readonly IRouteOptionsCreator _fileRouteOptionsCreator;
1615
private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator;
17-
private readonly IRegionCreator _regionCreator;
16+
private readonly ICacheOptionsCreator _cacheOptionsCreator;
1817
private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
1918
private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator;
2019
private readonly IDownstreamAddressesCreator _downstreamAddressesCreator;
@@ -30,21 +29,20 @@ public RoutesCreator(
3029
IQoSOptionsCreator qosOptionsCreator,
3130
IRouteOptionsCreator fileRouteOptionsCreator,
3231
IRateLimitOptionsCreator rateLimitOptionsCreator,
33-
IRegionCreator regionCreator,
32+
ICacheOptionsCreator cacheOptionsCreator,
3433
IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
3534
IHeaderFindAndReplaceCreator headerFAndRCreator,
3635
IDownstreamAddressesCreator downstreamAddressesCreator,
3736
ILoadBalancerOptionsCreator loadBalancerOptionsCreator,
3837
IRouteKeyCreator routeKeyCreator,
3938
ISecurityOptionsCreator securityOptionsCreator,
40-
IVersionCreator versionCreator
41-
)
39+
IVersionCreator versionCreator)
4240
{
4341
_routeKeyCreator = routeKeyCreator;
4442
_loadBalancerOptionsCreator = loadBalancerOptionsCreator;
4543
_downstreamAddressesCreator = downstreamAddressesCreator;
4644
_headerFAndRCreator = headerFAndRCreator;
47-
_regionCreator = regionCreator;
45+
_cacheOptionsCreator = cacheOptionsCreator;
4846
_rateLimitOptionsCreator = rateLimitOptionsCreator;
4947
_requestIdKeyCreator = requestIdKeyCreator;
5048
_upstreamTemplatePatternCreator = upstreamTemplatePatternCreator;
@@ -93,8 +91,6 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf
9391

9492
var rateLimitOption = _rateLimitOptionsCreator.Create(fileRoute.RateLimitOptions, globalConfiguration);
9593

96-
var region = _regionCreator.Create(fileRoute);
97-
9894
var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileRoute.HttpHandlerOptions);
9995

10096
var hAndRs = _headerFAndRCreator.Create(fileRoute);
@@ -107,6 +103,8 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf
107103

108104
var downstreamHttpVersion = _versionCreator.Create(fileRoute.DownstreamHttpVersion);
109105

106+
var cacheOptions = _cacheOptionsCreator.Create(fileRoute.FileCacheOptions, globalConfiguration, fileRoute.UpstreamPathTemplate, fileRoute.UpstreamHttpMethod);
107+
110108
var route = new DownstreamRouteBuilder()
111109
.WithKey(fileRoute.Key)
112110
.WithDownstreamPathTemplate(fileRoute.DownstreamPathTemplate)
@@ -122,7 +120,7 @@ private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConf
122120
.WithClaimsToDownstreamPath(claimsToDownstreamPath)
123121
.WithRequestIdKey(requestIdKey)
124122
.WithIsCached(fileRouteOptions.IsCached)
125-
.WithCacheOptions(new CacheOptions(fileRoute.FileCacheOptions.TtlSeconds, region, fileRoute.FileCacheOptions.Header))
123+
.WithCacheOptions(cacheOptions)
126124
.WithDownstreamScheme(fileRoute.DownstreamScheme)
127125
.WithLoadBalancerOptions(lbOptions)
128126
.WithDownstreamAddresses(downstreamAddresses)
Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
1-
namespace Ocelot.Configuration.File
2-
{
3-
public class FileCacheOptions
4-
{
5-
public FileCacheOptions()
6-
{
7-
Region = string.Empty;
8-
TtlSeconds = 0;
9-
}
1+
namespace Ocelot.Configuration.File;
102

11-
public FileCacheOptions(FileCacheOptions from)
12-
{
13-
Region = from.Region;
14-
TtlSeconds = from.TtlSeconds;
15-
}
3+
public class FileCacheOptions
4+
{
5+
public FileCacheOptions() { }
166

17-
public int TtlSeconds { get; set; }
18-
public string Region { get; set; }
19-
public string Header { get; set; }
20-
}
7+
public FileCacheOptions(FileCacheOptions from)
8+
{
9+
Region = from.Region;
10+
TtlSeconds = from.TtlSeconds;
11+
Header = from.Header;
12+
EnableContentHashing = from.EnableContentHashing;
13+
}
14+
15+
/// <summary>Using <see cref="Nullable{T}"/> where T is <see cref="int"/> to have <see langword="null"/> as default value and allowing global configuration usage.</summary>
16+
/// <remarks>If <see langword="null"/> then use global configuration with 0 by default.</remarks>
17+
/// <value>The time to live seconds, with 0 by default.</value>
18+
public int? TtlSeconds { get; set; }
19+
public string Region { get; set; }
20+
public string Header { get; set; }
21+
22+
/// <summary>Using <see cref="Nullable{T}"/> where T is <see cref="bool"/> to have <see langword="null"/> as default value and allowing global configuration usage.</summary>
23+
/// <remarks>If <see langword="null"/> then use global configuration with <see langword="false"/> by default.</remarks>
24+
/// <value><see langword="true"/> if content hashing is enabled; otherwise, <see langword="false"/>.</value>
25+
public bool? EnableContentHashing { get; set; }
2126
}

0 commit comments

Comments
 (0)