Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/Ocelot/Cache/OutputCacheMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,16 @@ public async Task Invoke(HttpContext httpContext)
cached = await CreateCachedResponse(downstreamResponse);

var ttl = TimeSpan.FromSeconds(options.TtlSeconds);
_outputCache.Add(downStreamRequestCacheKey, cached, options.Region, ttl);
Logger.LogDebug(() => $"Finished response added to cache for the '{downstreamUrlKey}' key.");
if (options.StatusCodes == null || options.StatusCodes.Contains((int)cached.StatusCode))
{
_outputCache.Add(downStreamRequestCacheKey, cached, options.Region, ttl);
Logger.LogDebug(() => $"Finished response added to cache for the '{downstreamUrlKey}' key.");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please wrap the logging for the Debug scenario with a preprocessor directive, as another team member will likely request this 👇

Suggested change
Logger.LogDebug(() => $"Finished response added to cache for the '{downstreamUrlKey}' key.");
#if DEBUG
Logger.LogDebug(() => $"Finished response added to cache for the '{downstreamUrlKey}' key.");
#endif

}
else
{
Logger.LogDebug(() => $"Finished response filtered out from being added to cache due to response status for the '{downstreamUrlKey}' key.");
}

}

private static void SetHttpResponseMessageThisRequest(HttpContext context, DownstreamResponse response)
Expand Down
13 changes: 11 additions & 2 deletions src/Ocelot/Configuration/CacheOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ public class CacheOptions

internal CacheOptions() { }
public CacheOptions(FileCacheOptions from, string defaultRegion)
: this(from.TtlSeconds, from.Region.IfEmpty(defaultRegion), from.Header, from.EnableContentHashing)
: this(from.TtlSeconds, from.Region.IfEmpty(defaultRegion), from.Header, from.EnableContentHashing, from.StatusCodes)
{ }

public CacheOptions(int ttlSeconds, string region, string header, bool? enableContentHashing)
: this(ttlSeconds, region, header, enableContentHashing, null)
{ }


/// <summary>
/// Initializes a new instance of the <see cref="CacheOptions"/> class.
/// </summary>
Expand All @@ -32,12 +37,13 @@ public CacheOptions(FileCacheOptions from, string defaultRegion)
/// <param name="region">The region of caching.</param>
/// <param name="header">The header name to control cached value.</param>
/// <param name="enableContentHashing">The switcher for content hashing. If not speciefied, false value is used by default.</param>
public CacheOptions(int? ttlSeconds, string region, string header, bool? enableContentHashing)
public CacheOptions(int? ttlSeconds, string region, string header, bool? enableContentHashing, int[] statusCodes)
{
TtlSeconds = ttlSeconds ?? NoSeconds;
Region = region;
Header = header.IfEmpty(Oc_Cache_Control);
EnableContentHashing = enableContentHashing ?? false;
StatusCodes = statusCodes;
Comment thread
raman-m marked this conversation as resolved.
Outdated
}

/// <summary>Time-to-live seconds.</summary>
Expand All @@ -53,4 +59,7 @@ public CacheOptions(int? ttlSeconds, string region, string header, bool? enableC
public bool EnableContentHashing { get; }

public bool UseCache => TtlSeconds > NoSeconds;

//public HttpStatusCode[] StatusCodes { get; }
public int[] StatusCodes { get; }
Comment thread
raman-m marked this conversation as resolved.
Outdated
}
5 changes: 3 additions & 2 deletions src/Ocelot/Configuration/Creator/CacheOptionsCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Ocelot.Configuration.Creator;
public class CacheOptionsCreator : ICacheOptionsCreator
{
public CacheOptions Create(FileCacheOptions options)
=> new(options?.TtlSeconds, options?.Region, options?.Header, options?.EnableContentHashing);
=> new(options?.TtlSeconds, options?.Region, options?.Header, options?.EnableContentHashing, options?.StatusCodes);

public CacheOptions Create(FileRoute route, FileGlobalConfiguration globalConfiguration, string loadBalancingKey)
{
Expand Down Expand Up @@ -58,6 +58,7 @@ protected virtual CacheOptions Merge(FileCacheOptions options, FileCacheOptions
var header = options.Header.IfEmpty(globalOptions.Header).IfEmpty(CacheOptions.Oc_Cache_Control);
var ttlSeconds = options.TtlSeconds ?? globalOptions.TtlSeconds;
var enableHashing = options.EnableContentHashing ?? globalOptions.EnableContentHashing;
return new CacheOptions(ttlSeconds, region, header, enableHashing);
var statusCodes = options.StatusCodes ?? globalOptions.StatusCodes;
return new CacheOptions(ttlSeconds, region, header, enableHashing, statusCodes);
}
}
3 changes: 3 additions & 0 deletions src/Ocelot/Configuration/File/FileCacheOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ public FileCacheOptions(FileCacheOptions from)
/// <remarks>If <see langword="null"/> then use global configuration with <see langword="false"/> by default.</remarks>
/// <value><see langword="true"/> if content hashing is enabled; otherwise, <see langword="false"/>.</value>
public bool? EnableContentHashing { get; set; }

//public HttpStatusCode[] StatusCodes { get; set; }
Comment thread
raman-m marked this conversation as resolved.
public int[] StatusCodes { get; set; }
}
64 changes: 64 additions & 0 deletions test/Ocelot.AcceptanceTests/Caching/CachingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,70 @@ public void Should_return_same_cached_response_when_request_body_changes_and_Ena
.BDDfy();
}

