From 47afc850ff1aa48f4ec0d88ec5a309c02992c7f6 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Fri, 4 Nov 2016 15:05:59 +0000 Subject: [PATCH] Added some basic cache stuff --- README.md | 13 +- configuration-explanation.txt | 8 +- src/Ocelot/Cache/IOcelotCache.cs | 10 ++ .../Cache/Middleware/OutputCacheMiddleware.cs | 69 ++++++++ .../OutputCacheMiddlewareExtensions.cs | 12 ++ src/Ocelot/Cache/OcelotCacheManagerCache.cs | 25 +++ .../Configuration/Builder/ReRouteBuilder.cs | 19 +- src/Ocelot/Configuration/CacheOptions.cs | 12 ++ .../Creator/FileOcelotConfigurationCreator.cs | 6 +- .../Configuration/File/FileCacheOptions.cs | 7 + src/Ocelot/Configuration/File/FileReRoute.cs | 2 + src/Ocelot/Configuration/ReRoute.cs | 9 +- .../ServiceCollectionExtensions.cs | 21 ++- src/Ocelot/Middleware/OcelotMiddleware.cs | 16 ++ .../Middleware/OcelotMiddlewareExtensions.cs | 5 + .../Middleware/HttpRequesterMiddleware.cs | 15 +- src/Ocelot/Responder/HttpContextResponder.cs | 8 +- .../HttpErrorResponderMiddleware.cs | 33 +++- src/Ocelot/project.json | 5 +- test/Ocelot.AcceptanceTests/CachingTests.cs | 130 ++++++++++++++ .../CustomMiddlewareTests.cs | 162 ++++++++++++++++-- test/Ocelot.AcceptanceTests/Steps.cs | 12 ++ .../Ocelot.AcceptanceTests/configuration.json | 2 +- test/Ocelot.ManualTest/Startup.cs | 15 +- test/Ocelot.ManualTest/configuration.json | 3 +- .../Cache/CacheManagerCacheTests.cs | 77 +++++++++ .../Cache/OutputCacheMiddlewareTests.cs | 139 +++++++++++++++ .../Errors/GobalErrorHandlerTests.cs | 83 --------- .../Infrastructure/ClaimParserTests.cs | 1 - .../Requester/HttpRequesterMiddlewareTests.cs | 21 +-- .../HttpErrorResponderMiddlewareTests.cs | 8 + 31 files changed, 800 insertions(+), 148 deletions(-) create mode 100644 src/Ocelot/Cache/IOcelotCache.cs create mode 100644 src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs create mode 100644 src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs create mode 100644 src/Ocelot/Cache/OcelotCacheManagerCache.cs create mode 100644 src/Ocelot/Configuration/CacheOptions.cs create mode 100644 src/Ocelot/Configuration/File/FileCacheOptions.cs create mode 100644 test/Ocelot.AcceptanceTests/CachingTests.cs create mode 100644 test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs create mode 100644 test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs delete mode 100644 test/Ocelot.UnitTests/Errors/GobalErrorHandlerTests.cs diff --git a/README.md b/README.md index af8acf6f..4a47ff43 100644 --- a/README.md +++ b/README.md @@ -81,12 +81,21 @@ This is pretty much all you need to get going.......more to come! Ocelot uses the standard logging interfaces ILoggerFactory / ILogger as such you can use any logging provider you like such as default, nlog, serilog or whatever you want. +## Caching + +Ocelot supports some very rudimentary caching at the moment provider by the [CacheManager](http://cachemanager.net/) project. This is an amazing project +that is solving a lot of caching problems. I would reccomend using this package to cache with Ocelot. If you look at the example [here](https://github.com/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/Startup.cs) +you can see how the cache manager is setup and then passed into the Ocelot AddOcelotOutputCaching configuration method. You can +use any settings supported by the CacheManager package and just pass them in. + +Anyway Ocelot currently supports caching on the URL of the downstream service and setting a TTL in seconds to expire the cache. More to come! + ## Not supported Ocelot does not support... - - Chunked Encoding - Ocelot will always get the body size and return Content-Length header. Sorry + * Chunked Encoding - Ocelot will always get the body size and return Content-Length header. Sorry if this doesn't work for your use case! - - Fowarding a host header - The host header that you send to Ocelot will not be forwarded to + * Fowarding a host header - The host header that you send to Ocelot will not be forwarded to the downstream service. Obviously this would break everything :( diff --git a/configuration-explanation.txt b/configuration-explanation.txt index c3b6b026..1256a562 100644 --- a/configuration-explanation.txt +++ b/configuration-explanation.txt @@ -60,5 +60,11 @@ # This tells Ocelot to look for a header and use its value as a request/correlation id. # If it is set here then the id will be forwarded to the downstream service. If it # does not then it will not be forwarded - "RequestIdKey": "OcRequestId" + "RequestIdKey": "OcRequestId", + # If this is set the response from the downstream service will be cached using the key that called it. + # This gives the user a chance to influence the key by adding some random query string paramter for + # a user id or something that would get ignored by the downstream service. This is a hack and I + # intend to provide a mechanism the user can specify for the ttl caching. Also want to expand + # the caching a lot. + "FileCacheOptions": { "TtlSeconds": 15 } } \ No newline at end of file diff --git a/src/Ocelot/Cache/IOcelotCache.cs b/src/Ocelot/Cache/IOcelotCache.cs new file mode 100644 index 00000000..1ceb220c --- /dev/null +++ b/src/Ocelot/Cache/IOcelotCache.cs @@ -0,0 +1,10 @@ +using System; + +namespace Ocelot.Cache +{ + public interface IOcelotCache + { + void Add(string key, T value, TimeSpan ttl); + T Get(string key); + } +} diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs new file mode 100644 index 00000000..7fbf6424 --- /dev/null +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -0,0 +1,69 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Middleware; + +namespace Ocelot.Cache.Middleware +{ + public class OutputCacheMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + private readonly IOcelotCache _outputCache; + + public OutputCacheMiddleware(RequestDelegate next, + ILoggerFactory loggerFactory, + IRequestScopedDataRepository scopedDataRepository, + IOcelotCache outputCache) + :base(scopedDataRepository) + { + _next = next; + _outputCache = outputCache; + _logger = loggerFactory.CreateLogger(); + } + + public async Task Invoke(HttpContext context) + { + var downstreamUrlKey = DownstreamUrl; + + if (!DownstreamRoute.ReRoute.IsCached) + { + await _next.Invoke(context); + return; + } + + _logger.LogDebug("OutputCacheMiddleware.Invoke stared checking cache for downstreamUrlKey", downstreamUrlKey); + + var cached = _outputCache.Get(downstreamUrlKey); + + if (cached != null) + { + SetHttpResponseMessageThisRequest(cached); + + _logger.LogDebug("OutputCacheMiddleware.Invoke finished returned cached response for downstreamUrlKey", downstreamUrlKey); + + return; + } + + _logger.LogDebug("OutputCacheMiddleware.Invoke no resonse cached for downstreamUrlKey", downstreamUrlKey); + + await _next.Invoke(context); + + if (PipelineError()) + { + _logger.LogDebug("OutputCacheMiddleware.Invoke there was a pipeline error for downstreamUrlKey", downstreamUrlKey); + + return; + } + + var response = HttpResponseMessage; + + _outputCache.Add(downstreamUrlKey, response, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.FileCacheOptions.TtlSeconds)); + + _logger.LogDebug("OutputCacheMiddleware.Invoke finished response added to cache for downstreamUrlKey", downstreamUrlKey); + } + } +} diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs new file mode 100644 index 00000000..76e406ee --- /dev/null +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.Cache.Middleware +{ + public static class OutputCacheMiddlewareExtensions + { + public static IApplicationBuilder UseOutputCacheMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/src/Ocelot/Cache/OcelotCacheManagerCache.cs b/src/Ocelot/Cache/OcelotCacheManagerCache.cs new file mode 100644 index 00000000..84bdc679 --- /dev/null +++ b/src/Ocelot/Cache/OcelotCacheManagerCache.cs @@ -0,0 +1,25 @@ +using System; +using CacheManager.Core; + +namespace Ocelot.Cache +{ + public class OcelotCacheManagerCache : IOcelotCache + { + private readonly ICacheManager _cacheManager; + + public OcelotCacheManagerCache(ICacheManager cacheManager) + { + _cacheManager = cacheManager; + } + + public void Add(string key, T value, TimeSpan ttl) + { + _cacheManager.Add(new CacheItem(key, value, ExpirationMode.Absolute, ttl)); + } + + public T Get(string key) + { + return _cacheManager.Get(key); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index d47675de..f77ae66b 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -21,6 +21,8 @@ namespace Ocelot.Configuration.Builder private bool _isAuthorised; private List _claimToQueries; private string _requestIdHeaderKey; + private bool _isCached; + private CacheOptions _fileCacheOptions; public ReRouteBuilder() { @@ -127,9 +129,24 @@ namespace Ocelot.Configuration.Builder return this; } + public ReRouteBuilder WithIsCached(bool input) + { + _isCached = input; + return this; + } + + public ReRouteBuilder WithCacheOptions(CacheOptions input) + { + _fileCacheOptions = input; + return this; + } + public ReRoute Build() { - return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised, _claimToQueries, _requestIdHeaderKey); + return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, + _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, + _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, + _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions); } } } diff --git a/src/Ocelot/Configuration/CacheOptions.cs b/src/Ocelot/Configuration/CacheOptions.cs new file mode 100644 index 00000000..2fdaf2bb --- /dev/null +++ b/src/Ocelot/Configuration/CacheOptions.cs @@ -0,0 +1,12 @@ +namespace Ocelot.Configuration +{ + public class CacheOptions + { + public CacheOptions(int ttlSeconds) + { + TtlSeconds = ttlSeconds; + } + + public int TtlSeconds { get; private set; } + } +} diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 510eb25a..b777daca 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -100,6 +100,8 @@ namespace Ocelot.Configuration.Creator var isAuthorised = reRoute.RouteClaimsRequirement?.Count > 0; + var isCached = reRoute.FileCacheOptions.TtlSeconds > 0; + if (isAuthenticated) { var authOptionsForRoute = new AuthenticationOptions(reRoute.AuthenticationOptions.Provider, @@ -115,7 +117,7 @@ namespace Ocelot.Configuration.Creator reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, authOptionsForRoute, claimsToHeaders, claimsToClaims, reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, - reRoute.RequestIdKey + reRoute.RequestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds) ); } @@ -123,7 +125,7 @@ namespace Ocelot.Configuration.Creator reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, null, new List(), new List(), reRoute.RouteClaimsRequirement, isAuthorised, new List(), - reRoute.RequestIdKey); + reRoute.RequestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds)); } private List GetAddThingsToRequest(Dictionary thingBeingAdded) diff --git a/src/Ocelot/Configuration/File/FileCacheOptions.cs b/src/Ocelot/Configuration/File/FileCacheOptions.cs new file mode 100644 index 00000000..3f86006b --- /dev/null +++ b/src/Ocelot/Configuration/File/FileCacheOptions.cs @@ -0,0 +1,7 @@ +namespace Ocelot.Configuration.File +{ + public class FileCacheOptions + { + public int TtlSeconds { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 78a4d137..3547cf99 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -11,6 +11,7 @@ namespace Ocelot.Configuration.File RouteClaimsRequirement = new Dictionary(); AddQueriesToRequest = new Dictionary(); AuthenticationOptions = new FileAuthenticationOptions(); + FileCacheOptions = new FileCacheOptions(); } public string DownstreamTemplate { get; set; } @@ -22,5 +23,6 @@ namespace Ocelot.Configuration.File public Dictionary RouteClaimsRequirement { get; set; } public Dictionary AddQueriesToRequest { get; set; } public string RequestIdKey { get; set; } + public FileCacheOptions FileCacheOptions { get; set; } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 0caf972a..433250b3 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -4,7 +4,10 @@ namespace Ocelot.Configuration { public class ReRoute { - public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries, string requestIdKey) + public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, + bool isAuthenticated, AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, + List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries, + string requestIdKey, bool isCached, CacheOptions fileCacheOptions) { DownstreamTemplate = downstreamTemplate; UpstreamTemplate = upstreamTemplate; @@ -15,6 +18,8 @@ namespace Ocelot.Configuration RouteClaimsRequirement = routeClaimsRequirement; IsAuthorised = isAuthorised; RequestIdKey = requestIdKey; + IsCached = isCached; + FileCacheOptions = fileCacheOptions; ClaimsToQueries = claimsToQueries ?? new List(); ClaimsToClaims = claimsToClaims @@ -35,5 +40,7 @@ namespace Ocelot.Configuration public List ClaimsToClaims { get; private set; } public Dictionary RouteClaimsRequirement { get; private set; } public string RequestIdKey { get; private set; } + public bool IsCached { get; private set; } + public CacheOptions FileCacheOptions { get; private set; } } } \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 4984fc91..753b93e0 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -1,9 +1,14 @@ -using Microsoft.AspNetCore.Http; +using System; +using System.Net.Http; +using CacheManager.Core; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Ocelot.Authentication.Handler.Creator; using Ocelot.Authentication.Handler.Factory; using Ocelot.Authorisation; +using Ocelot.Cache; using Ocelot.Claims; using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; @@ -26,11 +31,21 @@ namespace Ocelot.DependencyInjection { public static class ServiceCollectionExtensions { + + public static IServiceCollection AddOcelotOutputCaching(this IServiceCollection services, Action settings) + { + var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", settings); + var ocelotCacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); + + services.AddSingleton>(cacheManagerOutputCache); + services.AddSingleton>(ocelotCacheManager); + + return services; + } public static IServiceCollection AddOcelotFileConfiguration(this IServiceCollection services, IConfigurationRoot configurationRoot) { services.Configure(configurationRoot); - // ocelot services. services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -40,11 +55,9 @@ namespace Ocelot.DependencyInjection public static IServiceCollection AddOcelot(this IServiceCollection services) { - // framework services dependency for the identity server middleware services.AddMvcCore().AddJsonFormatters(); services.AddLogging(); - // ocelot services. services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/Middleware/OcelotMiddleware.cs b/src/Ocelot/Middleware/OcelotMiddleware.cs index 6cabce87..16e4ff2c 100644 --- a/src/Ocelot/Middleware/OcelotMiddleware.cs +++ b/src/Ocelot/Middleware/OcelotMiddleware.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Net.Http; using Ocelot.DownstreamRouteFinder; using Ocelot.Errors; using Ocelot.Infrastructure.RequestData; @@ -59,6 +60,15 @@ namespace Ocelot.Middleware } } + public HttpResponseMessage HttpResponseMessage + { + get + { + var request = _requestScopedDataRepository.Get("HttpResponseMessage"); + return request.Data; + } + } + public void SetDownstreamRouteForThisRequest(DownstreamRoute downstreamRoute) { _requestScopedDataRepository.Add("DownstreamRoute", downstreamRoute); @@ -73,5 +83,11 @@ namespace Ocelot.Middleware { _requestScopedDataRepository.Add("Request", request); } + + public void SetHttpResponseMessageThisRequest(HttpResponseMessage responseMessage) + { + _requestScopedDataRepository.Add("HttpResponseMessage", responseMessage); + + } } } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index cc763f3c..cbb904ce 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Builder; using Ocelot.Authentication.Middleware; +using Ocelot.Cache.Middleware; using Ocelot.Claims.Middleware; using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamUrlCreator.Middleware; @@ -99,6 +100,10 @@ namespace Ocelot.Middleware // This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used builder.UseDownstreamUrlCreatorMiddleware(); + // Not sure if this is the best place for this but we use the downstream url + // as the basis for our cache key. + builder.UseOutputCacheMiddleware(); + // Everything should now be ready to build or HttpRequest builder.UseHttpRequestBuilderMiddleware(); diff --git a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs index 6dbb28bf..3d7d37a0 100644 --- a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs +++ b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs @@ -1,10 +1,7 @@ -using System.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.Infrastructure.RequestData; using Ocelot.Middleware; -using Ocelot.Responder; namespace Ocelot.Requester.Middleware { @@ -12,17 +9,14 @@ namespace Ocelot.Requester.Middleware { private readonly RequestDelegate _next; private readonly IHttpRequester _requester; - private readonly IHttpResponder _responder; public HttpRequesterMiddleware(RequestDelegate next, IHttpRequester requester, - IRequestScopedDataRepository requestScopedDataRepository, - IHttpResponder responder) + IRequestScopedDataRepository requestScopedDataRepository) :base(requestScopedDataRepository) { _next = next; _requester = requester; - _responder = responder; } public async Task Invoke(HttpContext context) @@ -35,12 +29,7 @@ namespace Ocelot.Requester.Middleware return; } - var setResponse = await _responder.SetResponseOnHttpContext(context, response.Data); - - if (setResponse.IsError) - { - SetPipelineError(response.Errors); - } + SetHttpResponseMessageThisRequest(response.Data); } } } \ No newline at end of file diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index cd989cbd..8b61e13b 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -38,7 +38,7 @@ namespace Ocelot.Responder AddHeaderIfDoesntExist(context, httpResponseHeader); } - var content = await response.Content.ReadAsStreamAsync(); + var content = await response.Content.ReadAsByteArrayAsync(); AddHeaderIfDoesntExist(context, new KeyValuePair>("Content-Length", new []{ content.Length.ToString() }) ); @@ -52,12 +52,10 @@ namespace Ocelot.Responder }, context); - using (var reader = new StreamReader(content)) + using (Stream stream = new MemoryStream(content)) { - var responseContent = reader.ReadToEnd(); - await context.Response.WriteAsync(responseContent); + await stream.CopyToAsync(context.Response.Body); } - return new OkResponse(); } diff --git a/src/Ocelot/Responder/Middleware/HttpErrorResponderMiddleware.cs b/src/Ocelot/Responder/Middleware/HttpErrorResponderMiddleware.cs index 79967714..1cf14227 100644 --- a/src/Ocelot/Responder/Middleware/HttpErrorResponderMiddleware.cs +++ b/src/Ocelot/Responder/Middleware/HttpErrorResponderMiddleware.cs @@ -1,6 +1,7 @@ -using System.Net.Http; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Ocelot.Errors; using Ocelot.Infrastructure.RequestData; using Ocelot.Middleware; @@ -30,18 +31,32 @@ namespace Ocelot.Responder.Middleware if (PipelineError()) { var errors = GetPipelineErrors(); - - var statusCode = _codeMapper.Map(errors); - if (!statusCode.IsError) + await SetErrorResponse(context, errors); + } + else + { + var setResponse = await _responder.SetResponseOnHttpContext(context, HttpResponseMessage); + + if (setResponse.IsError) { - await _responder.SetErrorResponseOnContext(context, statusCode.Data); - } - else - { - await _responder.SetErrorResponseOnContext(context, 500); + await SetErrorResponse(context, setResponse.Errors); } } } + + private async Task SetErrorResponse(HttpContext context, List errors) + { + var statusCode = _codeMapper.Map(errors); + + if (!statusCode.IsError) + { + await _responder.SetErrorResponseOnContext(context, statusCode.Data); + } + else + { + await _responder.SetErrorResponseOnContext(context, 500); + } + } } } \ No newline at end of file diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 8f194c8a..e5f5389c 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -27,7 +27,10 @@ "Microsoft.NETCore.App": { "version": "1.0.1", "type": "platform" - } + }, + "CacheManager.Core": "0.9.1", + "CacheManager.Microsoft.Extensions.Configuration": "0.9.1", + "CacheManager.Microsoft.Extensions.Logging": "0.9.1" }, "frameworks": { diff --git a/test/Ocelot.AcceptanceTests/CachingTests.cs b/test/Ocelot.AcceptanceTests/CachingTests.cs new file mode 100644 index 00000000..34f10b7a --- /dev/null +++ b/test/Ocelot.AcceptanceTests/CachingTests.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.File; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class CachingTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + + public CachingTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_return_cached_response() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamTemplate = "http://localhost:51879/", + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 100 + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .Given(x => x.GivenTheServiceNowReturns("http://localhost:51879", 200, "Hello from Tom")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_not_return_cached_response_as_ttl_expires() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamTemplate = "http://localhost:51879/", + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 1 + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .Given(x => x.GivenTheServiceNowReturns("http://localhost:51879", 200, "Hello from Tom")) + .And(x => x.GivenTheCacheExpires()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) + .BDDfy(); + } + + private void GivenTheCacheExpires() + { + Thread.Sleep(1000); + } + + private void GivenTheServiceNowReturns(string url, int statusCode, string responseBody) + { + _builder.Dispose(); + GivenThereIsAServiceRunningOn(url, statusCode, responseBody); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) + { + _builder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _builder.Start(); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs index ec00a6af..1a267a98 100644 --- a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -2,15 +2,12 @@ using System.Collections.Generic; using System.IO; using System.Net; -using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; using Ocelot.Configuration.File; -using Ocelot.Infrastructure.RequestData; using Ocelot.Middleware; +using Shouldly; using TestStack.BDDfy; using Xunit; @@ -21,21 +18,160 @@ namespace Ocelot.AcceptanceTests private readonly string _configurationPath; private IWebHost _builder; private readonly Steps _steps; + private int _counter; public CustomMiddlewareTests() { + _counter = 0; _steps = new Steps();; _configurationPath = "configuration.json"; } [Fact] - public void response_should_come_from_pre_authorisation_middleware() + public void should_call_pre_query_string_builder_middleware() + { + var configuration = new OcelotMiddlewareConfiguration + { + AuthorisationMiddleware = async (ctx, next) => + { + _counter++; + await next.Invoke(); + } + }; + + var fileConfiguration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamTemplate = "http://localhost:41879/", + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) + .And(x => _steps.GivenOcelotIsRunning(configuration)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => x.ThenTheCounterIs(1)) + .BDDfy(); + } + + [Fact] + public void should_call_authorisation_middleware() + { + var configuration = new OcelotMiddlewareConfiguration + { + AuthorisationMiddleware = async (ctx, next) => + { + _counter++; + await next.Invoke(); + } + }; + + var fileConfiguration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamTemplate = "http://localhost:41879/", + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) + .And(x => _steps.GivenOcelotIsRunning(configuration)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => x.ThenTheCounterIs(1)) + .BDDfy(); + } + + [Fact] + public void should_call_authentication_middleware() + { + var configuration = new OcelotMiddlewareConfiguration + { + AuthenticationMiddleware = async (ctx, next) => + { + _counter++; + await next.Invoke(); + } + }; + + var fileConfiguration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamTemplate = "http://localhost:41879/", + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) + .And(x => _steps.GivenOcelotIsRunning(configuration)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => x.ThenTheCounterIs(1)) + .BDDfy(); + } + + [Fact] + public void should_call_pre_error_middleware() + { + var configuration = new OcelotMiddlewareConfiguration + { + PreErrorResponderMiddleware = async (ctx, next) => + { + _counter++; + await next.Invoke(); + } + }; + + var fileConfiguration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamTemplate = "http://localhost:41879/", + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:41879", 200)) + .And(x => _steps.GivenThereIsAConfiguration(fileConfiguration, _configurationPath)) + .And(x => _steps.GivenOcelotIsRunning(configuration)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => x.ThenTheCounterIs(1)) + .BDDfy(); + } + + [Fact] + public void should_call_pre_authorisation_middleware() { var configuration = new OcelotMiddlewareConfiguration { PreAuthorisationMiddleware = async (ctx, next) => { - await ctx.Response.WriteAsync("PreHttpResponderMiddleware"); + _counter++; + await next.Invoke(); } }; @@ -57,18 +193,19 @@ namespace Ocelot.AcceptanceTests .And(x => _steps.GivenOcelotIsRunning(configuration)) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("PreHttpResponderMiddleware")) + .And(x => x.ThenTheCounterIs(1)) .BDDfy(); } [Fact] - public void response_should_come_from_pre_http_authentication_middleware() + public void should_call_pre_http_authentication_middleware() { var configuration = new OcelotMiddlewareConfiguration { PreAuthenticationMiddleware = async (ctx, next) => { - await ctx.Response.WriteAsync("PreHttpRequesterMiddleware"); + _counter++; + await next.Invoke(); } }; @@ -90,10 +227,15 @@ namespace Ocelot.AcceptanceTests .And(x => _steps.GivenOcelotIsRunning(configuration)) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("PreHttpRequesterMiddleware")) + .And(x => x.ThenTheCounterIs(1)) .BDDfy(); } + private void ThenTheCounterIs(int expected) + { + _counter.ShouldBe(expected); + } + private void GivenThereIsAServiceRunningOn(string url, int statusCode) { _builder = new WebHostBuilder() diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 7b9a8f83..5f8f1eac 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using CacheManager.Core; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; @@ -15,6 +16,7 @@ using Ocelot.DependencyInjection; using Ocelot.ManualTest; using Ocelot.Middleware; using Shouldly; +using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; namespace Ocelot.AcceptanceTests { @@ -82,6 +84,16 @@ namespace Ocelot.AcceptanceTests .UseConfiguration(configuration) .ConfigureServices(s => { + Action settings = (x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithDictionaryHandle(); + }; + + s.AddOcelotOutputCaching(settings); s.AddOcelotFileConfiguration(configuration); s.AddOcelot(); }) diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index b4a490dc..d6177a81 100644 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamTemplate":"http://localhost:41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null}]} \ No newline at end of file +{"ReRoutes":[{"DownstreamTemplate":"http://localhost:41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0}}]} \ No newline at end of file diff --git a/test/Ocelot.ManualTest/Startup.cs b/test/Ocelot.ManualTest/Startup.cs index f53e894c..d202cc3e 100644 --- a/test/Ocelot.ManualTest/Startup.cs +++ b/test/Ocelot.ManualTest/Startup.cs @@ -1,10 +1,13 @@ -using Microsoft.AspNetCore.Builder; +using System; +using CacheManager.Core; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Ocelot.DependencyInjection; using Ocelot.Middleware; +using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; namespace Ocelot.ManualTest { @@ -27,6 +30,16 @@ namespace Ocelot.ManualTest // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + Action settings = (x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithDictionaryHandle(); + }; + + services.AddOcelotOutputCaching(settings); services.AddOcelotFileConfiguration(Configuration); services.AddOcelot(); } diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index 6207d5cd..8d027770 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -40,7 +40,8 @@ { "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts", "UpstreamTemplate": "/posts", - "UpstreamHttpMethod": "Get" + "UpstreamHttpMethod": "Get", + "FileCacheOptions": { "TtlSeconds": 15 } }, { "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}", diff --git a/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs b/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs new file mode 100644 index 00000000..87752a52 --- /dev/null +++ b/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs @@ -0,0 +1,77 @@ +using System; +using CacheManager.Core; +using Moq; +using Ocelot.Cache; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Cache +{ + public class CacheManagerCacheTests + { + private OcelotCacheManagerCache _ocelotOcelotCacheManager; + private Mock> _mockCacheManager; + private string _key; + private string _value; + private string _resultGet; + private TimeSpan _ttlSeconds; + + public CacheManagerCacheTests() + { + _mockCacheManager = new Mock>(); + _ocelotOcelotCacheManager = new OcelotCacheManagerCache(_mockCacheManager.Object); + } + [Fact] + public void should_get_from_cache() + { + this.Given(x => x.GivenTheFollowingIsCached("someKey", "someValue")) + .When(x => x.WhenIGetFromTheCache()) + .Then(x => x.ThenTheResultIs("someValue")) + .BDDfy(); + + } + + [Fact] + public void should_add_to_cache() + { + this.When(x => x.WhenIAddToTheCache("someKey", "someValue", TimeSpan.FromSeconds(1))) + .Then(x => x.ThenTheCacheIsCalledCorrectly()) + .BDDfy(); + } + + private void WhenIAddToTheCache(string key, string value, TimeSpan ttlSeconds) + { + _key = key; + _value = value; + _ttlSeconds = ttlSeconds; + + _ocelotOcelotCacheManager.Add(_key, _value, _ttlSeconds); + } + + private void ThenTheCacheIsCalledCorrectly() + { + _mockCacheManager + .Verify(x => x.Add(It.IsAny>()), Times.Once); + } + + private void ThenTheResultIs(string expected) + { + _resultGet.ShouldBe(expected); + } + + private void WhenIGetFromTheCache() + { + _resultGet = _ocelotOcelotCacheManager.Get(_key); + } + + private void GivenTheFollowingIsCached(string key, string value) + { + _key = key; + _value = value; + _mockCacheManager + .Setup(x => x.Get(It.IsAny())) + .Returns(value); + } + } +} diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs new file mode 100644 index 00000000..24255a18 --- /dev/null +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using CacheManager.Core; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +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.Infrastructure.RequestData; +using Ocelot.Responses; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Cache +{ + public class OutputCacheMiddlewareTests + { + private readonly Mock> _cacheManager; + private readonly Mock _scopedRepo; + + private readonly string _url; + private readonly TestServer _server; + private readonly HttpClient _client; + private HttpResponseMessage _result; + private HttpResponseMessage _response; + + public OutputCacheMiddlewareTests() + { + _cacheManager = new Mock>(); + _scopedRepo = new Mock(); + _url = "http://localhost:51879"; + var builder = new WebHostBuilder() + .ConfigureServices(x => + { + x.AddLogging(); + x.AddSingleton(_cacheManager.Object); + x.AddSingleton(_scopedRepo.Object); + }) + .UseUrls(_url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(_url) + .Configure(app => + { + app.UseOutputCacheMiddleware(); + }); + + _server = new TestServer(builder); + _client = _server.CreateClient(); + } + + [Fact] + public void should_returned_cached_item_when_it_is_in_cache() + { + this.Given(x => x.GivenThereIsACachedResponse(new HttpResponseMessage())) + .And(x => x.GivenTheDownstreamRouteIs()) + .And(x => x.GivenThereIsADownstreamUrl()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheCacheGetIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_continue_with_pipeline_and_cache_response() + { + this.Given(x => x.GivenResponseIsNotCached()) + .And(x => x.GivenTheDownstreamRouteIs()) + .And(x => x.GivenThereAreNoErrors()) + .And(x => x.GivenThereIsADownstreamUrl()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheCacheAddIsCalledCorrectly()) + .BDDfy(); + } + + private void GivenTheDownstreamRouteIs() + { + var reRoute = new ReRouteBuilder().WithIsCached(true).WithCacheOptions(new CacheOptions(100)).Build(); + var downstreamRoute = new DownstreamRoute(new List(), reRoute); + + _scopedRepo + .Setup(x => x.Get(It.IsAny())) + .Returns(new OkResponse(downstreamRoute)); + } + + private void GivenThereAreNoErrors() + { + _scopedRepo + .Setup(x => x.Get("OcelotMiddlewareError")) + .Returns(new OkResponse(false)); + } + + private void GivenThereIsADownstreamUrl() + { + _scopedRepo + .Setup(x => x.Get("DownstreamUrl")) + .Returns(new OkResponse("anything")); + } + + private void ThenTheCacheGetIsCalledCorrectly() + { + _cacheManager + .Verify(x => x.Get(It.IsAny()), Times.Once); + } + + private void ThenTheCacheAddIsCalledCorrectly() + { + _cacheManager + .Verify(x => x.Add(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + private void GivenResponseIsNotCached() + { + _scopedRepo + .Setup(x => x.Get("HttpResponseMessage")) + .Returns(new OkResponse(new HttpResponseMessage())); + } + + private void GivenThereIsACachedResponse(HttpResponseMessage response) + { + _response = response; + _cacheManager + .Setup(x => x.Get(It.IsAny())) + .Returns(_response); + } + + private void WhenICallTheMiddleware() + { + _result = _client.GetAsync(_url).Result; + } + } +} diff --git a/test/Ocelot.UnitTests/Errors/GobalErrorHandlerTests.cs b/test/Ocelot.UnitTests/Errors/GobalErrorHandlerTests.cs deleted file mode 100644 index 9a75e38a..00000000 --- a/test/Ocelot.UnitTests/Errors/GobalErrorHandlerTests.cs +++ /dev/null @@ -1,83 +0,0 @@ -/* -using System; -using System.IO; -using System.Net.Http; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; -using Ocelot.Middleware; -using Ocelot.RequestId.Provider; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Errors -{ - public class GobalErrorHandlerTests - { - private readonly Mock _loggerFactory; - private readonly Mock> _logger; - private readonly Mock _requestIdProvider; - private readonly string _url; - private readonly TestServer _server; - private readonly HttpClient _client; - private HttpResponseMessage _result; - - public GobalErrorHandlerTests() - { - _url = "http://localhost:51879"; - _logger = new Mock>(); - _loggerFactory = new Mock(); - _requestIdProvider = new Mock(); - var builder = new WebHostBuilder() - .ConfigureServices(x => - { - x.AddSingleton(_requestIdProvider.Object); - x.AddSingleton(_loggerFactory.Object); - }) - .UseUrls(_url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(_url) - .Configure(app => - { - app.UseExceptionHandlerMiddleware(); - - app.Run(x => - { - throw new Exception("BLAM"); - }); - }); - - _loggerFactory - .Setup(x => x.CreateLogger()) - .Returns(_logger.Object); - - _server = new TestServer(builder); - _client = _server.CreateClient(); - } - - [Fact] - public void should_catch_exception_and_log() - { - this.When(x => x.WhenICallTheMiddleware()) - .And(x => x.TheLoggerIsCalledCorrectly()) - .BDDfy(); - } - - private void TheLoggerIsCalledCorrectly() - { - _logger - .Verify(x => x.LogError(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } - - private void WhenICallTheMiddleware() - { - _result = _client.GetAsync(_url).Result; - } - } -} -*/ diff --git a/test/Ocelot.UnitTests/Infrastructure/ClaimParserTests.cs b/test/Ocelot.UnitTests/Infrastructure/ClaimParserTests.cs index ddd383c3..2358166f 100644 --- a/test/Ocelot.UnitTests/Infrastructure/ClaimParserTests.cs +++ b/test/Ocelot.UnitTests/Infrastructure/ClaimParserTests.cs @@ -4,7 +4,6 @@ namespace Ocelot.UnitTests.Infrastructure { using System.Collections.Generic; using System.Security.Claims; - using Errors; using Ocelot.Infrastructure.Claims.Parser; using Responses; using Shouldly; diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index d15a57ab..0c570713 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -27,19 +27,16 @@ namespace Ocelot.UnitTests.Requester private HttpResponseMessage _result; private OkResponse _response; private OkResponse _request; - private readonly Mock _responder; public HttpRequesterMiddlewareTests() { _url = "http://localhost:51879"; _requester = new Mock(); _scopedRepository = new Mock(); - _responder = new Mock(); var builder = new WebHostBuilder() .ConfigureServices(x => { - x.AddSingleton(_responder.Object); x.AddSingleton(_requester.Object); x.AddSingleton(_scopedRepository.Object); }) @@ -62,9 +59,9 @@ namespace Ocelot.UnitTests.Requester { this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),new CookieContainer()))) .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) - .And(x => x.GivenTheResponderReturns()) + .And(x => x.GivenTheScopedRepoReturns()) .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheResponderIsCalledCorrectly()) + .Then(x => x.ThenTheScopedRepoIsCalledCorrectly()) .BDDfy(); } @@ -76,17 +73,17 @@ namespace Ocelot.UnitTests.Requester .ReturnsAsync(_response); } - private void GivenTheResponderReturns() + private void GivenTheScopedRepoReturns() { - _responder - .Setup(x => x.SetResponseOnHttpContext(It.IsAny(), _response.Data)) - .ReturnsAsync(new OkResponse()); + _scopedRepository + .Setup(x => x.Add(It.IsAny(), _response.Data)) + .Returns(new OkResponse()); } - private void ThenTheResponderIsCalledCorrectly() + private void ThenTheScopedRepoIsCalledCorrectly() { - _responder - .Verify(x => x.SetResponseOnHttpContext(It.IsAny(), _response.Data), Times.Once()); + _scopedRepository + .Verify(x => x.Add("HttpResponseMessage", _response.Data), Times.Once()); } private void WhenICallTheMiddleware() diff --git a/test/Ocelot.UnitTests/Responder/HttpErrorResponderMiddlewareTests.cs b/test/Ocelot.UnitTests/Responder/HttpErrorResponderMiddlewareTests.cs index 7edabd62..61d260dd 100644 --- a/test/Ocelot.UnitTests/Responder/HttpErrorResponderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Responder/HttpErrorResponderMiddlewareTests.cs @@ -59,11 +59,19 @@ namespace Ocelot.UnitTests.Responder { this.Given(x => x.GivenTheHttpResponseMessageIs(new HttpResponseMessage())) .And(x => x.GivenThereAreNoPipelineErrors()) + .And(x => x.GivenTheResponderReturns()) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenThereAreNoErrors()) .BDDfy(); } + private void GivenTheResponderReturns() + { + _responder + .Setup(x => x.SetResponseOnHttpContext(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OkResponse()); + } + private void GivenThereAreNoPipelineErrors() { _scopedRepository