diff --git a/src/Ocelot/Cache/CachedResponse.cs b/src/Ocelot/Cache/CachedResponse.cs index 02b327b39..e4360e0d2 100644 --- a/src/Ocelot/Cache/CachedResponse.cs +++ b/src/Ocelot/Cache/CachedResponse.cs @@ -6,13 +6,16 @@ namespace Ocelot.Cache public class CachedResponse { public CachedResponse( - HttpStatusCode statusCode = HttpStatusCode.OK, - Dictionary> headers = null, - string body = null + HttpStatusCode statusCode, + Dictionary> headers, + string body, + Dictionary> contentHeaders + ) { StatusCode = statusCode; Headers = headers ?? new Dictionary>(); + ContentHeaders = contentHeaders ?? new Dictionary>(); Body = body ?? ""; } @@ -20,6 +23,8 @@ public CachedResponse( public Dictionary> Headers { get; private set; } + public Dictionary> ContentHeaders { get; private set; } + public string Body { get; private set; } } } diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 717283f54..07f7445e1 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -82,13 +82,21 @@ internal HttpResponseMessage CreateHttpResponseMessage(CachedResponse cached) } var response = new HttpResponseMessage(cached.StatusCode); + foreach (var header in cached.Headers) { response.Headers.Add(header.Key, header.Value); } + var content = new MemoryStream(Convert.FromBase64String(cached.Body)); + response.Content = new StreamContent(content); + foreach (var header in cached.ContentHeaders) + { + response.Content.Headers.Add(header.Key, header.Value); + } + return response; } @@ -109,7 +117,10 @@ internal async Task CreateCachedResponse(HttpResponseMessage res body = Convert.ToBase64String(content); } - var cached = new CachedResponse(statusCode, headers, body); + var contentHeaders = response?.Content?.Headers.ToDictionary(v => v.Key, v => v.Value); + + + var cached = new CachedResponse(statusCode, headers, body, contentHeaders); return cached; } } diff --git a/test/Ocelot.AcceptanceTests/CachingTests.cs b/test/Ocelot.AcceptanceTests/CachingTests.cs index 244a872f0..755237a59 100644 --- a/test/Ocelot.AcceptanceTests/CachingTests.cs +++ b/test/Ocelot.AcceptanceTests/CachingTests.cs @@ -61,6 +61,7 @@ public void should_return_cached_response() .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(x => _steps.ThenTheContentLengthIs(16)) .BDDfy(); } diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index bcd017b4f..218e58e73 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -457,5 +457,10 @@ public void ThenTheRequestIdIsReturned(string expected) { _response.Headers.GetValues(RequestIdKey).First().ShouldBe(expected); } + + public void ThenTheContentLengthIs(int expected) + { + _response.Content.Headers.ContentLength.ShouldBe(expected); + } } } diff --git a/test/Ocelot.ManualTest/ManualTestStartup.cs b/test/Ocelot.ManualTest/ManualTestStartup.cs index dd5cf9797..d23731c14 100644 --- a/test/Ocelot.ManualTest/ManualTestStartup.cs +++ b/test/Ocelot.ManualTest/ManualTestStartup.cs @@ -34,4 +34,4 @@ public void Configure(IApplicationBuilder app) app.UseOcelot().Wait(); } } -} \ No newline at end of file +} diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index 45f184bcf..93bf6ac66 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -76,8 +76,7 @@ "UpstreamHttpMethod": [ "Get" ], "HttpHandlerOptions": { "AllowAutoRedirect": true, - "UseCookieContainer": true, - "UseTracing": true + "UseCookieContainer": true }, "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs new file mode 100644 index 000000000..70619c6b0 --- /dev/null +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs @@ -0,0 +1,131 @@ +using Ocelot.Infrastructure.RequestData; + +namespace Ocelot.UnitTests.Cache +{ + using System.Linq; + using System.Net; + using System.Net.Http.Headers; + using CacheManager.Core; + using Shouldly; + using System.Collections.Generic; + using System.Net.Http; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Cache; + using Ocelot.Cache.Middleware; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + + public class OutputCacheMiddlewareRealCacheTests : ServerHostedMiddlewareTest + { + private IOcelotCache _cacheManager; + private CachedResponse _response; + private IRequestScopedDataRepository _repo; + + public OutputCacheMiddlewareRealCacheTests() + { + ScopedRepository + .Setup(sr => sr.Get("DownstreamRequest")) + .Returns(new OkResponse(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"))); + + GivenTheTestServerIsConfigured(); + } + + [Fact] + public void should_cache_content_headers() + { + var content = new StringContent("{\"Test\": 1}") + { + Headers = { ContentType = new MediaTypeHeaderValue("application/json")} + }; + + var response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = content, + }; + + this.Given(x => x.GivenResponseIsNotCached(response)) + .And(x => x.GivenTheDownstreamRouteIs()) + .And(x => x.GivenThereAreNoErrors()) + .And(x => x.GivenThereIsADownstreamUrl()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheContentTypeHeaderIsCached()) + .BDDfy(); + } + + private void ThenTheContentTypeHeaderIsCached() + { + var result = _cacheManager.Get("GET-https://some.url/blah?abcd=123", "kanken"); + var header = result.ContentHeaders["Content-Type"]; + header.First().ShouldBe("application/json"); + } + + protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services) + { + var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", x => + { + x.WithDictionaryHandle(); + }); + + _cacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); + + services.AddSingleton>(cacheManagerOutputCache); + services.AddSingleton>(_cacheManager); + + services.AddSingleton(); + + services.AddLogging(); + services.AddSingleton(_cacheManager); + services.AddSingleton(ScopedRepository.Object); + services.AddSingleton(); + } + + protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) + { + app.UseOutputCacheMiddleware(); + } + + private void GivenResponseIsNotCached(HttpResponseMessage message) + { + ScopedRepository + .Setup(x => x.Get("HttpResponseMessage")) + .Returns(new OkResponse(message)); + } + + private void GivenTheDownstreamRouteIs() + { + var reRoute = new ReRouteBuilder() + .WithIsCached(true) + .WithCacheOptions(new CacheOptions(100, "kanken")) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build(); + + var downstreamRoute = new DownstreamRoute(new List(), reRoute); + + ScopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(new OkResponse(downstreamRoute)); + } + + private void GivenThereAreNoErrors() + { + ScopedRepository + .Setup(x => x.Get("OcelotMiddlewareError")) + .Returns(new OkResponse(false)); + } + + private void GivenThereIsADownstreamUrl() + { + ScopedRepository + .Setup(x => x.Get("DownstreamUrl")) + .Returns(new OkResponse("anything")); + } + } +} diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index 71839a5a1..2a60e3768 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -1,4 +1,6 @@ -namespace Ocelot.UnitTests.Cache +using System.Net; + +namespace Ocelot.UnitTests.Cache { using System; using System.Collections.Generic; @@ -36,7 +38,7 @@ public OutputCacheMiddlewareTests() [Fact] public void should_returned_cached_item_when_it_is_in_cache() { - var cachedResponse = new CachedResponse(); + var cachedResponse = new CachedResponse(HttpStatusCode.OK, new Dictionary>(), "", new Dictionary>()); this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) .And(x => x.GivenTheDownstreamRouteIs()) .And(x => x.GivenThereIsADownstreamUrl())