[Theory]
[InlineData(null, HttpStatusCode.OK)]
[InlineData(null, HttpStatusCode.Forbidden)]
[InlineData(null, HttpStatusCode.InternalServerError)]
[InlineData(null, HttpStatusCode.Unauthorized)]
[InlineData(new int[] { (int)HttpStatusCode.OK, (int)HttpStatusCode.Forbidden }, HttpStatusCode.OK)]
[InlineData(new int[] { StatusCodes.Status200OK, StatusCodes.Status403Forbidden }, HttpStatusCode.Forbidden)]
[Trait("Feat", "741")]
public void Should_cache_when_whitelisted(int[] statusCodes, HttpStatusCode responseCode)
{
// Arrange
var port = PortFinder.GetRandomPort();
var options = new FileCacheOptions
{
TtlSeconds = 100,
StatusCodes = statusCodes,
};
var (testBody1String, testBody2String) = TestBodiesFactory();
var configuration = GivenFileConfiguration(port, options);

this.Given(x => x.GivenThereIsAServiceRunningOn(port, responseCode, HelloLauraContent, null, null))
.And(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenOcelotIsRunning())
.When(x => WhenIGetUrlOnTheApiGateway("/"))
.Then(x => ThenTheStatusCodeShouldBe(responseCode))
.And(x => ThenTheResponseBodyShouldBe(HelloLauraContent))
.Given(x => x.GivenTheServiceNowReturns(port, responseCode, HelloTomContent, null, null))
.When(x => WhenIGetUrlOnTheApiGateway("/"))
.Then(x => ThenTheStatusCodeShouldBe(responseCode))
.And(x => ThenTheResponseBodyShouldBe(HelloLauraContent))
.And(x => ThenTheContentLengthIs(HelloLauraContent.Length))
.BDDfy();
}

[Theory]
[InlineData(new int[] { StatusCodes.Status200OK, StatusCodes.Status403Forbidden }, HttpStatusCode.InternalServerError)]
[InlineData(new int[] { StatusCodes.Status200OK, StatusCodes.Status403Forbidden }, HttpStatusCode.BadRequest)]
[Trait("Feat", "741")]
public void Should_not_cache_when_not_whitelisted(int[] statusCodes, HttpStatusCode responseCode)
{
// Arrange
var port = PortFinder.GetRandomPort();
var options = new FileCacheOptions
{
TtlSeconds = 100,
StatusCodes = statusCodes,
};
var (testBody1String, testBody2String) = TestBodiesFactory();
var configuration = GivenFileConfiguration(port, options);

this.Given(x => x.GivenThereIsAServiceRunningOn(port, responseCode, HelloLauraContent, null, null))
.And(x => GivenThereIsAConfiguration(configuration))
.And(x => GivenOcelotIsRunning())
.When(x => WhenIGetUrlOnTheApiGateway("/"))
.Then(x => ThenTheStatusCodeShouldBe(responseCode))
.And(x => ThenTheResponseBodyShouldBe(HelloLauraContent))
.Given(x => x.GivenTheServiceNowReturns(port, responseCode, HelloTomContent, null, null))
.When(x => WhenIGetUrlOnTheApiGateway("/"))
.Then(x => ThenTheStatusCodeShouldBe(responseCode))
.And(x => ThenTheResponseBodyShouldBe(HelloTomContent))
.And(x => ThenTheContentLengthIs(HelloTomContent.Length))
.BDDfy();
}

[Fact]
[Trait("Issue", "1172")]
public void Should_clean_cached_response_by_cache_header_via_new_caching_key()
Expand Down
38 changes: 38 additions & 0 deletions test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,44 @@ private void ThenTheMessageIs(string expected)
Assert.Equal(expected, msg);
}

[Theory]
[InlineData(null, HttpStatusCode.OK)]
[InlineData(null, HttpStatusCode.Forbidden)]
[InlineData(null, HttpStatusCode.InternalServerError)]
[InlineData(null, HttpStatusCode.Unauthorized)]
[InlineData(new int[] { StatusCodes.Status200OK, StatusCodes.Status403Forbidden }, HttpStatusCode.OK)]
[InlineData(new int[] { StatusCodes.Status200OK, StatusCodes.Status403Forbidden }, HttpStatusCode.Forbidden)]
public async Task Should_cache_when_whitelisted(int[] statusCodes, HttpStatusCode responseCode)
{
// Arrange
var response = new HttpResponseMessage(responseCode);
GivenResponseIsNotCached(response);
GivenTheDownstreamRouteIs(new CacheOptions(100, "kanken", null, false, statusCodes));

// Act
await WhenICallTheMiddlewareAsync();

// Assert
ThenTheCacheAddIsCalled(Times.Once);
}

[Theory]
[InlineData(new int[] { StatusCodes.Status200OK, StatusCodes.Status403Forbidden }, HttpStatusCode.InternalServerError)]
[InlineData(new int[] { StatusCodes.Status200OK, StatusCodes.Status403Forbidden }, HttpStatusCode.BadRequest)]
public async Task Should_not_cache_when_not_whitelisted(int[] statusCodes, HttpStatusCode responseCode)
{
// Arrange
var response = new HttpResponseMessage(responseCode);
GivenResponseIsNotCached(response);
GivenTheDownstreamRouteIs(new CacheOptions(100, "kanken", null, false, statusCodes));

// Act
await WhenICallTheMiddlewareAsync();

// Assert
ThenTheCacheAddIsCalled(Times.Never);
}

private async Task WhenICallTheMiddlewareAsync()
{
_middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cache.Object, _cacheGenerator.Object);
Expand Down