diff --git a/docs/features/delegatinghandlers.rst b/docs/features/delegatinghandlers.rst index b3108534..a055f6da 100644 --- a/docs/features/delegatinghandlers.rst +++ b/docs/features/delegatinghandlers.rst @@ -1,26 +1,16 @@ Delegating Handers ================== -Ocelot allows the user to add delegating handlers to the HttpClient transport. This feature was requested `GitHub #208 `_ and I decided that it was going to be useful in various ways. +Ocelot allows the user to add delegating handlers to the HttpClient transport. This feature was requested `GitHub #208 `_ +and I decided that it was going to be useful in various ways. Since then we extended it in `GitHub #208 `_. Usage ^^^^^ -In order to add delegating handlers to the HttpClient transport you need to do the following. +In order to add delegating handlers to the HttpClient transport you need to do two main things. -This will register the Handlers as singletons. Because Ocelot caches the HttpClient for the downstream services to avoid -socket exhaustion (well known http client issue) you can only register singleton handlers. - -.. code-block:: csharp - - services.AddOcelot() - .AddDelegatingHandler() - .AddDelegatingHandler() - -You can have as many DelegatingHandlers as you want and they are run in a first in first out order. If you are using Ocelot's QoS functionality then that will always be run after your last delegating handler. If you are also registering handlers in DI these will be -run first. - -In order to create a class that can be used a delegating handler it must look as follows +First in order to create a class that can be used a delegating handler it must look as follows. We are going to register these handlers in the +asp.net core container so you can inject any other services you have registered into the constructor of your handler. .. code-block:: csharp @@ -33,4 +23,57 @@ In order to create a class that can be used a delegating handler it must look as } } -Hopefully other people will find this feature useful! \ No newline at end of file +Next you must add the handlers to Ocelot's container either as singleton like follows.. + +.. code-block:: csharp + + services.AddOcelot() + .AddSingletonDelegatingHandler() + .AddSingletonDelegatingHandler() + +Or transient as below... + +.. code-block:: csharp + + services.AddOcelot() + .AddTransientDelegatingHandler() + .AddTransientDelegatingHandler() + +Both of these Add methods have a default parameter called global which is set to false. If it is false then the intent of +the DelegatingHandler is to be applied to specific ReRoutes via configuration.json (more on that later). If it is set to true +then it becomes a global handler and will be applied to all ReRoutes. + +e.g. + +.. code-block:: csharp + + services.AddOcelot() + .AddSingletonDelegatingHandler(true) + +Or transient as below... + +.. code-block:: csharp + + services.AddOcelot() + .AddTransientDelegatingHandler(true) + +Finally if you want ReRoute specific DelegatingHandlers or to order your specific and / or global (more on this later) DelegatingHandlers +then you must add the following json to the specific ReRoute in configuration.json. The names in the array must match the class names of your +DelegatingHandlers for Ocelot to match them together. + +.. code-block:: json + + "DelegatingHandlers": [ + "FakeHandlerTwo", + "FakeHandler" + ] + +You can have as many DelegatingHandlers as you want and they are run in the following order: + +1. Any globals that are left in the order they were added to services and are not in the DelegatingHandlers array from configuration.json. +2. Any non global DelegatingHandlers plus any globals that were in the DelegatingHandlers array from configuration.json ordered as they are in the DelegatingHandlers array. +3. Tracing DelegatingHandler if enabled (see tracing docs). +4. QoS DelegatingHandler if enabled (see QoS docs). +5. The HttpClient sends the HttpRequestMessage. + +Hopefully other people will find this feature useful! diff --git a/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs b/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs deleted file mode 100644 index aa60a8b0..00000000 --- a/src/Ocelot/Authentication/Handler/SupportedAuthenticationProviders.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ocelot.Authentication.Handler -{ - public enum SupportedAuthenticationProviders - { - IdentityServer, - Jwt - } -} diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs index 868b668b..27b0f197 100644 --- a/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs +++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs @@ -1,4 +1,3 @@ -using Microsoft.AspNetCore.Builder; using Ocelot.Middleware.Pipeline; namespace Ocelot.Authentication.Middleware diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs index f6c19522..4ace7dbc 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs @@ -1,16 +1,12 @@ -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Responses; -using Ocelot.Configuration; - -namespace Ocelot.Authorisation.Middleware +namespace Ocelot.Authorisation.Middleware { using System.Collections.Generic; using System.Threading.Tasks; using Errors; - using Microsoft.AspNetCore.Http; - using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Middleware; + using Logging; + using Responses; + using Configuration; public class AuthorisationMiddleware : OcelotMiddleware { diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs index ffc30177..df00eb6b 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs @@ -2,8 +2,6 @@ using Ocelot.Middleware.Pipeline; namespace Ocelot.Authorisation.Middleware { - using Microsoft.AspNetCore.Builder; - public static class AuthorisationMiddlewareMiddlewareExtensions { public static IOcelotPipelineBuilder UseAuthorisationMiddleware(this IOcelotPipelineBuilder builder) diff --git a/src/Ocelot/Authorisation/ScopesAuthoriser.cs b/src/Ocelot/Authorisation/ScopesAuthoriser.cs index ec433915..1654e2a1 100644 --- a/src/Ocelot/Authorisation/ScopesAuthoriser.cs +++ b/src/Ocelot/Authorisation/ScopesAuthoriser.cs @@ -34,9 +34,9 @@ namespace Ocelot.Authorisation var userScopes = values.Data; - List matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList(); + var matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList(); - if (matchesScopes == null || matchesScopes.Count == 0) + if (matchesScopes.Count == 0) { return new ErrorResponse(new List { @@ -48,4 +48,4 @@ namespace Ocelot.Authorisation return new OkResponse(true); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs index 5f82f688..0e28aaa7 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs @@ -36,9 +36,12 @@ namespace Ocelot.Configuration.Builder private readonly List _downstreamAddresses; private string _upstreamHost; private string _key; + private List _delegatingHandlers; + public DownstreamReRouteBuilder() { _downstreamAddresses = new List(); + _delegatingHandlers = new List(); } public DownstreamReRouteBuilder WithDownstreamAddresses(List downstreamAddresses) @@ -215,6 +218,12 @@ namespace Ocelot.Configuration.Builder return this; } + public DownstreamReRouteBuilder WithDelegatingHandlers(List delegatingHandlers) + { + _delegatingHandlers = delegatingHandlers; + return this; + } + public DownstreamReRoute Build() { return new DownstreamReRoute( @@ -243,7 +252,8 @@ namespace Ocelot.Configuration.Builder _isAuthorised, _authenticationOptions, new PathTemplate(_downstreamPathTemplate), - _reRouteKey); + _reRouteKey, + _delegatingHandlers); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index a35c7d54..94cde717 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -212,6 +212,7 @@ namespace Ocelot.Configuration.Creator .WithUpstreamHeaderFindAndReplace(hAndRs.Upstream) .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) .WithUpstreamHost(fileReRoute.UpstreamHost) + .WithDelegatingHandlers(fileReRoute.DelegatingHandlers) .Build(); return reRoute; diff --git a/src/Ocelot/Configuration/DownstreamReRoute.cs b/src/Ocelot/Configuration/DownstreamReRoute.cs index 9322d5cf..6c9aa1dd 100644 --- a/src/Ocelot/Configuration/DownstreamReRoute.cs +++ b/src/Ocelot/Configuration/DownstreamReRoute.cs @@ -31,8 +31,10 @@ namespace Ocelot.Configuration bool isAuthorised, AuthenticationOptions authenticationOptions, PathTemplate downstreamPathTemplate, - string reRouteKey) + string reRouteKey, + List delegatingHandlers) { + DelegatingHandlers = delegatingHandlers; Key = key; UpstreamPathTemplate = upstreamPathTemplate; UpstreamHeadersFindAndReplace = upstreamHeadersFindAndReplace ?? new List(); @@ -87,5 +89,6 @@ namespace Ocelot.Configuration public AuthenticationOptions AuthenticationOptions { get; private set; } public PathTemplate DownstreamPathTemplate { get; private set; } public string ReRouteKey { get; private set; } + public List DelegatingHandlers {get;private set;} } } diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 7169e62c..adc747f9 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -19,6 +19,7 @@ namespace Ocelot.Configuration.File HttpHandlerOptions = new FileHttpHandlerOptions(); UpstreamHeaderTransform = new Dictionary(); DownstreamHostAndPorts = new List(); + DelegatingHandlers = new List(); } public string DownstreamPathTemplate { get; set; } @@ -44,5 +45,6 @@ namespace Ocelot.Configuration.File public List DownstreamHostAndPorts {get;set;} public string UpstreamHost { get; set; } public string Key { get;set; } + public List DelegatingHandlers {get;set;} } } diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index ad1e4777..37cfa0d8 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -3,7 +3,6 @@ using CacheManager.Core; using System; using System.Net.Http; using IdentityServer4.AccessTokenValidation; -using Ocelot.Requester; namespace Ocelot.DependencyInjection { @@ -19,6 +18,10 @@ namespace Ocelot.DependencyInjection IOcelotAdministrationBuilder AddAdministration(string path, Action configOptions); - IOcelotBuilder AddDelegatingHandler() where T : DelegatingHandler; + IOcelotBuilder AddSingletonDelegatingHandler(bool global = false) + where T : DelegatingHandler; + + IOcelotBuilder AddTransientDelegatingHandler(bool global = false) + where T : DelegatingHandler; } } diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 75dddb1d..9e2e763b 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -182,10 +182,41 @@ namespace Ocelot.DependencyInjection return new OcelotAdministrationBuilder(_services, _configurationRoot); } - public IOcelotBuilder AddDelegatingHandler() + public IOcelotBuilder AddSingletonDelegatingHandler(bool global = false) + where THandler : DelegatingHandler + { + if(global) + { + _services.AddSingleton(); + _services.AddSingleton(s => { + var service = s.GetService(); + return new GlobalDelegatingHandler(service); + }); + } + else + { + _services.AddSingleton(); + } + + return this; + } + + public IOcelotBuilder AddTransientDelegatingHandler(bool global = false) where THandler : DelegatingHandler { - _services.AddSingleton(); + if(global) + { + _services.AddTransient(); + _services.AddTransient(s => { + var service = s.GetService(); + return new GlobalDelegatingHandler(service); + }); + } + else + { + _services.AddTransient(); + } + return this; } diff --git a/src/Ocelot/Authentication/BearerToken.cs b/src/Ocelot/Raft/BearerToken.cs similarity index 78% rename from src/Ocelot/Authentication/BearerToken.cs rename to src/Ocelot/Raft/BearerToken.cs index 5a52614a..983dada9 100644 --- a/src/Ocelot/Authentication/BearerToken.cs +++ b/src/Ocelot/Raft/BearerToken.cs @@ -1,16 +1,17 @@ -using Newtonsoft.Json; - -namespace Ocelot.Authentication -{ - class BearerToken - { - [JsonProperty("access_token")] - public string AccessToken { get; set; } - - [JsonProperty("expires_in")] - public int ExpiresIn { get; set; } - - [JsonProperty("token_type")] - public string TokenType { get; set; } - } -} \ No newline at end of file +using Newtonsoft.Json; + +namespace Ocelot.Raft +{ + [ExcludeFromCoverage] + internal class BearerToken + { + [JsonProperty("access_token")] + public string AccessToken { get; set; } + + [JsonProperty("expires_in")] + public int ExpiresIn { get; set; } + + [JsonProperty("token_type")] + public string TokenType { get; set; } + } +} diff --git a/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs similarity index 52% rename from src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs rename to src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs index 4e242137..e0d9da82 100644 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs +++ b/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net.Http; using Microsoft.Extensions.DependencyInjection; using Ocelot.Configuration; @@ -29,13 +30,36 @@ namespace Ocelot.Requester public Response>> Get(DownstreamReRoute request) { - var handlersAppliedToAll = _serviceProvider.GetServices(); + var globalDelegatingHandlers = _serviceProvider + .GetServices() + .ToList(); + + var reRouteSpecificHandlers = _serviceProvider + .GetServices() + .ToList(); var handlers = new List>(); - foreach (var handler in handlersAppliedToAll) + foreach (var handler in globalDelegatingHandlers) { - handlers.Add(() => handler); + if (GlobalIsInHandlersConfig(request, handler)) + { + reRouteSpecificHandlers.Add(handler.DelegatingHandler); + } + else + { + handlers.Add(() => handler.DelegatingHandler); + } + } + + if (request.DelegatingHandlers.Any()) + { + var sorted = SortByConfigOrder(request, reRouteSpecificHandlers); + + foreach (var handler in sorted) + { + handlers.Add(() => handler); + } } if (request.HttpHandlerOptions.UseTracing) @@ -57,5 +81,22 @@ namespace Ocelot.Requester return new OkResponse>>(handlers); } + + private List SortByConfigOrder(DownstreamReRoute request, List reRouteSpecificHandlers) + { + return reRouteSpecificHandlers + .Where(x => request.DelegatingHandlers.Contains(x.GetType().Name)) + .OrderBy(d => + { + var type = d.GetType().Name; + var pos = request.DelegatingHandlers.IndexOf(type); + return pos; + }).ToList(); + } + + private bool GlobalIsInHandlersConfig(DownstreamReRoute request, GlobalDelegatingHandler handler) + { + return request.DelegatingHandlers.Contains(handler.DelegatingHandler.GetType().Name); + } } } diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index f2d32733..c60e3b78 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -15,7 +15,7 @@ namespace Ocelot.Requester private readonly IOcelotLogger _logger; private readonly IDelegatingHandlerHandlerFactory _factory; - public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, + public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, IHttpClientCache cacheHandlers, IDelegatingHandlerHandlerFactory house) { @@ -29,7 +29,7 @@ namespace Ocelot.Requester var builder = new HttpClientBuilder(_factory); var cacheKey = GetCacheKey(request); - + var httpClient = GetHttpClient(cacheKey, builder, request); try @@ -72,8 +72,23 @@ namespace Ocelot.Requester private string GetCacheKey(DownstreamContext request) { var baseUrl = $"{request.DownstreamRequest.RequestUri.Scheme}://{request.DownstreamRequest.RequestUri.Authority}"; - + return baseUrl; } } + + public class ReRouteDelegatingHandler where T : DelegatingHandler + { + public T DelegatingHandler { get; private set; } + } + + public class GlobalDelegatingHandler + { + public GlobalDelegatingHandler(DelegatingHandler delegatingHandler) + { + DelegatingHandler = delegatingHandler; + } + + public DelegatingHandler DelegatingHandler { get; private set; } + } } diff --git a/test/Ocelot.AcceptanceTests/GzipTests.cs b/test/Ocelot.AcceptanceTests/GzipTests.cs index 9c04314c..8403c305 100644 --- a/test/Ocelot.AcceptanceTests/GzipTests.cs +++ b/test/Ocelot.AcceptanceTests/GzipTests.cs @@ -107,6 +107,7 @@ namespace Ocelot.AcceptanceTests _builder.Start(); } + public void Dispose() { _builder?.Dispose(); diff --git a/test/Ocelot.AcceptanceTests/HeaderTests.cs b/test/Ocelot.AcceptanceTests/HeaderTests.cs index 5257b448..350e1e52 100644 --- a/test/Ocelot.AcceptanceTests/HeaderTests.cs +++ b/test/Ocelot.AcceptanceTests/HeaderTests.cs @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; -using Shouldly; using TestStack.BDDfy; using Xunit; diff --git a/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs b/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs index 72f1f341..740ee6e7 100644 --- a/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs +++ b/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs @@ -26,9 +26,48 @@ namespace Ocelot.AcceptanceTests _steps = new Steps(); } + [Fact] + public void should_call_re_route_ordered_specific_handlers() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 7197, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + DelegatingHandlers = new List + { + "FakeHandlerTwo", + "FakeHandler" + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:7197", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningWithSpecficHandlersRegisteredInDi()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(x => ThenTheOrderedHandlersAreCalledCorrectly()) + .BDDfy(); + } [Fact] - public void should_call_di_handlers() + public void should_call_global_di_handlers() { var configuration = new FileConfiguration { @@ -54,7 +93,7 @@ namespace Ocelot.AcceptanceTests this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:7187", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningWithHandlersRegisteredInDi()) + .And(x => _steps.GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) @@ -62,9 +101,8 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } - [Fact] - public void should_call_di_handlers_with_dependency() + public void should_call_global_di_handlers_with_dependency() { var configuration = new FileConfiguration { @@ -92,7 +130,7 @@ namespace Ocelot.AcceptanceTests this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:7188", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningWithHandlersRegisteredInDi(dependency)) + .And(x => _steps.GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi(dependency)) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) @@ -110,45 +148,54 @@ namespace Ocelot.AcceptanceTests FakeHandler.TimeCalled.ShouldBeLessThan(FakeHandlerTwo.TimeCalled); } + private void ThenTheOrderedHandlersAreCalledCorrectly() + { + FakeHandlerTwo.TimeCalled.ShouldBeLessThan(FakeHandler.TimeCalled); + } + public class FakeDependency { public bool Called; } - class FakeHandlerWithDependency : DelegatingHandler + // ReSharper disable once ClassNeverInstantiated.Local + private class FakeHandlerWithDependency : DelegatingHandler { - private FakeDependency _dependency; + private readonly FakeDependency _dependency; public FakeHandlerWithDependency(FakeDependency dependency) { _dependency = dependency; } - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { _dependency.Called = true; - return await base.SendAsync(request, cancellationToken); + return base.SendAsync(request, cancellationToken); } } - - class FakeHandler : DelegatingHandler + + // ReSharper disable once ClassNeverInstantiated.Local + private class FakeHandler : DelegatingHandler { public static DateTime TimeCalled { get; private set; } - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { TimeCalled = DateTime.Now; - return await base.SendAsync(request, cancellationToken); + return base.SendAsync(request, cancellationToken); } } - class FakeHandlerTwo : DelegatingHandler + + // ReSharper disable once ClassNeverInstantiated.Local + private class FakeHandlerTwo : DelegatingHandler { public static DateTime TimeCalled { get; private set; } - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { TimeCalled = DateTime.Now; - return await base.SendAsync(request, cancellationToken); + return base.SendAsync(request, cancellationToken); } } diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 90d5bc64..ed09ed96 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -50,4 +50,9 @@ + + + ..\..\..\..\Users\TGP\.nuget\packages\castle.core\4.2.1\lib\netstandard1.3\Castle.Core.dll + + diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 0ea0e6b6..0de7113d 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -25,6 +25,7 @@ using Ocelot.AcceptanceTests.Caching; using System.IO.Compression; using System.Text; using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; +using Ocelot.Requester; namespace Ocelot.AcceptanceTests { @@ -175,7 +176,7 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } - public void GivenOcelotIsRunningWithHandlersRegisteredInDi() + public void GivenOcelotIsRunningWithSpecficHandlersRegisteredInDi() where TOne : DelegatingHandler where TWo : DelegatingHandler { @@ -195,8 +196,8 @@ namespace Ocelot.AcceptanceTests { s.AddSingleton(_webHostBuilder); s.AddOcelot() - .AddDelegatingHandler() - .AddDelegatingHandler(); + .AddSingletonDelegatingHandler() + .AddSingletonDelegatingHandler(); }) .Configure(a => { @@ -208,7 +209,40 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } - public void GivenOcelotIsRunningWithHandlersRegisteredInDi(FakeDependency dependency) + public void GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi() + where TOne : DelegatingHandler + where TWo : DelegatingHandler + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddOcelot() + .AddSingletonDelegatingHandler(true) + .AddSingletonDelegatingHandler(true); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi(FakeDependency dependency) where TOne : DelegatingHandler { _webHostBuilder = new WebHostBuilder(); @@ -228,7 +262,7 @@ namespace Ocelot.AcceptanceTests s.AddSingleton(_webHostBuilder); s.AddSingleton(dependency); s.AddOcelot() - .AddDelegatingHandler(); + .AddSingletonDelegatingHandler(true); }) .Configure(a => { @@ -618,15 +652,16 @@ namespace Ocelot.AcceptanceTests public void GivenThePostHasGzipContent(object input) { - string json = JsonConvert.SerializeObject(input); - byte[] jsonBytes = Encoding.UTF8.GetBytes(json); - MemoryStream ms = new MemoryStream(); - using (GZipStream gzip = new GZipStream(ms, CompressionMode.Compress, true)) + var json = JsonConvert.SerializeObject(input); + var jsonBytes = Encoding.UTF8.GetBytes(json); + var ms = new MemoryStream(); + using (var gzip = new GZipStream(ms, CompressionMode.Compress, true)) { gzip.Write(jsonBytes, 0, jsonBytes.Length); } + ms.Position = 0; - StreamContent content = new StreamContent(ms); + var content = new StreamContent(ms); content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); content.Headers.ContentEncoding.Add("gzip"); _postContent = content; diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 4de07053..79aff9ed 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -402,11 +402,14 @@ namespace Ocelot.UnitTests.Configuration var reRouteOptions = new ReRouteOptionsBuilder() .Build(); + var handlers = new List {"Polly", "Tracer"}; + var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamScheme("https") .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List {"Get"}) + .WithDelegatingHandlers(handlers) .Build(); this.Given(x => x.GivenTheConfigIs(new FileConfiguration @@ -419,6 +422,7 @@ namespace Ocelot.UnitTests.Configuration UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = new List { "Get" }, + DelegatingHandlers = handlers } }, })) @@ -821,7 +825,8 @@ namespace Ocelot.UnitTests.Configuration result.DownstreamReRoute[0].ClaimsToClaims.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToClaims.Count); result.DownstreamReRoute[0].ClaimsToHeaders.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToHeaders.Count); result.DownstreamReRoute[0].ClaimsToQueries.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToQueries.Count); - result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey); + result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey); + result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers); } } diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 4b4e328b..df812f70 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -28,26 +28,58 @@ namespace Ocelot.UnitTests.DependencyInjection private readonly IConfiguration _configRoot; private IOcelotBuilder _ocelotBuilder; private readonly int _maxRetries; + private Exception _ex; public OcelotBuilderTests() { _configRoot = new ConfigurationRoot(new List()); _services = new ServiceCollection(); _services.AddSingleton(); - _services.AddSingleton(_configRoot); + _services.AddSingleton(_configRoot); _maxRetries = 100; } - private Exception _ex; + [Fact] + public void should_add_specific_delegating_handlers_transient() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddSpecificTransientDelegatingHandler()) + .And(x => AddSpecificTransientDelegatingHandler()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificHandlers()) + .And(x => ThenTheSpecificHandlersAreTransient()) + .BDDfy(); + } [Fact] - public void should_add_delegating_handlers_with_di() + public void should_add_specific_delegating_handler_singleton() { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddDelegate()) - .And(x => AddDelegate()) + .When(x => AddSpecificDelegatingHandler()) + .And(x => AddSpecificDelegatingHandler()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificHandlers()) + .And(x => ThenTheSpecificHandlersAreSingleton()) + .BDDfy(); + } + + [Fact] + public void should_add_global_delegating_handlers_transient() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddTransientGlobalDelegatingHandler()) + .And(x => AddTransientGlobalDelegatingHandler()) .Then(x => ThenTheProviderIsRegisteredAndReturnsHandlers()) + .And(x => ThenTheGlobalHandlersAreTransient()) + .BDDfy(); + } + + [Fact] + public void should_add_global_delegating_handlers_singleton() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddGlobalDelegatingHandler()) + .And(x => AddGlobalDelegatingHandler()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsHandlers()) + .And(x => ThenTheGlobalHandlersAreSingleton()) .BDDfy(); } @@ -118,16 +150,6 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } - private void WhenISetUpAdministration() - { - _ocelotBuilder.AddAdministration("/administration", "secret"); - } - - private void WhenISetUpAdministration(Action options) - { - _ocelotBuilder.AddAdministration("/administration", options); - } - [Fact] public void should_use_logger_factory() { @@ -155,6 +177,62 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } + private void ThenTheSpecificHandlersAreSingleton() + { + var handlers = _serviceProvider.GetServices().ToList(); + var first = handlers[0]; + handlers = _serviceProvider.GetServices().ToList(); + var second = handlers[0]; + first.ShouldBe(second); + } + + private void ThenTheSpecificHandlersAreTransient() + { + var handlers = _serviceProvider.GetServices().ToList(); + var first = handlers[0]; + handlers = _serviceProvider.GetServices().ToList(); + var second = handlers[0]; + first.ShouldNotBe(second); + } + + private void ThenTheGlobalHandlersAreSingleton() + { + var handlers = _serviceProvider.GetServices().ToList(); + var first = handlers[0].DelegatingHandler; + handlers = _serviceProvider.GetServices().ToList(); + var second = handlers[0].DelegatingHandler; + first.ShouldBe(second); + } + + private void ThenTheGlobalHandlersAreTransient() + { + var handlers = _serviceProvider.GetServices().ToList(); + var first = handlers[0].DelegatingHandler; + handlers = _serviceProvider.GetServices().ToList(); + var second = handlers[0].DelegatingHandler; + first.ShouldNotBe(second); + } + + private void WhenISetUpAdministration() + { + _ocelotBuilder.AddAdministration("/administration", "secret"); + } + + private void WhenISetUpAdministration(Action options) + { + _ocelotBuilder.AddAdministration("/administration", options); + } + + private void AddTransientGlobalDelegatingHandler() where T : DelegatingHandler + { + _ocelotBuilder.AddTransientDelegatingHandler(true); + } + + private void AddSpecificTransientDelegatingHandler() where T : DelegatingHandler + { + _ocelotBuilder.AddTransientDelegatingHandler(); + } + private void ThenTheCorrectAdminPathIsRegitered() { _serviceProvider = _services.BuildServiceProvider(); @@ -163,6 +241,14 @@ namespace Ocelot.UnitTests.DependencyInjection } private void ThenTheProviderIsRegisteredAndReturnsHandlers() + { + _serviceProvider = _services.BuildServiceProvider(); + var handlers = _serviceProvider.GetServices().ToList(); + handlers[0].DelegatingHandler.ShouldBeOfType(); + handlers[1].DelegatingHandler.ShouldBeOfType(); + } + + private void ThenTheProviderIsRegisteredAndReturnsSpecificHandlers() { _serviceProvider = _services.BuildServiceProvider(); var handlers = _serviceProvider.GetServices().ToList(); @@ -174,14 +260,18 @@ namespace Ocelot.UnitTests.DependencyInjection { var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); var outputCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); - var thing = (CacheManager.Core.ICacheManager)outputCacheManager.ImplementationInstance; - thing.Configuration.MaxRetries.ShouldBe(_maxRetries); - + var instance = (ICacheManager)outputCacheManager.ImplementationInstance; var ocelotConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); var ocelotConfigCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); - var fileConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); var fileConfigCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); + + instance.Configuration.MaxRetries.ShouldBe(_maxRetries); + outputCache.ShouldNotBeNull(); + ocelotConfigCache.ShouldNotBeNull(); + ocelotConfigCacheManager.ShouldNotBeNull(); + fileConfigCache.ShouldNotBeNull(); + fileConfigCacheManager.ShouldNotBeNull(); } private void WhenISetUpConsul() @@ -208,9 +298,14 @@ namespace Ocelot.UnitTests.DependencyInjection } } - private void AddDelegate() where T : DelegatingHandler + private void AddGlobalDelegatingHandler() where T : DelegatingHandler { - _ocelotBuilder.AddDelegatingHandler(); + _ocelotBuilder.AddSingletonDelegatingHandler(true); + } + + private void AddSpecificDelegatingHandler() where T : DelegatingHandler + { + _ocelotBuilder.AddSingletonDelegatingHandler(); } private void ThenAnOcelotBuilderIsReturned() @@ -281,6 +376,7 @@ namespace Ocelot.UnitTests.DependencyInjection { _serviceProvider = _services.BuildServiceProvider(); var logger = _serviceProvider.GetService(); + logger.ShouldNotBeNull(); } catch (Exception e) { @@ -293,6 +389,7 @@ namespace Ocelot.UnitTests.DependencyInjection try { var tracingHandler = _serviceProvider.GetService(); + tracingHandler.ShouldNotBeNull(); } catch (Exception e) { diff --git a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs index 793f3601..28a4bcf2 100644 --- a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs @@ -19,18 +19,179 @@ namespace Ocelot.UnitTests.Requester public class DelegatingHandlerHandlerProviderFactoryTests { private DelegatingHandlerHandlerFactory _factory; - private Mock _loggerFactory; + private readonly Mock _loggerFactory; private DownstreamReRoute _request; - private Response>> _provider; + private Response>> _result; private readonly Mock _qosProviderHouse; private readonly Mock _tracingFactory; private IServiceProvider _serviceProvider; + private readonly IServiceCollection _services; public DelegatingHandlerHandlerProviderFactoryTests() { _tracingFactory = new Mock(); _qosProviderHouse = new Mock(); _loggerFactory = new Mock(); + _services = new ServiceCollection(); + } + + [Fact] + public void should_follow_ordering_add_specifics() + { + var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true)) + .WithDelegatingHandlers(new List + { + "FakeDelegatingHandler", + "FakeDelegatingHandlerTwo" + }) + .WithReRouteKey("") + .Build(); + + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheTracingFactoryReturns()) + .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) + .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) + .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) + .When(x => WhenIGet()) + .Then(x => ThenThereIsDelegatesInProvider(6)) + .And(x => ThenHandlerAtPositionIs(0)) + .And(x => ThenHandlerAtPositionIs(1)) + .And(x => ThenHandlerAtPositionIs(2)) + .And(x => ThenHandlerAtPositionIs(3)) + .And(x => ThenHandlerAtPositionIs(4)) + .And(x => ThenHandlerAtPositionIs(5)) + .BDDfy(); + } + + [Fact] + public void should_follow_ordering_order_specifics_and_globals() + { + var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true)) + .WithDelegatingHandlers(new List + { + "FakeDelegatingHandlerTwo", + "FakeDelegatingHandler", + "FakeDelegatingHandlerFour" + }) + .WithReRouteKey("") + .Build(); + + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheTracingFactoryReturns()) + .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) + .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) + .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) + .When(x => WhenIGet()) + .Then(x => ThenThereIsDelegatesInProvider(6)) + .And(x => ThenHandlerAtPositionIs(0)) //first because global not in config + .And(x => ThenHandlerAtPositionIs(1)) //first from config + .And(x => ThenHandlerAtPositionIs(2)) //second from config + .And(x => ThenHandlerAtPositionIs(3)) //third from config (global) + .And(x => ThenHandlerAtPositionIs(4)) + .And(x => ThenHandlerAtPositionIs(5)) + .BDDfy(); + } + + [Fact] + public void should_follow_ordering_order_specifics() + { + var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true)) + .WithDelegatingHandlers(new List + { + "FakeDelegatingHandlerTwo", + "FakeDelegatingHandler" + }) + .WithReRouteKey("") + .Build(); + + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheTracingFactoryReturns()) + .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) + .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) + .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) + .When(x => WhenIGet()) + .Then(x => ThenThereIsDelegatesInProvider(6)) + .And(x => ThenHandlerAtPositionIs(0)) + .And(x => ThenHandlerAtPositionIs(1)) + .And(x => ThenHandlerAtPositionIs(2)) + .And(x => ThenHandlerAtPositionIs(3)) + .And(x => ThenHandlerAtPositionIs(4)) + .And(x => ThenHandlerAtPositionIs(5)) + .BDDfy(); + } + + [Fact] + public void should_follow_ordering_order_and_only_add_specifics_in_config() + { + var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true)) + .WithDelegatingHandlers(new List + { + "FakeDelegatingHandler", + }) + .WithReRouteKey("") + .Build(); + + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheTracingFactoryReturns()) + .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) + .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) + .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) + .When(x => WhenIGet()) + .Then(x => ThenThereIsDelegatesInProvider(5)) + .And(x => ThenHandlerAtPositionIs(0)) + .And(x => ThenHandlerAtPositionIs(1)) + .And(x => ThenHandlerAtPositionIs(2)) + .And(x => ThenHandlerAtPositionIs(3)) + .And(x => ThenHandlerAtPositionIs(4)) + .BDDfy(); + } + + [Fact] + public void should_follow_ordering_dont_add_specifics() + { + var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true)) + .WithReRouteKey("") + .Build(); + + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheTracingFactoryReturns()) + .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) + .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) + .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) + .When(x => WhenIGet()) + .Then(x => ThenThereIsDelegatesInProvider(4)) + .And(x => ThenHandlerAtPositionIs(0)) + .And(x => ThenHandlerAtPositionIs(1)) + .And(x => ThenHandlerAtPositionIs(2)) + .And(x => ThenHandlerAtPositionIs(3)) + .BDDfy(); + } + + [Fact] + public void should_apply_re_route_specific() + { + var reRoute = new DownstreamReRouteBuilder() + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)) + .WithDelegatingHandlers(new List + { + "FakeDelegatingHandler", + "FakeDelegatingHandlerTwo" + }) + .WithReRouteKey("") + .Build(); + + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) + .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) + .When(x => WhenIGet()) + .Then(x => ThenThereIsDelegatesInProvider(2)) + .And(x => ThenTheDelegatesAreAddedCorrectly()) + .BDDfy(); } [Fact] @@ -41,7 +202,7 @@ namespace Ocelot.UnitTests.Requester this.Given(x => GivenTheFollowingRequest(reRoute)) .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) - .And(x => GivenTheServiceProviderReturns()) + .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(3)) .And(x => ThenTheDelegatesAreAddedCorrectly()) @@ -91,30 +252,58 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } - private void GivenTheServiceProviderReturns() + private void ThenHandlerAtPositionIs(int pos) + where T : DelegatingHandler + { + var delegates = _result.Data; + var del = delegates[pos].Invoke(); + del.ShouldBeOfType(); + } + + private void GivenTheTracingFactoryReturns() + { + _tracingFactory + .Setup(x => x.Get()) + .Returns(new FakeTracingHandler()); + } + + private void GivenTheServiceProviderReturnsGlobalDelegatingHandlers() where TOne : DelegatingHandler where TTwo : DelegatingHandler { - IServiceCollection services = new ServiceCollection(); - services.AddSingleton(); - services.AddSingleton(); - _serviceProvider = services.BuildServiceProvider(); + _services.AddTransient(); + _services.AddTransient(s => { + var service = s.GetService(); + return new GlobalDelegatingHandler(service); + }); + _services.AddTransient(); + _services.AddTransient(s => { + var service = s.GetService(); + return new GlobalDelegatingHandler(service); + }); } - private void GivenTheServiceProviderReturnsNothing() + private void GivenTheServiceProviderReturnsSpecificDelegatingHandlers() + where TOne : DelegatingHandler + where TTwo : DelegatingHandler { - IServiceCollection services = new ServiceCollection(); - _serviceProvider = services.BuildServiceProvider(); + _services.AddTransient(); + _services.AddTransient(); + } + + private void GivenTheServiceProviderReturnsNothing() + { + _serviceProvider = _services.BuildServiceProvider(); } private void ThenAnErrorIsReturned() { - _provider.IsError.ShouldBeTrue(); + _result.IsError.ShouldBeTrue(); } private void ThenTheDelegatesAreAddedCorrectly() { - var delegates = _provider.Data; + var delegates = _result.Data; var del = delegates[0].Invoke(); var handler = (FakeDelegatingHandler) del; @@ -134,15 +323,15 @@ namespace Ocelot.UnitTests.Requester private void ThenItIsPolly(int i) { - var delegates = _provider.Data; + var delegates = _result.Data; var del = delegates[i].Invoke(); del.ShouldBeOfType(); } private void ThenThereIsDelegatesInProvider(int count) { - _provider.ShouldNotBeNull(); - _provider.Data.Count.ShouldBe(count); + _result.ShouldNotBeNull(); + _result.Data.Count.ShouldBe(count); } private void GivenTheFollowingRequest(DownstreamReRoute request) @@ -152,14 +341,19 @@ namespace Ocelot.UnitTests.Requester private void WhenIGet() { + _serviceProvider = _services.BuildServiceProvider(); _factory = new DelegatingHandlerHandlerFactory(_loggerFactory.Object, _tracingFactory.Object, _qosProviderHouse.Object, _serviceProvider); - _provider = _factory.Get(_request); + _result = _factory.Get(_request); } private void ThenNoDelegatesAreInTheProvider() { - _provider.ShouldNotBeNull(); - _provider.Data.Count.ShouldBe(0); + _result.ShouldNotBeNull(); + _result.Data.Count.ShouldBe(0); } } + + internal class FakeTracingHandler : DelegatingHandler, ITracingHandler + { + } } diff --git a/test/Ocelot.UnitTests/Requester/FakeDelegatingHandler.cs b/test/Ocelot.UnitTests/Requester/FakeDelegatingHandler.cs index df68978b..c300c92c 100644 --- a/test/Ocelot.UnitTests/Requester/FakeDelegatingHandler.cs +++ b/test/Ocelot.UnitTests/Requester/FakeDelegatingHandler.cs @@ -28,6 +28,42 @@ namespace Ocelot.UnitTests.Requester } } + public class FakeDelegatingHandlerThree : DelegatingHandler + { + public FakeDelegatingHandlerThree() + { + Order = 3; + } + + public int Order { get; private set; } + + public DateTime TimeCalled { get; private set; } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + TimeCalled = DateTime.Now; + return Task.FromResult(new HttpResponseMessage()); + } + } + + public class FakeDelegatingHandlerFour : DelegatingHandler + { + public FakeDelegatingHandlerFour() + { + Order = 4; + } + + public int Order { get; private set; } + + public DateTime TimeCalled { get; private set; } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + TimeCalled = DateTime.Now; + return Task.FromResult(new HttpResponseMessage()); + } + } + public class FakeDelegatingHandlerTwo : DelegatingHandler { public FakeDelegatingHandlerTwo() @@ -36,12 +72,13 @@ namespace Ocelot.UnitTests.Requester } public int Order {get;private set;} + public DateTime TimeCalled {get;private set;} - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { TimeCalled = DateTime.Now; - return new HttpResponseMessage(); + return Task.FromResult(new HttpResponseMessage()); } } }