From 0f2cf2d188f1404d3a84973a3b8b4db5a4176e18 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 29 Jul 2018 18:23:49 +0100 Subject: [PATCH 01/50] =?UTF-8?q?Made=20the=20file=20config=20poller=20use?= =?UTF-8?q?=20IHostedService,=20bit=20more=20generic,=20not=E2=80=A6=20(#5?= =?UTF-8?q?07)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Made the file config poller use IHostedService, bit more generic, not just need to provide the correct implementations of the repo services and it will poll anything..this means we can open up redis for #458 * removed comments --- ...onPoller.cs => FileConfigurationPoller.cs} | 196 +++-- .../Repository/IConsulPollerConfiguration.cs | 2 +- ...InMemoryFileConfigurationPollerOptions.cs} | 2 +- .../FileAndInternalConfigurationSetter.cs | 6 +- .../DependencyInjection/OcelotBuilder.cs | 4 +- .../Middleware/OcelotMiddlewareExtensions.cs | 523 ++++++------ .../ConfigurationInConsulTests.cs | 781 +++++++++--------- ...sts.cs => FileConfigurationPollerTests.cs} | 47 +- 8 files changed, 798 insertions(+), 763 deletions(-) rename src/Ocelot/Configuration/Repository/{ConsulFileConfigurationPoller.cs => FileConfigurationPoller.cs} (55%) rename src/Ocelot/Configuration/Repository/{InMemoryConsulPollerConfiguration.cs => InMemoryFileConfigurationPollerOptions.cs} (50%) rename test/Ocelot.UnitTests/Configuration/{ConsulFileConfigurationPollerTests.cs => FileConfigurationPollerTests.cs} (73%) diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs b/src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs similarity index 55% rename from src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs rename to src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs index 8dabeaf2..74bbf002 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs +++ b/src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs @@ -1,84 +1,112 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Setter; -using Ocelot.Logging; - -namespace Ocelot.Configuration.Repository -{ - public class ConsulFileConfigurationPoller : IDisposable - { - private readonly IOcelotLogger _logger; - private readonly IFileConfigurationRepository _repo; - private readonly IFileConfigurationSetter _setter; - private string _previousAsJson; - private readonly Timer _timer; - private bool _polling; - private readonly IConsulPollerConfiguration _config; - - public ConsulFileConfigurationPoller( - IOcelotLoggerFactory factory, - IFileConfigurationRepository repo, - IFileConfigurationSetter setter, - IConsulPollerConfiguration config) - { - _setter = setter; - _config = config; - _logger = factory.CreateLogger(); - _repo = repo; - _previousAsJson = ""; - _timer = new Timer(async x => - { - if(_polling) - { - return; - } - - _polling = true; - await Poll(); - _polling = false; - }, null, _config.Delay, _config.Delay); - } - - private async Task Poll() - { - _logger.LogInformation("Started polling consul"); - - var fileConfig = await _repo.Get(); - - if(fileConfig.IsError) - { - _logger.LogWarning($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}"); - return; - } - - var asJson = ToJson(fileConfig.Data); - - if(!fileConfig.IsError && asJson != _previousAsJson) - { - await _setter.Set(fileConfig.Data); - _previousAsJson = asJson; - } - - _logger.LogInformation("Finished polling consul"); - } - - /// - /// We could do object comparison here but performance isnt really a problem. This might be an issue one day! - /// - /// hash of the config - private string ToJson(FileConfiguration config) - { - var currentHash = JsonConvert.SerializeObject(config); - return currentHash; - } - - public void Dispose() - { - _timer.Dispose(); - } - } -} +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Newtonsoft.Json; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Setter; +using Ocelot.Logging; + +namespace Ocelot.Configuration.Repository +{ + public class FileConfigurationPoller : IHostedService, IDisposable + { + private readonly IOcelotLogger _logger; + private readonly IFileConfigurationRepository _repo; + private string _previousAsJson; + private Timer _timer; + private bool _polling; + private readonly IFileConfigurationPollerOptions _options; + private readonly IInternalConfigurationRepository _internalConfigRepo; + private readonly IInternalConfigurationCreator _internalConfigCreator; + + public FileConfigurationPoller( + IOcelotLoggerFactory factory, + IFileConfigurationRepository repo, + IFileConfigurationPollerOptions options, + IInternalConfigurationRepository internalConfigRepo, + IInternalConfigurationCreator internalConfigCreator) + { + _internalConfigRepo = internalConfigRepo; + _internalConfigCreator = internalConfigCreator; + _options = options; + _logger = factory.CreateLogger(); + _repo = repo; + _previousAsJson = ""; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _logger.LogInformation($"{nameof(FileConfigurationPoller)} is starting."); + + _timer = new Timer(async x => + { + if(_polling) + { + return; + } + + _polling = true; + await Poll(); + _polling = false; + }, null, _options.Delay, _options.Delay); + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _logger.LogInformation($"{nameof(FileConfigurationPoller)} is stopping."); + + _timer?.Change(Timeout.Infinite, 0); + + return Task.CompletedTask; + } + + private async Task Poll() + { + _logger.LogInformation("Started polling consul"); + + var fileConfig = await _repo.Get(); + + if(fileConfig.IsError) + { + _logger.LogWarning($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}"); + return; + } + + var asJson = ToJson(fileConfig.Data); + + if(!fileConfig.IsError && asJson != _previousAsJson) + { + var config = await _internalConfigCreator.Create(fileConfig.Data); + + if(!config.IsError) + { + _internalConfigRepo.AddOrReplace(config.Data); + } + + _previousAsJson = asJson; + } + + _logger.LogInformation("Finished polling consul"); + } + + /// + /// We could do object comparison here but performance isnt really a problem. This might be an issue one day! + /// + /// hash of the config + private string ToJson(FileConfiguration config) + { + var currentHash = JsonConvert.SerializeObject(config); + return currentHash; + } + + public void Dispose() + { + _timer.Dispose(); + } + } +} diff --git a/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs b/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs index d1f1430d..70fc9bd4 100644 --- a/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs +++ b/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs @@ -1,6 +1,6 @@ namespace Ocelot.Configuration.Repository { - public interface IConsulPollerConfiguration + public interface IFileConfigurationPollerOptions { int Delay { get; } } diff --git a/src/Ocelot/Configuration/Repository/InMemoryConsulPollerConfiguration.cs b/src/Ocelot/Configuration/Repository/InMemoryFileConfigurationPollerOptions.cs similarity index 50% rename from src/Ocelot/Configuration/Repository/InMemoryConsulPollerConfiguration.cs rename to src/Ocelot/Configuration/Repository/InMemoryFileConfigurationPollerOptions.cs index 9e411f76..f5ebe186 100644 --- a/src/Ocelot/Configuration/Repository/InMemoryConsulPollerConfiguration.cs +++ b/src/Ocelot/Configuration/Repository/InMemoryFileConfigurationPollerOptions.cs @@ -1,6 +1,6 @@ namespace Ocelot.Configuration.Repository { - public class InMemoryConsulPollerConfiguration : IConsulPollerConfiguration + public class InMemoryFileConfigurationPollerOptions : IFileConfigurationPollerOptions { public int Delay => 1000; } diff --git a/src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs index 6821553e..a5549b29 100644 --- a/src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs +++ b/src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs @@ -8,7 +8,7 @@ namespace Ocelot.Configuration.Setter { public class FileAndInternalConfigurationSetter : IFileConfigurationSetter { - private readonly IInternalConfigurationRepository _configRepo; + private readonly IInternalConfigurationRepository internalConfigRepo; private readonly IInternalConfigurationCreator _configCreator; private readonly IFileConfigurationRepository _repo; @@ -17,7 +17,7 @@ namespace Ocelot.Configuration.Setter IInternalConfigurationCreator configCreator, IFileConfigurationRepository repo) { - _configRepo = configRepo; + internalConfigRepo = configRepo; _configCreator = configCreator; _repo = repo; } @@ -35,7 +35,7 @@ namespace Ocelot.Configuration.Setter if(!config.IsError) { - _configRepo.AddOrReplace(config.Data); + internalConfigRepo.AddOrReplace(config.Data); } return new ErrorResponse(config.Errors); diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 72064a9d..07e62358 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -160,7 +160,7 @@ namespace Ocelot.DependencyInjection // We add this here so that we can always inject something into the factory for IoC.. _services.AddSingleton(); - _services.TryAddSingleton(); + _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); @@ -245,7 +245,7 @@ namespace Ocelot.DependencyInjection public IOcelotBuilder AddStoreOcelotConfigurationInConsul() { - _services.AddSingleton(); + _services.AddHostedService(); _services.AddSingleton(); return this; } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index fecf749f..d5421b12 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -1,266 +1,263 @@ -namespace Ocelot.Middleware -{ - using System; - using System.Linq; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.Options; - using System.Diagnostics; - using DependencyInjection; - using Microsoft.AspNetCore.Builder; - using Ocelot.Configuration; - using Ocelot.Configuration.Creator; - using Ocelot.Configuration.File; - using Ocelot.Configuration.Repository; - using Ocelot.Configuration.Setter; - using Ocelot.Responses; - using Ocelot.Logging; - using Rafty.Concensus; - using Rafty.Infrastructure; - using Ocelot.Middleware.Pipeline; - using Pivotal.Discovery.Client; - using Rafty.Concensus.Node; - - public static class OcelotMiddlewareExtensions - { - public static async Task UseOcelot(this IApplicationBuilder builder) - { - await builder.UseOcelot(new OcelotPipelineConfiguration()); - - return builder; - } - - public static async Task UseOcelot(this IApplicationBuilder builder, Action pipelineConfiguration) - { - var config = new OcelotPipelineConfiguration(); - pipelineConfiguration?.Invoke(config); - return await builder.UseOcelot(config); - } - public static async Task UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) - { - var configuration = await CreateConfiguration(builder); - - CreateAdministrationArea(builder, configuration); - - if (UsingRafty(builder)) - { - SetUpRafty(builder); - } - - if (UsingEurekaServiceDiscoveryProvider(configuration)) - { - builder.UseDiscoveryClient(); - } - - ConfigureDiagnosticListener(builder); - - var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices); - - pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration); - - var firstDelegate = pipelineBuilder.Build(); - - /* - inject first delegate into first piece of asp.net middleware..maybe not like this - then because we are updating the http context in ocelot it comes out correct for - rest of asp.net.. - */ - - builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware"; - - builder.Use(async (context, task) => - { - var downstreamContext = new DownstreamContext(context); - await firstDelegate.Invoke(downstreamContext); - }); - - return builder; - } - - private static bool UsingEurekaServiceDiscoveryProvider(IInternalConfiguration configuration) - { - return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "eureka"; - } - - private static bool UsingRafty(IApplicationBuilder builder) - { - var possible = builder.ApplicationServices.GetService(typeof(INode)) as INode; - if (possible != null) - { - return true; - } - - return false; - } - - private static void SetUpRafty(IApplicationBuilder builder) - { - var applicationLifetime = (IApplicationLifetime)builder.ApplicationServices.GetService(typeof(IApplicationLifetime)); - applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder)); - var node = (INode)builder.ApplicationServices.GetService(typeof(INode)); - var nodeId = (NodeId)builder.ApplicationServices.GetService(typeof(NodeId)); - node.Start(nodeId); - } - - private static async Task CreateConfiguration(IApplicationBuilder builder) - { - // make configuration from file system? - // earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this - var fileConfig = (IOptions)builder.ApplicationServices.GetService(typeof(IOptions)); - - // now create the config - var internalConfigCreator = (IInternalConfigurationCreator)builder.ApplicationServices.GetService(typeof(IInternalConfigurationCreator)); - var internalConfig = await internalConfigCreator.Create(fileConfig.Value); - - // now save it in memory - var internalConfigRepo = (IInternalConfigurationRepository)builder.ApplicationServices.GetService(typeof(IInternalConfigurationRepository)); - internalConfigRepo.AddOrReplace(internalConfig.Data); - - var fileConfigRepo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository)); - - var adminPath = (IAdministrationPath)builder.ApplicationServices.GetService(typeof(IAdministrationPath)); - - if (UsingConsul(fileConfigRepo)) +namespace Ocelot.Middleware +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Options; + using System.Diagnostics; + using DependencyInjection; + using Microsoft.AspNetCore.Builder; + using Ocelot.Configuration; + using Ocelot.Configuration.Creator; + using Ocelot.Configuration.File; + using Ocelot.Configuration.Repository; + using Ocelot.Configuration.Setter; + using Ocelot.Responses; + using Ocelot.Logging; + using Rafty.Concensus; + using Rafty.Infrastructure; + using Ocelot.Middleware.Pipeline; + using Pivotal.Discovery.Client; + using Rafty.Concensus.Node; + + public static class OcelotMiddlewareExtensions + { + public static async Task UseOcelot(this IApplicationBuilder builder) + { + await builder.UseOcelot(new OcelotPipelineConfiguration()); + + return builder; + } + + public static async Task UseOcelot(this IApplicationBuilder builder, Action pipelineConfiguration) + { + var config = new OcelotPipelineConfiguration(); + pipelineConfiguration?.Invoke(config); + return await builder.UseOcelot(config); + } + public static async Task UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) + { + var configuration = await CreateConfiguration(builder); + + CreateAdministrationArea(builder, configuration); + + if (UsingRafty(builder)) { - //Lots of jazz happens in here..check it out if you are using consul to store your config. - await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo); - } - else if(AdministrationApiInUse(adminPath)) + SetUpRafty(builder); + } + + if (UsingEurekaServiceDiscoveryProvider(configuration)) + { + builder.UseDiscoveryClient(); + } + + ConfigureDiagnosticListener(builder); + + var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices); + + pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration); + + var firstDelegate = pipelineBuilder.Build(); + + /* + inject first delegate into first piece of asp.net middleware..maybe not like this + then because we are updating the http context in ocelot it comes out correct for + rest of asp.net.. + */ + + builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware"; + + builder.Use(async (context, task) => + { + var downstreamContext = new DownstreamContext(context); + await firstDelegate.Invoke(downstreamContext); + }); + + return builder; + } + + private static bool UsingEurekaServiceDiscoveryProvider(IInternalConfiguration configuration) + { + return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "eureka"; + } + + private static bool UsingRafty(IApplicationBuilder builder) + { + var possible = builder.ApplicationServices.GetService(typeof(INode)) as INode; + if (possible != null) + { + return true; + } + + return false; + } + + private static void SetUpRafty(IApplicationBuilder builder) + { + var applicationLifetime = (IApplicationLifetime)builder.ApplicationServices.GetService(typeof(IApplicationLifetime)); + applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder)); + var node = (INode)builder.ApplicationServices.GetService(typeof(INode)); + var nodeId = (NodeId)builder.ApplicationServices.GetService(typeof(NodeId)); + node.Start(nodeId); + } + + private static async Task CreateConfiguration(IApplicationBuilder builder) + { + // make configuration from file system? + // earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this + var fileConfig = (IOptions)builder.ApplicationServices.GetService(typeof(IOptions)); + + // now create the config + var internalConfigCreator = (IInternalConfigurationCreator)builder.ApplicationServices.GetService(typeof(IInternalConfigurationCreator)); + var internalConfig = await internalConfigCreator.Create(fileConfig.Value); + + // now save it in memory + var internalConfigRepo = (IInternalConfigurationRepository)builder.ApplicationServices.GetService(typeof(IInternalConfigurationRepository)); + internalConfigRepo.AddOrReplace(internalConfig.Data); + + var fileConfigRepo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository)); + + var adminPath = (IAdministrationPath)builder.ApplicationServices.GetService(typeof(IAdministrationPath)); + + if (UsingConsul(fileConfigRepo)) + { + //Lots of jazz happens in here..check it out if you are using consul to store your config. + await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo); + } + else if(AdministrationApiInUse(adminPath)) { //We have to make sure the file config is set for the ocelot.env.json and ocelot.json so that if we pull it from the - //admin api it works...boy this is getting a spit spags boll. - var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); - - await SetFileConfig(fileConfigSetter, fileConfig); - } - - return GetOcelotConfigAndReturn(internalConfigRepo); - } - - private static bool AdministrationApiInUse(IAdministrationPath adminPath) - { - return adminPath.GetType() != typeof(NullAdministrationPath); - } - - private static async Task SetFileConfigInConsul(IApplicationBuilder builder, - IFileConfigurationRepository fileConfigRepo, IOptions fileConfig, - IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo) - { - // get the config from consul. - var fileConfigFromConsul = await fileConfigRepo.Get(); - - if (IsError(fileConfigFromConsul)) - { - ThrowToStopOcelotStarting(fileConfigFromConsul); - } - else if (ConfigNotStoredInConsul(fileConfigFromConsul)) - { - //there was no config in consul set the file in config in consul - await fileConfigRepo.Set(fileConfig.Value); - } - else - { - // create the internal config from consul data - var internalConfig = await internalConfigCreator.Create(fileConfigFromConsul.Data); - - if (IsError(internalConfig)) - { - ThrowToStopOcelotStarting(internalConfig); - } - else - { - // add the internal config to the internal repo - var response = internalConfigRepo.AddOrReplace(internalConfig.Data); - - if (IsError(response)) - { - ThrowToStopOcelotStarting(response); - } - } - - if (IsError(internalConfig)) - { - ThrowToStopOcelotStarting(internalConfig); - } - } - - //todo - this starts the poller if it has been registered...please this is so bad. - var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller)); - } - - private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptions fileConfig) - { - var response = await fileConfigSetter.Set(fileConfig.Value); - - if (IsError(response)) - { - ThrowToStopOcelotStarting(response); - } - } - - private static bool ConfigNotStoredInConsul(Responses.Response fileConfigFromConsul) - { - return fileConfigFromConsul.Data == null; - } - - private static bool IsError(Response response) - { - return response == null || response.IsError; - } - - private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider) - { - var ocelotConfiguration = provider.Get(); - - if (ocelotConfiguration?.Data == null || ocelotConfiguration.IsError) - { - ThrowToStopOcelotStarting(ocelotConfiguration); - } - - return ocelotConfiguration.Data; - } - - private static void ThrowToStopOcelotStarting(Response config) - { - throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}"); - } - - private static bool UsingConsul(IFileConfigurationRepository fileConfigRepo) - { - return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository); - } - - private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration) - { - if (!string.IsNullOrEmpty(configuration.AdministrationPath)) - { - builder.Map(configuration.AdministrationPath, app => - { - //todo - hack so we know that we are using internal identity server - var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration)); - if (identityServerConfiguration != null) - { - app.UseIdentityServer(); - } - - app.UseAuthentication(); - app.UseMvc(); - }); - } - } - - private static void ConfigureDiagnosticListener(IApplicationBuilder builder) - { - var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment)); - var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener)); - var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener)); - diagnosticListener.SubscribeWithAdapter(listener); - } - - private static void OnShutdown(IApplicationBuilder app) - { - var node = (INode)app.ApplicationServices.GetService(typeof(INode)); - node.Stop(); - } - } -} + //admin api it works...boy this is getting a spit spags boll. + var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); + + await SetFileConfig(fileConfigSetter, fileConfig); + } + + return GetOcelotConfigAndReturn(internalConfigRepo); + } + + private static bool AdministrationApiInUse(IAdministrationPath adminPath) + { + return adminPath.GetType() != typeof(NullAdministrationPath); + } + + private static async Task SetFileConfigInConsul(IApplicationBuilder builder, + IFileConfigurationRepository fileConfigRepo, IOptions fileConfig, + IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo) + { + // get the config from consul. + var fileConfigFromConsul = await fileConfigRepo.Get(); + + if (IsError(fileConfigFromConsul)) + { + ThrowToStopOcelotStarting(fileConfigFromConsul); + } + else if (ConfigNotStoredInConsul(fileConfigFromConsul)) + { + //there was no config in consul set the file in config in consul + await fileConfigRepo.Set(fileConfig.Value); + } + else + { + // create the internal config from consul data + var internalConfig = await internalConfigCreator.Create(fileConfigFromConsul.Data); + + if (IsError(internalConfig)) + { + ThrowToStopOcelotStarting(internalConfig); + } + else + { + // add the internal config to the internal repo + var response = internalConfigRepo.AddOrReplace(internalConfig.Data); + + if (IsError(response)) + { + ThrowToStopOcelotStarting(response); + } + } + + if (IsError(internalConfig)) + { + ThrowToStopOcelotStarting(internalConfig); + } + } + } + + private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptions fileConfig) + { + var response = await fileConfigSetter.Set(fileConfig.Value); + + if (IsError(response)) + { + ThrowToStopOcelotStarting(response); + } + } + + private static bool ConfigNotStoredInConsul(Responses.Response fileConfigFromConsul) + { + return fileConfigFromConsul.Data == null; + } + + private static bool IsError(Response response) + { + return response == null || response.IsError; + } + + private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider) + { + var ocelotConfiguration = provider.Get(); + + if (ocelotConfiguration?.Data == null || ocelotConfiguration.IsError) + { + ThrowToStopOcelotStarting(ocelotConfiguration); + } + + return ocelotConfiguration.Data; + } + + private static void ThrowToStopOcelotStarting(Response config) + { + throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}"); + } + + private static bool UsingConsul(IFileConfigurationRepository fileConfigRepo) + { + return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository); + } + + private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration) + { + if (!string.IsNullOrEmpty(configuration.AdministrationPath)) + { + builder.Map(configuration.AdministrationPath, app => + { + //todo - hack so we know that we are using internal identity server + var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration)); + if (identityServerConfiguration != null) + { + app.UseIdentityServer(); + } + + app.UseAuthentication(); + app.UseMvc(); + }); + } + } + + private static void ConfigureDiagnosticListener(IApplicationBuilder builder) + { + var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment)); + var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener)); + var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener)); + diagnosticListener.SubscribeWithAdapter(listener); + } + + private static void OnShutdown(IApplicationBuilder app) + { + var node = (INode)app.ApplicationServices.GetService(typeof(INode)); + node.Stop(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index 5a5cce82..af99888d 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -1,390 +1,391 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Text; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using static Ocelot.Infrastructure.Wait; - -namespace Ocelot.AcceptanceTests -{ - public class ConfigurationInConsulTests : IDisposable - { - private IWebHost _builder; - private readonly Steps _steps; - private IWebHost _fakeConsulBuilder; - private FileConfiguration _config; - - public ConfigurationInConsulTests() - { - _steps = new Steps(); - } - - [Fact] - public void should_return_response_200_with_simple_url() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51779, - } - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = 9500 - } - } - }; - - var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; - - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_simple_url_when_using_jsonserialized_cache() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51779, - } - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = 9502 - } - } - }; - - var fakeConsulServiceDiscoveryUrl = "http://localhost:9502"; - - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_load_configuration_out_of_consul() - { - var consulPort = 8500; - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - - var consulConfig = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/status", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51779, - } - }, - UpstreamPathTemplate = "/cs/status", - UpstreamHttpMethod = new List {"Get"} - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) - .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_load_configuration_out_of_consul_if_it_is_changed() - { - var consulPort = 8506; - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - - var consulConfig = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/status", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51780, - } - }, - UpstreamPathTemplate = "/cs/status", - UpstreamHttpMethod = new List {"Get"} - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - var secondConsulConfig = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/status", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51780, - } - }, - UpstreamPathTemplate = "/cs/status/awesome", - UpstreamHttpMethod = new List {"Get"} - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) - .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51780", "/status", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) - .And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status")) - .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .When(x => GivenTheConsulConfigurationIs(secondConsulConfig)) - .Then(x => ThenTheConfigIsUpdatedInOcelot()) - .BDDfy(); - } - - private void ThenTheConfigIsUpdatedInOcelot() - { - var result = WaitFor(20000).Until(() => { - try - { - _steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome"); - _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK); - _steps.ThenTheResponseBodyShouldBe("Hello from Laura"); - return true; - } - catch (Exception) - { - return false; - } - }); - result.ShouldBeTrue(); - } - - private void GivenTheConsulConfigurationIs(FileConfiguration config) - { - _config = config; - } - - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) - { - _fakeConsulBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") - { - var json = JsonConvert.SerializeObject(_config); - - var bytes = Encoding.UTF8.GetBytes(json); - - var base64 = Convert.ToBase64String(bytes); - - var kvp = new FakeConsulGetResponse(base64); - - await context.Response.WriteJsonAsync(new FakeConsulGetResponse[] { kvp }); - } - else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") - { - try - { - var reader = new StreamReader(context.Request.Body); - - var json = reader.ReadToEnd(); - - _config = JsonConvert.DeserializeObject(json); - - var response = JsonConvert.SerializeObject(true); - - await context.Response.WriteAsync(response); - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } - } - }); - }) - .Build(); - - _fakeConsulBuilder.Start(); - } - - public class FakeConsulGetResponse - { - public FakeConsulGetResponse(string value) - { - Value = value; - } - - public int CreateIndex => 100; - public int ModifyIndex => 200; - public int LockIndex => 200; - public string Key => "InternalConfiguration"; - public int Flags => 0; - public string Value { get; private set; } - public string Session => "adf4238a-882b-9ddc-4a9d-5b6758e4159e"; - } - - private void GivenThereIsAServiceRunningOn(string url, string basePath, int statusCode, string responseBody) - { - _builder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.UsePathBase(basePath); - - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _builder.Start(); - } - - public void Dispose() - { - _builder?.Dispose(); - _steps.Dispose(); - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Text; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using static Ocelot.Infrastructure.Wait; + +namespace Ocelot.AcceptanceTests +{ + public class ConfigurationInConsulTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + private IWebHost _fakeConsulBuilder; + private FileConfiguration _config; + + public ConfigurationInConsulTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_return_response_200_with_simple_url() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51779, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = 9500 + } + } + }; + + var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; + + this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_simple_url_when_using_jsonserialized_cache() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51779, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = 9502 + } + } + }; + + var fakeConsulServiceDiscoveryUrl = "http://localhost:9502"; + + this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_load_configuration_out_of_consul() + { + var consulPort = 8500; + + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort + } + } + }; + + var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; + + var consulConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/status", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51779, + } + }, + UpstreamPathTemplate = "/cs/status", + UpstreamHttpMethod = new List {"Get"} + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort + } + } + }; + + this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) + .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_load_configuration_out_of_consul_if_it_is_changed() + { + var consulPort = 8506; + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort + } + } + }; + + var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; + + var consulConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/status", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51780, + } + }, + UpstreamPathTemplate = "/cs/status", + UpstreamHttpMethod = new List {"Get"} + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort + } + } + }; + + var secondConsulConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/status", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51780, + } + }, + UpstreamPathTemplate = "/cs/status/awesome", + UpstreamHttpMethod = new List {"Get"} + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort + } + } + }; + + this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) + .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51780", "/status", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .When(x => GivenTheConsulConfigurationIs(secondConsulConfig)) + .Then(x => ThenTheConfigIsUpdatedInOcelot()) + .BDDfy(); + } + + private void ThenTheConfigIsUpdatedInOcelot() + { + var result = WaitFor(20000).Until(() => { + try + { + _steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome"); + _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK); + _steps.ThenTheResponseBodyShouldBe("Hello from Laura"); + return true; + } + catch (Exception) + { + return false; + } + }); + result.ShouldBeTrue(); + } + + private void GivenTheConsulConfigurationIs(FileConfiguration config) + { + _config = config; + } + + private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) + { + _fakeConsulBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") + { + var json = JsonConvert.SerializeObject(_config); + + var bytes = Encoding.UTF8.GetBytes(json); + + var base64 = Convert.ToBase64String(bytes); + + var kvp = new FakeConsulGetResponse(base64); + + await context.Response.WriteJsonAsync(new FakeConsulGetResponse[] { kvp }); + } + else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") + { + try + { + var reader = new StreamReader(context.Request.Body); + + var json = reader.ReadToEnd(); + + _config = JsonConvert.DeserializeObject(json); + + var response = JsonConvert.SerializeObject(true); + + await context.Response.WriteAsync(response); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + }); + }) + .Build(); + + _fakeConsulBuilder.Start(); + } + + public class FakeConsulGetResponse + { + public FakeConsulGetResponse(string value) + { + Value = value; + } + + public int CreateIndex => 100; + public int ModifyIndex => 200; + public int LockIndex => 200; + public string Key => "InternalConfiguration"; + public int Flags => 0; + public string Value { get; private set; } + public string Session => "adf4238a-882b-9ddc-4a9d-5b6758e4159e"; + } + + private void GivenThereIsAServiceRunningOn(string url, string basePath, int statusCode, string responseBody) + { + _builder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.UsePathBase(basePath); + + 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.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationPollerTests.cs similarity index 73% rename from test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs rename to test/Ocelot.UnitTests/Configuration/FileConfigurationPollerTests.cs index 17fc604d..18e8ddb6 100644 --- a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationPollerTests.cs @@ -12,37 +12,39 @@ using TestStack.BDDfy; using Xunit; using Shouldly; using static Ocelot.Infrastructure.Wait; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration; namespace Ocelot.UnitTests.Configuration { - public class ConsulFileConfigurationPollerTests : IDisposable + public class FileConfigurationPollerTests : IDisposable { - private readonly ConsulFileConfigurationPoller _poller; + private readonly FileConfigurationPoller _poller; private Mock _factory; private readonly Mock _repo; - private readonly Mock _setter; private readonly FileConfiguration _fileConfig; - private Mock _config; + private Mock _config; + private readonly Mock _internalConfigRepo; + private readonly Mock _internalConfigCreator; + private IInternalConfiguration _internalConfig; - public ConsulFileConfigurationPollerTests() + public FileConfigurationPollerTests() { var logger = new Mock(); _factory = new Mock(); - _factory.Setup(x => x.CreateLogger()).Returns(logger.Object); + _factory.Setup(x => x.CreateLogger()).Returns(logger.Object); _repo = new Mock(); - _setter = new Mock(); _fileConfig = new FileConfiguration(); - _config = new Mock(); + _config = new Mock(); _repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse(_fileConfig)); _config.Setup(x => x.Delay).Returns(100); - _poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object, _config.Object); + _internalConfigRepo = new Mock(); + _internalConfigCreator = new Mock(); + _internalConfigCreator.Setup(x => x.Create(It.IsAny())).ReturnsAsync(new OkResponse(_internalConfig)); + _poller = new FileConfigurationPoller(_factory.Object, _repo.Object, _config.Object, _internalConfigRepo.Object, _internalConfigCreator.Object); + _poller.StartAsync(new CancellationToken()); } - - public void Dispose() - { - _poller.Dispose(); - } - + [Fact] public void should_start() { @@ -141,10 +143,11 @@ namespace Ocelot.UnitTests.Configuration private void ThenTheSetterIsCalled(FileConfiguration fileConfig, int times) { - var result = WaitFor(2000).Until(() => { + var result = WaitFor(4000).Until(() => { try { - _setter.Verify(x => x.Set(fileConfig), Times.Exactly(times)); + _internalConfigRepo.Verify(x => x.AddOrReplace(_internalConfig), Times.Exactly(times)); + _internalConfigCreator.Verify(x => x.Create(fileConfig), Times.Exactly(times)); return true; } catch(Exception) @@ -157,10 +160,11 @@ namespace Ocelot.UnitTests.Configuration private void ThenTheSetterIsCalledAtLeast(FileConfiguration fileConfig, int times) { - var result = WaitFor(2000).Until(() => { + var result = WaitFor(4000).Until(() => { try { - _setter.Verify(x => x.Set(fileConfig), Times.AtLeast(times)); + _internalConfigRepo.Verify(x => x.AddOrReplace(_internalConfig), Times.AtLeast(times)); + _internalConfigCreator.Verify(x => x.Create(fileConfig), Times.AtLeast(times)); return true; } catch(Exception) @@ -170,5 +174,10 @@ namespace Ocelot.UnitTests.Configuration }); result.ShouldBeTrue(); } + + public void Dispose() + { + _poller.Dispose(); + } } } From b0a20d13b93acb829ba1c9c6ee25b77564f49fec Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 29 Jul 2018 20:32:45 +0100 Subject: [PATCH 02/50] Feature/more dynamic routes (#508) * Made the file config poller use IHostedService, bit more generic, not just need to provide the correct implementations of the repo services and it will poll anything..this means we can open up redis for #458 * removed comments * #458 allow users to set rate limits per service for dynamic re routes * #458 added docs for rate limting on dynamic reroutes --- docs/features/servicediscovery.rst | 50 +++- .../FileInternalConfigurationCreator.cs | 27 ++- .../Creator/IRateLimitOptionsCreator.cs | 18 +- .../Creator/RateLimitOptionsCreator.cs | 64 +++--- .../Creator/ReRouteOptionsCreator.cs | 88 +++---- .../Configuration/File/FileConfiguration.cs | 42 ++-- .../Configuration/File/FileDynamicReRoute.cs | 8 + .../File/FileRateLimitOptions.cs | 74 +++--- .../Configuration/File/FileRateLimitRule.cs | 104 ++++----- src/Ocelot/Configuration/ReRouteOptions.cs | 36 +-- .../Finder/DownstreamRouteCreator.cs | 21 +- .../Finder/DownstreamRouteProviderFactory.cs | 4 +- .../DownstreamRouteFinderMiddleware.cs | 1 + .../ClientRateLimitTests.cs | 1 - .../ConfigurationInConsulTests.cs | 110 ++++++++- .../FileInternalConfigurationCreatorTests.cs | 61 ++++- .../RateLimitOptionsCreatorTests.cs | 216 +++++++++--------- .../DownstreamRouteCreatorTests.cs | 47 +++- .../DownstreamRouteProviderFactoryTests.cs | 30 +++ 19 files changed, 652 insertions(+), 350 deletions(-) create mode 100644 src/Ocelot/Configuration/File/FileDynamicReRoute.cs diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index bc89f97c..e6cb3695 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -125,20 +125,14 @@ is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them Dynamic Routing ^^^^^^^^^^^^^^^ -This feature was requested in `issue 340 `_. The idea is to enable dynamic routing when using -a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segmentof the upstream path to lookup the -downstream service with the service discovery provider. +This feature was requested in `issue 340 `_. The idea is to enable dynamic routing when using a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segment of the upstream path to lookup the downstream service with the service discovery provider. An example of this would be calling ocelot with a url like https://api.mywebsite.com/product/products. Ocelot will take the first segment of -the path which is product and use it as a key to look up the service in consul. If consul returns a service Ocelot will request it on whatever host and -port comes back from consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products. -Ocelot will apprend any query string to the downstream url as normal. +the path which is product and use it as a key to look up the service in consul. If consul returns a service Ocelot will request it on whatever host and port comes back from consul plus the remaining path segments in this case products thus making the downstream call http://hostfromconsul:portfromconsul/products. Ocelot will apprend any query string to the downstream url as normal. -In order to enable dynamic routing you need to have 0 ReRoutes in your config. At the moment you cannot mix dynamic and configuration ReRoutes. In addition to this you -need to specify the Service Discovery provider details as outlined above and the downstream http/https scheme as DownstreamScheme. +In order to enable dynamic routing you need to have 0 ReRoutes in your config. At the moment you cannot mix dynamic and configuration ReRoutes. In addition to this you need to specify the Service Discovery provider details as outlined above and the downstream http/https scheme as DownstreamScheme. -In addition to that you can set RateLimitOptions, QoSOptions, LoadBalancerOptions and HttpHandlerOptions, DownstreamScheme (You might want to call Ocelot on https but -talk to private services over http) that will be applied to all of the dynamic ReRoutes. +In addition to that you can set RateLimitOptions, QoSOptions, LoadBalancerOptions and HttpHandlerOptions, DownstreamScheme (You might want to call Ocelot on https but talk to private services over http) that will be applied to all of the dynamic ReRoutes. The config might look something like @@ -183,4 +177,40 @@ The config might look something like } } +Ocelot also allows you to set DynamicReRoutes which lets you set rate limiting rules per downstream service. This is useful if you have for example a product and search service and you want to rate limit one more than the other. An example of this would be as follows. + +.. code-block:: json + + { + "DynamicReRoutes": [ + { + "ServiceName": "product", + "RateLimitRule": { + "ClientWhitelist": [], + "EnableRateLimiting": true, + "Period": "1s", + "PeriodTimespan": 1000.0, + "Limit": 3 + } + } + ], + "GlobalConfiguration": { + "RequestIdKey": null, + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 8523, + }, + "RateLimitOptions": { + "ClientIdHeader": "ClientId", + "QuotaExceededMessage": "", + "RateLimitCounterPrefix": "", + "DisableRateLimitHeaders": false, + "HttpStatusCode": 428 + } + "DownstreamScheme": "http", + } + } + +This configuration means that if you have a request come into Ocelot on /product/* then dynamic routing will kick in and ocelot will use the rate limiting set against the product service in the DynamicReRoutes section. + Please take a look through all of the docs to understand these options. diff --git a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs index 76934434..d393b648 100644 --- a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs @@ -103,6 +103,12 @@ namespace Ocelot.Configuration.Creator reRoutes.Add(ocelotReRoute); } + foreach(var fileDynamicReRoute in fileConfiguration.DynamicReRoutes) + { + var reRoute = SetUpDynamicReRoute(fileDynamicReRoute, fileConfiguration.GlobalConfiguration); + reRoutes.Add(reRoute); + } + var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration); var lbOptions = CreateLoadBalancerOptions(fileConfiguration.GlobalConfiguration.LoadBalancerOptions); @@ -124,7 +130,24 @@ namespace Ocelot.Configuration.Creator return new OkResponse(config); } - public ReRoute SetUpAggregateReRoute(List reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) + private ReRoute SetUpDynamicReRoute(FileDynamicReRoute fileDynamicReRoute, FileGlobalConfiguration globalConfiguration) + { + var rateLimitOption = _rateLimitOptionsCreator.Create(fileDynamicReRoute.RateLimitRule, globalConfiguration); + + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithEnableRateLimiting(true) + .WithRateLimitOptions(rateLimitOption) + .WithServiceName(fileDynamicReRoute.ServiceName) + .Build(); + + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .Build(); + + return reRoute; + } + + private ReRoute SetUpAggregateReRoute(List reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) { var applicableReRoutes = reRoutes .SelectMany(x => x.DownstreamReRoute) @@ -186,7 +209,7 @@ namespace Ocelot.Configuration.Creator var qosOptions = _qosOptionsCreator.Create(fileReRoute.QoSOptions, fileReRoute.UpstreamPathTemplate, fileReRoute.UpstreamHttpMethod.ToArray()); - var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting); + var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute.RateLimitOptions, globalConfiguration); var region = _regionCreator.Create(fileReRoute); diff --git a/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs index 91beea74..e9096786 100644 --- a/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs @@ -1,9 +1,9 @@ -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public interface IRateLimitOptionsCreator - { - RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting); - } -} \ No newline at end of file +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IRateLimitOptionsCreator + { + RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration); + } +} diff --git a/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs b/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs index 5d987aee..10e63f6f 100644 --- a/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs @@ -1,32 +1,32 @@ -using System; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public class RateLimitOptionsCreator : IRateLimitOptionsCreator - { - public RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting) - { - RateLimitOptions rateLimitOption = null; - - if (enableRateLimiting) - { - rateLimitOption = new RateLimitOptionsBuilder() - .WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader) - .WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) - .WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders) - .WithEnableRateLimiting(fileReRoute.RateLimitOptions.EnableRateLimiting) - .WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode) - .WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage) - .WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix) - .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, - fileReRoute.RateLimitOptions.PeriodTimespan, - fileReRoute.RateLimitOptions.Limit)) - .Build(); - } - - return rateLimitOption; - } - } -} +using System; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class RateLimitOptionsCreator : IRateLimitOptionsCreator + { + public RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration) + { + RateLimitOptions rateLimitOption = null; + + if (fileRateLimitRule != null && fileRateLimitRule.EnableRateLimiting) + { + rateLimitOption = new RateLimitOptionsBuilder() + .WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader) + .WithClientWhiteList(fileRateLimitRule.ClientWhitelist) + .WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders) + .WithEnableRateLimiting(fileRateLimitRule.EnableRateLimiting) + .WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode) + .WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage) + .WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix) + .WithRateLimitRule(new RateLimitRule(fileRateLimitRule.Period, + fileRateLimitRule.PeriodTimespan, + fileRateLimitRule.Limit)) + .Build(); + } + + return rateLimitOption; + } + } +} diff --git a/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs b/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs index c777a50a..3a8d1811 100644 --- a/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs @@ -1,45 +1,45 @@ -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public class ReRouteOptionsCreator : IReRouteOptionsCreator - { - public ReRouteOptions Create(FileReRoute fileReRoute) - { - var isAuthenticated = IsAuthenticated(fileReRoute); - var isAuthorised = IsAuthorised(fileReRoute); - var isCached = IsCached(fileReRoute); - var enableRateLimiting = IsEnableRateLimiting(fileReRoute); - - var options = new ReRouteOptionsBuilder() - .WithIsAuthenticated(isAuthenticated) - .WithIsAuthorised(isAuthorised) - .WithIsCached(isCached) - .WithRateLimiting(enableRateLimiting) - .Build(); - - return options; - } - - private static bool IsEnableRateLimiting(FileReRoute fileReRoute) - { - return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false; - } - - private bool IsAuthenticated(FileReRoute fileReRoute) - { - return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.AuthenticationProviderKey); - } - - private bool IsAuthorised(FileReRoute fileReRoute) - { - return fileReRoute.RouteClaimsRequirement?.Count > 0; - } - - private bool IsCached(FileReRoute fileReRoute) - { - return fileReRoute.FileCacheOptions.TtlSeconds > 0; - } +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class ReRouteOptionsCreator : IReRouteOptionsCreator + { + public ReRouteOptions Create(FileReRoute fileReRoute) + { + var isAuthenticated = IsAuthenticated(fileReRoute); + var isAuthorised = IsAuthorised(fileReRoute); + var isCached = IsCached(fileReRoute); + var enableRateLimiting = IsEnableRateLimiting(fileReRoute); + + var options = new ReRouteOptionsBuilder() + .WithIsAuthenticated(isAuthenticated) + .WithIsAuthorised(isAuthorised) + .WithIsCached(isCached) + .WithRateLimiting(enableRateLimiting) + .Build(); + + return options; + } + + private static bool IsEnableRateLimiting(FileReRoute fileReRoute) + { + return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false; + } + + private bool IsAuthenticated(FileReRoute fileReRoute) + { + return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.AuthenticationProviderKey); + } + + private bool IsAuthorised(FileReRoute fileReRoute) + { + return fileReRoute.RouteClaimsRequirement?.Count > 0; + } + + private bool IsCached(FileReRoute fileReRoute) + { + return fileReRoute.FileCacheOptions.TtlSeconds > 0; + } } -} +} diff --git a/src/Ocelot/Configuration/File/FileConfiguration.cs b/src/Ocelot/Configuration/File/FileConfiguration.cs index e38ae6cb..49eb0c9e 100644 --- a/src/Ocelot/Configuration/File/FileConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileConfiguration.cs @@ -1,20 +1,22 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration.File -{ - public class FileConfiguration - { - public FileConfiguration() - { - ReRoutes = new List(); - GlobalConfiguration = new FileGlobalConfiguration(); - Aggregates = new List(); - } - - public List ReRoutes { get; set; } - - // Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates - public List Aggregates { get;set; } - public FileGlobalConfiguration GlobalConfiguration { get; set; } - } -} +using System.Collections.Generic; + +namespace Ocelot.Configuration.File +{ + public class FileConfiguration + { + public FileConfiguration() + { + ReRoutes = new List(); + GlobalConfiguration = new FileGlobalConfiguration(); + Aggregates = new List(); + DynamicReRoutes = new List(); + } + + public List ReRoutes { get; set; } + public List DynamicReRoutes { get; set; } + + // Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates + public List Aggregates { get;set; } + public FileGlobalConfiguration GlobalConfiguration { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileDynamicReRoute.cs b/src/Ocelot/Configuration/File/FileDynamicReRoute.cs new file mode 100644 index 00000000..26d8b4d4 --- /dev/null +++ b/src/Ocelot/Configuration/File/FileDynamicReRoute.cs @@ -0,0 +1,8 @@ +namespace Ocelot.Configuration.File +{ + public class FileDynamicReRoute + { + public string ServiceName { get; set; } + public FileRateLimitRule RateLimitRule { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs index c7f99cb0..05c6fac4 100644 --- a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs +++ b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs @@ -1,37 +1,37 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.Configuration.File -{ - public class FileRateLimitOptions - { - /// - /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId - /// - public string ClientIdHeader { get; set; } = "ClientId"; - - /// - /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. - /// If none specified the default will be: - /// API calls quota exceeded! maximum admitted {0} per {1} - /// - public string QuotaExceededMessage { get; set; } - - /// - /// Gets or sets the counter prefix, used to compose the rate limit counter cache key - /// - public string RateLimitCounterPrefix { get; set; } = "ocelot"; - - /// - /// Disables X-Rate-Limit and Rety-After headers - /// - public bool DisableRateLimitHeaders { get; set; } - - /// - /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) - /// - public int HttpStatusCode { get; set; } = 429; - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Configuration.File +{ + public class FileRateLimitOptions + { + /// + /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId + /// + public string ClientIdHeader { get; set; } = "ClientId"; + + /// + /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. + /// If none specified the default will be: + /// API calls quota exceeded! maximum admitted {0} per {1} + /// + public string QuotaExceededMessage { get; set; } + + /// + /// Gets or sets the counter prefix, used to compose the rate limit counter cache key + /// + public string RateLimitCounterPrefix { get; set; } = "ocelot"; + + /// + /// Disables X-Rate-Limit and Rety-After headers + /// + public bool DisableRateLimitHeaders { get; set; } + + /// + /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) + /// + public int HttpStatusCode { get; set; } = 429; + } +} diff --git a/src/Ocelot/Configuration/File/FileRateLimitRule.cs b/src/Ocelot/Configuration/File/FileRateLimitRule.cs index 7d1ca1ef..6ebdb2bd 100644 --- a/src/Ocelot/Configuration/File/FileRateLimitRule.cs +++ b/src/Ocelot/Configuration/File/FileRateLimitRule.cs @@ -1,52 +1,52 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Ocelot.Infrastructure.Extensions; - -namespace Ocelot.Configuration.File -{ - public class FileRateLimitRule - { - public FileRateLimitRule() - { - ClientWhitelist = new List(); - } - - public List ClientWhitelist { get; set; } - - /// - /// Enables endpoint rate limiting based URL path and HTTP verb - /// - public bool EnableRateLimiting { get; set; } - - /// - /// Rate limit period as in 1s, 1m, 1h - /// - public string Period { get; set; } - - public double PeriodTimespan { get; set; } - - /// - /// Maximum number of requests that a client can make in a defined period - /// - public long Limit { get; set; } - - public override string ToString() - { - if (!EnableRateLimiting) - { - return string.Empty; - } - - var sb = new StringBuilder(); - sb.Append( - $"{nameof(Period)}:{Period},{nameof(PeriodTimespan)}:{PeriodTimespan:F},{nameof(Limit)}:{Limit},{nameof(ClientWhitelist)}:["); - - sb.AppendJoin(',', ClientWhitelist); - sb.Append(']'); - return sb.ToString(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Ocelot.Infrastructure.Extensions; + +namespace Ocelot.Configuration.File +{ + public class FileRateLimitRule + { + public FileRateLimitRule() + { + ClientWhitelist = new List(); + } + + public List ClientWhitelist { get; set; } + + /// + /// Enables endpoint rate limiting based URL path and HTTP verb + /// + public bool EnableRateLimiting { get; set; } + + /// + /// Rate limit period as in 1s, 1m, 1h + /// + public string Period { get; set; } + + public double PeriodTimespan { get; set; } + + /// + /// Maximum number of requests that a client can make in a defined period + /// + public long Limit { get; set; } + + public override string ToString() + { + if (!EnableRateLimiting) + { + return string.Empty; + } + + var sb = new StringBuilder(); + sb.Append( + $"{nameof(Period)}:{Period},{nameof(PeriodTimespan)}:{PeriodTimespan:F},{nameof(Limit)}:{Limit},{nameof(ClientWhitelist)}:["); + + sb.AppendJoin(',', ClientWhitelist); + sb.Append(']'); + return sb.ToString(); + } + } +} diff --git a/src/Ocelot/Configuration/ReRouteOptions.cs b/src/Ocelot/Configuration/ReRouteOptions.cs index 13875ac8..074e45b6 100644 --- a/src/Ocelot/Configuration/ReRouteOptions.cs +++ b/src/Ocelot/Configuration/ReRouteOptions.cs @@ -1,18 +1,18 @@ -namespace Ocelot.Configuration -{ - public class ReRouteOptions - { - public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isEnableRateLimiting) - { - IsAuthenticated = isAuthenticated; - IsAuthorised = isAuthorised; - IsCached = isCached; - EnableRateLimiting = isEnableRateLimiting; - } - - public bool IsAuthenticated { get; private set; } - public bool IsAuthorised { get; private set; } - public bool IsCached { get; private set; } - public bool EnableRateLimiting { get; private set; } - } -} +namespace Ocelot.Configuration +{ + public class ReRouteOptions + { + public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isEnableRateLimiting) + { + IsAuthenticated = isAuthenticated; + IsAuthorised = isAuthorised; + IsCached = isCached; + EnableRateLimiting = isEnableRateLimiting; + } + + public bool IsAuthenticated { get; private set; } + public bool IsAuthorised { get; private set; } + public bool IsCached { get; private set; } + public bool EnableRateLimiting { get; private set; } + } +} diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs index 9a90edaf..f7dea902 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs @@ -2,6 +2,7 @@ { using System.Collections.Concurrent; using System.Collections.Generic; + using System.Linq; using Configuration; using Configuration.Builder; using Configuration.Creator; @@ -43,7 +44,7 @@ var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new []{ upstreamHttpMethod }); - var downstreamReRoute = new DownstreamReRouteBuilder() + var downstreamReRouteBuilder = new DownstreamReRouteBuilder() .WithServiceName(serviceName) .WithLoadBalancerKey(loadBalancerKey) .WithDownstreamPathTemplate(downstreamPath) @@ -51,8 +52,22 @@ .WithHttpHandlerOptions(configuration.HttpHandlerOptions) .WithQosOptions(qosOptions) .WithDownstreamScheme(configuration.DownstreamScheme) - .WithLoadBalancerOptions(configuration.LoadBalancerOptions) - .Build(); + .WithLoadBalancerOptions(configuration.LoadBalancerOptions); + + var rateLimitOptions = configuration.ReRoutes != null + ? configuration.ReRoutes + .SelectMany(x => x.DownstreamReRoute) + .FirstOrDefault(x => x.ServiceName == serviceName) + : null; + + if(rateLimitOptions != null) + { + downstreamReRouteBuilder + .WithRateLimitOptions(rateLimitOptions.RateLimitOptions) + .WithEnableRateLimiting(true); + } + + var downstreamReRoute = downstreamReRouteBuilder.Build(); var reRoute = new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs index fc74f679..080172d5 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs @@ -20,7 +20,9 @@ public IDownstreamRouteProvider Get(IInternalConfiguration config) { - if(!config.ReRoutes.Any() && IsServiceDiscovery(config.ServiceProviderConfiguration)) + //todo - this is a bit hacky we are saying there are no reRoutes or there are reRoutes but none of them have + //an upstream path template which means they are dyanmic and service discovery is on... + if((!config.ReRoutes.Any() || config.ReRoutes.All(x => string.IsNullOrEmpty(x.UpstreamPathTemplate.Value))) && IsServiceDiscovery(config.ServiceProviderConfiguration)) { _logger.LogInformation($"Selected {nameof(DownstreamRouteCreator)} as DownstreamRouteProvider for this request"); return _providers[nameof(DownstreamRouteCreator)]; diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index b6c4bcf6..3916721d 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -52,6 +52,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware } var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value)); + Logger.LogDebug($"downstream templates are {downstreamPathTemplates}"); context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues; diff --git a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs index 23e6dd62..d7d31abe 100644 --- a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs +++ b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs @@ -42,7 +42,6 @@ UpstreamPathTemplate = "/api/ClientRateLimit", UpstreamHttpMethod = new List { "Get" }, RequestIdKey = _steps.RequestIdKey, - RateLimitOptions = new FileRateLimitRule() { EnableRateLimiting = true, diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index af99888d..23ce3d11 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net; using System.Text; +using Consul; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -22,9 +23,11 @@ namespace Ocelot.AcceptanceTests private readonly Steps _steps; private IWebHost _fakeConsulBuilder; private FileConfiguration _config; + private List _consulServices; public ConfigurationInConsulTests() { + _consulServices = new List(); _steps = new Steps(); } @@ -63,7 +66,7 @@ namespace Ocelot.AcceptanceTests var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, "")) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) @@ -108,7 +111,7 @@ namespace Ocelot.AcceptanceTests var fakeConsulServiceDiscoveryUrl = "http://localhost:9502"; - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, "")) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache()) @@ -168,7 +171,7 @@ namespace Ocelot.AcceptanceTests }; this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) - .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, "")) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) @@ -257,7 +260,7 @@ namespace Ocelot.AcceptanceTests }; this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) - .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, "")) .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51780", "/status", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) @@ -269,6 +272,89 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_handle_request_to_consul_for_downstream_service_and_make_request_no_re_routes_and_rate_limit() + { + const int consulPort = 8523; + const string serviceName = "web"; + const int downstreamServicePort = 8187; + var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}"; + var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; + var serviceEntryOne = new ServiceEntry() + { + Service = new AgentService() + { + Service = serviceName, + Address = "localhost", + Port = downstreamServicePort, + ID = "web_90_0_2_224_8080", + Tags = new[] {"version-v1"} + }, + }; + + var consulConfig = new FileConfiguration + { + DynamicReRoutes = new List + { + new FileDynamicReRoute + { + ServiceName = serviceName, + RateLimitRule = new FileRateLimitRule() + { + EnableRateLimiting = true, + ClientWhitelist = new List(), + Limit = 3, + Period = "1s", + PeriodTimespan = 1000 + } + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Port = consulPort + }, + RateLimitOptions = new FileRateLimitOptions() + { + ClientIdHeader = "ClientId", + DisableRateLimitHeaders = false, + QuotaExceededMessage = "", + RateLimitCounterPrefix = "", + HttpStatusCode = 428 + }, + DownstreamScheme = "http", + } + }; + + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Port = consulPort + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/something", 200, "Hello from Laura")) + .And(x => GivenTheConsulConfigurationIs(consulConfig)) + .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) + .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something",1)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something", 2)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something",1)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(428)) + .BDDfy(); + } + private void ThenTheConfigIsUpdatedInOcelot() { var result = WaitFor(20000).Until(() => { @@ -292,7 +378,15 @@ namespace Ocelot.AcceptanceTests _config = config; } - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) + private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries) + { + foreach(var serviceEntry in serviceEntries) + { + _consulServices.Add(serviceEntry); + } + } + + private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName) { _fakeConsulBuilder = new WebHostBuilder() .UseUrls(url) @@ -336,6 +430,10 @@ namespace Ocelot.AcceptanceTests throw; } } + else if (context.Request.Path.Value == $"/v1/health/service/{serviceName}") + { + await context.Response.WriteJsonAsync(_consulServices); + } }); }) .Build(); diff --git a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs index af7dfed7..b9d4a422 100644 --- a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs @@ -17,6 +17,7 @@ using Ocelot.Errors; using Ocelot.UnitTests.TestData; using Ocelot.Values; + using System; public class FileInternalConfigurationCreatorTests { @@ -821,6 +822,54 @@ .BDDfy(); } + [Fact] + public void should_set_up_dynamic_re_routes() + { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithEnableRateLimiting(true) + .WithRateLimitOptions(new RateLimitOptionsBuilder().Build()) + .Build(); + + var rateLimitOptions = new RateLimitOptionsBuilder() + .WithRateLimitRule(new RateLimitRule("1s", 1, 1)) + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + DynamicReRoutes = new List + { + new FileDynamicReRoute + { + ServiceName = "test", + RateLimitRule = new FileRateLimitRule + { + Period = "1s", + PeriodTimespan = 1, + Limit = 1 + } + } + }, + })) + .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheRateLimitCreatorReturns(rateLimitOptions)) + .And(x => GivenTheDownstreamAddresses()) + .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheDynamicReRouteIsSetUp("test", rateLimitOptions.RateLimitRule)) + .BDDfy(); + } + + private void GivenTheRateLimitCreatorReturns(RateLimitOptions rateLimitOptions) + { + _rateLimitOptions + .Setup(x => x.Create(It.IsAny(), It.IsAny())) + .Returns(rateLimitOptions); + } + private void GivenTheConfigIsInvalid(List errors) { _validator @@ -844,7 +893,7 @@ private void ThenTheRateLimitOptionsCreatorIsCalledCorrectly() { _rateLimitOptions - .Verify(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + .Verify(x => x.Create(It.IsAny(), It.IsAny()), Times.Once); } private void GivenTheConfigIsValid() @@ -864,6 +913,16 @@ _config = _internalConfigurationCreator.Create(_fileConfiguration).Result; } + private void ThenTheDynamicReRouteIsSetUp(string serviceName, RateLimitRule rateLimitOptions) + { + var dynamic = _config.Data.ReRoutes[0].DownstreamReRoute[0]; + dynamic.ServiceName.ShouldBe(serviceName); + dynamic.EnableEndpointEndpointRateLimiting.ShouldBeTrue(); + dynamic.RateLimitOptions.RateLimitRule.Period.ShouldBe(rateLimitOptions.Period); + dynamic.RateLimitOptions.RateLimitRule.Limit.ShouldBe(rateLimitOptions.Limit); + dynamic.RateLimitOptions.RateLimitRule.PeriodTimespan.ShouldBe(rateLimitOptions.PeriodTimespan); + } + private void ThenTheReRoutesAre(List expectedReRoutes) { for (int i = 0; i < _config.Data.ReRoutes.Count; i++) diff --git a/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs index 69093cca..e77e3a62 100644 --- a/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs @@ -1,108 +1,108 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class RateLimitOptionsCreatorTests - { - private FileReRoute _fileReRoute; - private FileGlobalConfiguration _fileGlobalConfig; - private bool _enabled; - private RateLimitOptionsCreator _creator; - private RateLimitOptions _result; - - public RateLimitOptionsCreatorTests() - { - _creator = new RateLimitOptionsCreator(); - } - - [Fact] - public void should_create_rate_limit_options() - { - var fileReRoute = new FileReRoute - { - RateLimitOptions = new FileRateLimitRule - { - ClientWhitelist = new List(), - Period = "Period", - Limit = 1, - PeriodTimespan = 1, - EnableRateLimiting = true - } - }; - var fileGlobalConfig = new FileGlobalConfiguration - { - RateLimitOptions = new FileRateLimitOptions - { - ClientIdHeader = "ClientIdHeader", - DisableRateLimitHeaders = true, - QuotaExceededMessage = "QuotaExceededMessage", - RateLimitCounterPrefix = "RateLimitCounterPrefix", - HttpStatusCode = 200 - } - }; - var expected = new RateLimitOptionsBuilder() - .WithClientIdHeader("ClientIdHeader") - .WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) - .WithDisableRateLimitHeaders(true) - .WithEnableRateLimiting(true) - .WithHttpStatusCode(200) - .WithQuotaExceededMessage("QuotaExceededMessage") - .WithRateLimitCounterPrefix("RateLimitCounterPrefix") - .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, - fileReRoute.RateLimitOptions.PeriodTimespan, - fileReRoute.RateLimitOptions.Limit)) - .Build(); - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .And(x => x.GivenTheFollowingFileGlobalConfig(fileGlobalConfig)) - .And(x => x.GivenRateLimitingIsEnabled()) - .When(x => x.WhenICreate()) - .Then(x => x.ThenTheFollowingIsReturned(expected)) - .BDDfy(); - } - - private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute) - { - _fileReRoute = fileReRoute; - } - - private void GivenTheFollowingFileGlobalConfig(FileGlobalConfiguration fileGlobalConfig) - { - _fileGlobalConfig = fileGlobalConfig; - } - - private void GivenRateLimitingIsEnabled() - { - _enabled = true; - } - - private void WhenICreate() - { - _result = _creator.Create(_fileReRoute, _fileGlobalConfig, _enabled); - } - - private void ThenTheFollowingIsReturned(RateLimitOptions expected) - { - _result.ClientIdHeader.ShouldBe(expected.ClientIdHeader); - _result.ClientWhitelist.ShouldBe(expected.ClientWhitelist); - _result.DisableRateLimitHeaders.ShouldBe(expected.DisableRateLimitHeaders); - _result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); - _result.HttpStatusCode.ShouldBe(expected.HttpStatusCode); - _result.QuotaExceededMessage.ShouldBe(expected.QuotaExceededMessage); - _result.RateLimitCounterPrefix.ShouldBe(expected.RateLimitCounterPrefix); - _result.RateLimitRule.Limit.ShouldBe(expected.RateLimitRule.Limit); - _result.RateLimitRule.Period.ShouldBe(expected.RateLimitRule.Period); - TimeSpan.FromSeconds(_result.RateLimitRule.PeriodTimespan).Ticks.ShouldBe(TimeSpan.FromSeconds(expected.RateLimitRule.PeriodTimespan).Ticks); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class RateLimitOptionsCreatorTests + { + private FileReRoute _fileReRoute; + private FileGlobalConfiguration _fileGlobalConfig; + private bool _enabled; + private RateLimitOptionsCreator _creator; + private RateLimitOptions _result; + + public RateLimitOptionsCreatorTests() + { + _creator = new RateLimitOptionsCreator(); + } + + [Fact] + public void should_create_rate_limit_options() + { + var fileReRoute = new FileReRoute + { + RateLimitOptions = new FileRateLimitRule + { + ClientWhitelist = new List(), + Period = "Period", + Limit = 1, + PeriodTimespan = 1, + EnableRateLimiting = true + } + }; + var fileGlobalConfig = new FileGlobalConfiguration + { + RateLimitOptions = new FileRateLimitOptions + { + ClientIdHeader = "ClientIdHeader", + DisableRateLimitHeaders = true, + QuotaExceededMessage = "QuotaExceededMessage", + RateLimitCounterPrefix = "RateLimitCounterPrefix", + HttpStatusCode = 200 + } + }; + var expected = new RateLimitOptionsBuilder() + .WithClientIdHeader("ClientIdHeader") + .WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) + .WithDisableRateLimitHeaders(true) + .WithEnableRateLimiting(true) + .WithHttpStatusCode(200) + .WithQuotaExceededMessage("QuotaExceededMessage") + .WithRateLimitCounterPrefix("RateLimitCounterPrefix") + .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, + fileReRoute.RateLimitOptions.PeriodTimespan, + fileReRoute.RateLimitOptions.Limit)) + .Build(); + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .And(x => x.GivenTheFollowingFileGlobalConfig(fileGlobalConfig)) + .And(x => x.GivenRateLimitingIsEnabled()) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheFollowingIsReturned(expected)) + .BDDfy(); + } + + private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void GivenTheFollowingFileGlobalConfig(FileGlobalConfiguration fileGlobalConfig) + { + _fileGlobalConfig = fileGlobalConfig; + } + + private void GivenRateLimitingIsEnabled() + { + _enabled = true; + } + + private void WhenICreate() + { + _result = _creator.Create(_fileReRoute.RateLimitOptions, _fileGlobalConfig); + } + + private void ThenTheFollowingIsReturned(RateLimitOptions expected) + { + _result.ClientIdHeader.ShouldBe(expected.ClientIdHeader); + _result.ClientWhitelist.ShouldBe(expected.ClientWhitelist); + _result.DisableRateLimitHeaders.ShouldBe(expected.DisableRateLimitHeaders); + _result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); + _result.HttpStatusCode.ShouldBe(expected.HttpStatusCode); + _result.QuotaExceededMessage.ShouldBe(expected.QuotaExceededMessage); + _result.RateLimitCounterPrefix.ShouldBe(expected.RateLimitCounterPrefix); + _result.RateLimitRule.Limit.ShouldBe(expected.RateLimitRule.Limit); + _result.RateLimitRule.Period.ShouldBe(expected.RateLimitRule.Period); + TimeSpan.FromSeconds(_result.RateLimitRule.PeriodTimespan).Ticks.ShouldBe(TimeSpan.FromSeconds(expected.RateLimitRule.PeriodTimespan).Ticks); + } + } +} diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs index b29d2d29..a1c4b799 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs @@ -1,9 +1,3 @@ -using Ocelot.DownstreamRouteFinder.Finder; -using Xunit; -using Shouldly; -using Ocelot.Configuration; -using System.Net.Http; - namespace Ocelot.UnitTests.DownstreamRouteFinder { using System; @@ -14,6 +8,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder using Ocelot.LoadBalancer.LoadBalancers; using Responses; using TestStack.BDDfy; + using Ocelot.DownstreamRouteFinder.Finder; + using Xunit; + using Shouldly; + using Ocelot.Configuration; + using System.Net.Http; + using System.Collections.Generic; public class DownstreamRouteCreatorTests { @@ -53,6 +53,34 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .BDDfy(); } + [Fact] + public void should_create_downstream_route_with_rate_limit_options() + { + var rateLimitOptions = new RateLimitOptionsBuilder() + .WithEnableRateLimiting(true) + .WithClientIdHeader("test") + .Build(); + + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithServiceName("auth") + .WithRateLimitOptions(rateLimitOptions) + .Build(); + + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .Build(); + + var reRoutes = new List { reRoute }; + + var configuration = new InternalConfiguration(reRoutes, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions); + + this.Given(_ => GivenTheConfiguration(configuration)) + .When(_ => WhenICreate()) + .Then(_ => ThenTheDownstreamRouteIsCreated()) + .And(_ => WithRateLimitOptions(rateLimitOptions)) + .BDDfy(); + } + [Fact] public void should_cache_downstream_route() { @@ -174,6 +202,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .Returns(options); } + private void WithRateLimitOptions(RateLimitOptions expected) + { + _result.Data.ReRoute.DownstreamReRoute[0].EnableEndpointEndpointRateLimiting.ShouldBeTrue(); + _result.Data.ReRoute.DownstreamReRoute[0].RateLimitOptions.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); + _result.Data.ReRoute.DownstreamReRoute[0].RateLimitOptions.ClientIdHeader.ShouldBe(expected.ClientIdHeader); + } + private void ThenTheDownstreamRouteIsCreated() { _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test"); diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs index 2b595660..0380f2fc 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs @@ -53,6 +53,21 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .BDDfy(); } + [Fact] + public void should_return_downstream_route_finder_when_not_dynamic_re_route_and_service_discovery_on() + { + var spConfig = new ServiceProviderConfigurationBuilder().WithHost("test").WithPort(50).WithType("test").Build(); + var reRoutes = new List + { + new ReRouteBuilder().WithUpstreamPathTemplate("woot").Build() + }; + + this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheResultShouldBe()) + .BDDfy(); + } + [Fact] public void should_return_downstream_route_finder_as_no_service_discovery_given_no_host() { @@ -101,6 +116,21 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .BDDfy(); } + [Fact] + public void should_return_downstream_route_creator_with_dynamic_re_route() + { + var spConfig = new ServiceProviderConfigurationBuilder().WithHost("test").WithPort(50).WithType("test").Build(); + var reRoutes = new List + { + new ReRouteBuilder().Build() + }; + + this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheResultShouldBe()) + .BDDfy(); + } + private void ThenTheResultShouldBe() { _result.ShouldBeOfType(); From 8711b07de634322c1caa0f0da3dd0d31f6b011f8 Mon Sep 17 00:00:00 2001 From: aqa510415008 <510415008@qq.com> Date: Mon, 30 Jul 2018 14:07:12 +0800 Subject: [PATCH 03/50] Fix extended coverage main pipeline bug (#510) --- .../OcelotPipelineBuilderExtensions.cs | 2 +- .../Pipeline/OcelotPipelineExtensions.cs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs index de2a35eb..bc558533 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs @@ -172,7 +172,7 @@ namespace Ocelot.Middleware.Pipeline throw new ArgumentNullException(nameof(pipelineBuilderFunc)); } var branchBuilder = app.New(); - var predicate = pipelineBuilderFunc.Invoke(app); + var predicate = pipelineBuilderFunc.Invoke(branchBuilder); var branch = branchBuilder.Build(); var options = new MapWhenOptions diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs index 5c10d7c0..1153ccd5 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs @@ -28,15 +28,6 @@ namespace Ocelot.Middleware.Pipeline // It also sets the Request Id if anything is set globally builder.UseExceptionHandlerMiddleware(); - //Expand other branch pipes - if (pipelineConfiguration.MapWhenOcelotPipeline != null) - { - foreach (var pipeline in pipelineConfiguration.MapWhenOcelotPipeline) - { - builder.MapWhen(pipeline); - } - } - // If the request is for websockets upgrade we fork into a different pipeline builder.MapWhen(context => context.HttpContext.WebSockets.IsWebSocketRequest, app => @@ -57,6 +48,15 @@ namespace Ocelot.Middleware.Pipeline // Then we get the downstream route information builder.UseDownstreamRouteFinderMiddleware(); + //Expand other branch pipes + if (pipelineConfiguration.MapWhenOcelotPipeline != null) + { + foreach (var pipeline in pipelineConfiguration.MapWhenOcelotPipeline) + { + builder.MapWhen(pipeline); + } + } + // Now we have the ds route we can transform headers and stuff? builder.UseHttpHeadersTransformationMiddleware(); From a5bb74a2ea69be8f50fbd06b268ec4b6db54daa0 Mon Sep 17 00:00:00 2001 From: Varorbc Date: Mon, 30 Jul 2018 14:49:11 +0800 Subject: [PATCH 04/50] Update tracing.rst (#509) --- docs/features/tracing.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/features/tracing.rst b/docs/features/tracing.rst index aa4b1fd3..ae38c9ea 100644 --- a/docs/features/tracing.rst +++ b/docs/features/tracing.rst @@ -1,7 +1,7 @@ Tracing ======= -Ocelot providers tracing functionality from the excellent `Butterfly `_ project. +Ocelot providers tracing functionality from the excellent `Butterfly `_ project. In order to use the tracing please read the Butterfly documentation. @@ -28,4 +28,4 @@ Then in your ocelot.json add the following to the ReRoute you want to trace.. "UseTracing": true }, -Ocelot will now send tracing information to Butterfly when this ReRoute is called. \ No newline at end of file +Ocelot will now send tracing information to Butterfly when this ReRoute is called. From 049731b43bf439a1331ccf274452676be1adf5e8 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Mon, 30 Jul 2018 22:44:07 +0100 Subject: [PATCH 05/50] removed fake service tracer (#511) * removed fake service tracer * removed comments --- .../Creator/HttpHandlerOptionsCreator.cs | 50 +-- .../DependencyInjection/OcelotBuilder.cs | 4 - .../Logging/OcelotDiagnosticListener.cs | 191 +++++----- src/Ocelot/Requester/FakeServiceTracer.cs | 17 - src/Ocelot/Requester/TracingHandlerFactory.cs | 12 +- .../HttpHandlerOptionsCreatorTests.cs | 345 +++++++++--------- .../Logging/OcelotDiagnosticListenerTests.cs | 9 +- .../Requester/TracingHandlerFactoryTests.cs | 11 +- 8 files changed, 318 insertions(+), 321 deletions(-) delete mode 100644 src/Ocelot/Requester/FakeServiceTracer.cs diff --git a/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs b/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs index fbe659cf..37b0a097 100644 --- a/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs @@ -1,24 +1,26 @@ -using Butterfly.Client.Tracing; -using Ocelot.Configuration.File; -using Ocelot.Requester; - -namespace Ocelot.Configuration.Creator -{ - public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator - { - private readonly IServiceTracer _tracer; - - public HttpHandlerOptionsCreator(IServiceTracer tracer) - { - _tracer = tracer; - } - - public HttpHandlerOptions Create(FileHttpHandlerOptions options) - { - var useTracing = _tracer.GetType() != typeof(FakeServiceTracer) && options.UseTracing; - - return new HttpHandlerOptions(options.AllowAutoRedirect, - options.UseCookieContainer, useTracing, options.UseProxy); - } - } -} +namespace Ocelot.Configuration.Creator +{ + using System; + using Butterfly.Client.Tracing; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.Configuration.File; + using Ocelot.Requester; + + public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator + { + private readonly IServiceTracer _tracer; + + public HttpHandlerOptionsCreator(IServiceProvider services) + { + _tracer = services.GetService(); + } + + public HttpHandlerOptions Create(FileHttpHandlerOptions options) + { + var useTracing = _tracer!= null && options.UseTracing; + + return new HttpHandlerOptions(options.AllowAutoRedirect, + options.UseCookieContainer, useTracing, options.UseProxy); + } + } +} diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 07e62358..c742fd0f 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -158,8 +158,6 @@ namespace Ocelot.DependencyInjection _services.TryAddSingleton(); _services.AddSingleton(); - // We add this here so that we can always inject something into the factory for IoC.. - _services.AddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); @@ -237,8 +235,6 @@ namespace Ocelot.DependencyInjection public IOcelotBuilder AddOpenTracing(Action settings) { - // Earlier we add FakeServiceTracer and need to remove it here before we add butterfly - _services.RemoveAll(); _services.AddButterfly(settings); return this; } diff --git a/src/Ocelot/Logging/OcelotDiagnosticListener.cs b/src/Ocelot/Logging/OcelotDiagnosticListener.cs index f2c51760..15952ce0 100644 --- a/src/Ocelot/Logging/OcelotDiagnosticListener.cs +++ b/src/Ocelot/Logging/OcelotDiagnosticListener.cs @@ -1,96 +1,95 @@ -using System; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DiagnosticAdapter; -using Butterfly.Client.AspNetCore; -using Butterfly.OpenTracing; -using Ocelot.Middleware; -using Butterfly.Client.Tracing; -using System.Linq; -using System.Collections.Generic; -using Ocelot.Infrastructure.Extensions; -using Ocelot.Requester; - -namespace Ocelot.Logging -{ - public class OcelotDiagnosticListener - { - private readonly IServiceTracer _tracer; - private readonly IOcelotLogger _logger; - - public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceTracer tracer) - { - _tracer = tracer; - _logger = factory.CreateLogger(); - } - - [DiagnosticName("Ocelot.MiddlewareException")] - public virtual void OcelotMiddlewareException(Exception exception, DownstreamContext context, string name) - { - _logger.LogTrace($"Ocelot.MiddlewareException: {name}; {exception.Message};"); - Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}"); - } - - [DiagnosticName("Ocelot.MiddlewareStarted")] - public virtual void OcelotMiddlewareStarted(DownstreamContext context, string name) - { - _logger.LogTrace($"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}"); - Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}"); - } - - [DiagnosticName("Ocelot.MiddlewareFinished")] - public virtual void OcelotMiddlewareFinished(DownstreamContext context, string name) - { - _logger.LogTrace($"Ocelot.MiddlewareFinished: {name}; {context.HttpContext.Request.Path}"); - Event(context.HttpContext, $"OcelotMiddlewareFinished: {name}; {context.HttpContext.Request.Path}"); - } - - [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareStarting")] - public virtual void OnMiddlewareStarting(HttpContext httpContext, string name) - { - _logger.LogTrace($"MiddlewareStarting: {name}; {httpContext.Request.Path}"); - Event(httpContext, $"MiddlewareStarting: {name}; {httpContext.Request.Path}"); - } - - [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException")] - public virtual void OnMiddlewareException(Exception exception, string name) - { - _logger.LogTrace($"MiddlewareException: {name}; {exception.Message};"); - } - - [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareFinished")] - public virtual void OnMiddlewareFinished(HttpContext httpContext, string name) - { - _logger.LogTrace($"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}"); - Event(httpContext, $"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}"); - } - - private void Event(HttpContext httpContext, string @event) - { - // todo - if the user isnt using tracing the code gets here and will blow up on - // _tracer.Tracer.TryExtract. We already use the fake tracer for another scenario - // so sticking it here as well..I guess we need a factory for this but cba to do it at - // the moment - if(_tracer.GetType() == typeof(FakeServiceTracer)) - { - return; - } - - var span = httpContext.GetSpan(); - - if(span == null) - { - var spanBuilder = new SpanBuilder($"server {httpContext.Request.Method} {httpContext.Request.Path}"); - if (_tracer.Tracer.TryExtract(out var spanContext, httpContext.Request.Headers, (c, k) => c[k].GetValue(), - c => c.Select(x => new KeyValuePair(x.Key, x.Value.GetValue())).GetEnumerator())) - { - spanBuilder.AsChildOf(spanContext); - } - - span = _tracer.Start(spanBuilder); - httpContext.SetSpan(span); - } - - span?.Log(LogField.CreateNew().Event(@event)); - } - } -} +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DiagnosticAdapter; +using Microsoft.Extensions.DependencyInjection; +using Butterfly.Client.AspNetCore; +using Butterfly.OpenTracing; +using Ocelot.Middleware; +using Butterfly.Client.Tracing; +using System.Linq; +using System.Collections.Generic; +using Ocelot.Infrastructure.Extensions; +using Ocelot.Requester; + +namespace Ocelot.Logging +{ + public class OcelotDiagnosticListener + { + private readonly IServiceTracer _tracer; + private readonly IOcelotLogger _logger; + + public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceProvider services) + { + _tracer = services.GetService(); + _logger = factory.CreateLogger(); + } + + [DiagnosticName("Ocelot.MiddlewareException")] + public virtual void OcelotMiddlewareException(Exception exception, DownstreamContext context, string name) + { + _logger.LogTrace($"Ocelot.MiddlewareException: {name}; {exception.Message};"); + Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}"); + } + + [DiagnosticName("Ocelot.MiddlewareStarted")] + public virtual void OcelotMiddlewareStarted(DownstreamContext context, string name) + { + _logger.LogTrace($"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}"); + Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}"); + } + + [DiagnosticName("Ocelot.MiddlewareFinished")] + public virtual void OcelotMiddlewareFinished(DownstreamContext context, string name) + { + _logger.LogTrace($"Ocelot.MiddlewareFinished: {name}; {context.HttpContext.Request.Path}"); + Event(context.HttpContext, $"OcelotMiddlewareFinished: {name}; {context.HttpContext.Request.Path}"); + } + + [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareStarting")] + public virtual void OnMiddlewareStarting(HttpContext httpContext, string name) + { + _logger.LogTrace($"MiddlewareStarting: {name}; {httpContext.Request.Path}"); + Event(httpContext, $"MiddlewareStarting: {name}; {httpContext.Request.Path}"); + } + + [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException")] + public virtual void OnMiddlewareException(Exception exception, string name) + { + _logger.LogTrace($"MiddlewareException: {name}; {exception.Message};"); + } + + [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareFinished")] + public virtual void OnMiddlewareFinished(HttpContext httpContext, string name) + { + _logger.LogTrace($"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}"); + Event(httpContext, $"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}"); + } + + private void Event(HttpContext httpContext, string @event) + { + // todo - if the user isnt using tracing the code gets here and will blow up on + // _tracer.Tracer.TryExtract.. + if(_tracer == null) + { + return; + } + + var span = httpContext.GetSpan(); + + if(span == null) + { + var spanBuilder = new SpanBuilder($"server {httpContext.Request.Method} {httpContext.Request.Path}"); + if (_tracer.Tracer.TryExtract(out var spanContext, httpContext.Request.Headers, (c, k) => c[k].GetValue(), + c => c.Select(x => new KeyValuePair(x.Key, x.Value.GetValue())).GetEnumerator())) + { + spanBuilder.AsChildOf(spanContext); + } + + span = _tracer.Start(spanBuilder); + httpContext.SetSpan(span); + } + + span?.Log(LogField.CreateNew().Event(@event)); + } + } +} diff --git a/src/Ocelot/Requester/FakeServiceTracer.cs b/src/Ocelot/Requester/FakeServiceTracer.cs deleted file mode 100644 index 95347ef6..00000000 --- a/src/Ocelot/Requester/FakeServiceTracer.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Butterfly.Client.Tracing; -using Butterfly.OpenTracing; - -namespace Ocelot.Requester -{ - public class FakeServiceTracer : IServiceTracer - { - public ITracer Tracer { get; } - public string ServiceName { get; } - public string Environment { get; } - public string Identity { get; } - public ISpan Start(ISpanBuilder spanBuilder) - { - return null; - } - } -} diff --git a/src/Ocelot/Requester/TracingHandlerFactory.cs b/src/Ocelot/Requester/TracingHandlerFactory.cs index f0eb97b1..943de460 100644 --- a/src/Ocelot/Requester/TracingHandlerFactory.cs +++ b/src/Ocelot/Requester/TracingHandlerFactory.cs @@ -1,19 +1,21 @@ -using Butterfly.Client.Tracing; -using Ocelot.Infrastructure.RequestData; - namespace Ocelot.Requester { + using System; + using Butterfly.Client.Tracing; + using Ocelot.Infrastructure.RequestData; + using Microsoft.Extensions.DependencyInjection; + public class TracingHandlerFactory : ITracingHandlerFactory { private readonly IServiceTracer _tracer; private readonly IRequestScopedDataRepository _repo; public TracingHandlerFactory( - IServiceTracer tracer, + IServiceProvider services, IRequestScopedDataRepository repo) { _repo = repo; - _tracer = tracer; + _tracer = services.GetService(); } public ITracingHandler Get() diff --git a/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs index 94b5413b..aea0e659 100644 --- a/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs @@ -1,175 +1,180 @@ -using System; -using Butterfly.Client.Tracing; -using Butterfly.OpenTracing; -using Ocelot.Configuration; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Ocelot.Requester; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class HttpHandlerOptionsCreatorTests - { - private IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; - private FileReRoute _fileReRoute; - private HttpHandlerOptions _httpHandlerOptions; - private IServiceTracer _serviceTracer; - - public HttpHandlerOptionsCreatorTests() - { - _serviceTracer = new FakeServiceTracer(); - _httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(_serviceTracer); - } - - [Fact] - public void should_not_use_tracing_if_fake_tracer_registered() - { - var fileReRoute = new FileReRoute - { - HttpHandlerOptions = new FileHttpHandlerOptions - { - UseTracing = true - } - }; - - var expectedOptions = new HttpHandlerOptions(false, false, false, true); - - this.Given(x => GivenTheFollowing(fileReRoute)) - .When(x => WhenICreateHttpHandlerOptions()) - .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) - .BDDfy(); - } - - [Fact] - public void should_use_tracing_if_real_tracer_registered() - { - var fileReRoute = new FileReRoute - { - HttpHandlerOptions = new FileHttpHandlerOptions - { - UseTracing = true - } - }; - - var expectedOptions = new HttpHandlerOptions(false, false, true, true); - - this.Given(x => GivenTheFollowing(fileReRoute)) - .And(x => GivenARealTracer()) - .When(x => WhenICreateHttpHandlerOptions()) - .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) - .BDDfy(); - } - - [Fact] - public void should_create_options_with_useCookie_false_and_allowAutoRedirect_true_as_default() - { - var fileReRoute = new FileReRoute(); - var expectedOptions = new HttpHandlerOptions(false, false, false, true); - - this.Given(x => GivenTheFollowing(fileReRoute)) - .When(x => WhenICreateHttpHandlerOptions()) - .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) - .BDDfy(); - } - - [Fact] - public void should_create_options_with_specified_useCookie_and_allowAutoRedirect() - { - var fileReRoute = new FileReRoute - { - HttpHandlerOptions = new FileHttpHandlerOptions - { - AllowAutoRedirect = false, - UseCookieContainer = false, - UseTracing = false - } - }; - - var expectedOptions = new HttpHandlerOptions(false, false, false, true); - - this.Given(x => GivenTheFollowing(fileReRoute)) - .When(x => WhenICreateHttpHandlerOptions()) - .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) - .BDDfy(); +using System; +using Butterfly.Client.Tracing; +using Butterfly.OpenTracing; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Configuration; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Requester; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class HttpHandlerOptionsCreatorTests + { + private IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; + private FileReRoute _fileReRoute; + private HttpHandlerOptions _httpHandlerOptions; + private IServiceProvider _serviceProvider; + private IServiceCollection _serviceCollection; + + public HttpHandlerOptionsCreatorTests() + { + _serviceCollection = new ServiceCollection(); + _serviceProvider = _serviceCollection.BuildServiceProvider(); + _httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(_serviceProvider); } - [Fact] - public void should_create_options_with_useproxy_true_as_default() - { - var fileReRoute = new FileReRoute - { - HttpHandlerOptions = new FileHttpHandlerOptions() - }; - - var expectedOptions = new HttpHandlerOptions(false, false, false, true); - - this.Given(x => GivenTheFollowing(fileReRoute)) - .When(x => WhenICreateHttpHandlerOptions()) - .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) - .BDDfy(); + [Fact] + public void should_not_use_tracing_if_fake_tracer_registered() + { + var fileReRoute = new FileReRoute + { + HttpHandlerOptions = new FileHttpHandlerOptions + { + UseTracing = true + } + }; + + var expectedOptions = new HttpHandlerOptions(false, false, false, true); + + this.Given(x => GivenTheFollowing(fileReRoute)) + .When(x => WhenICreateHttpHandlerOptions()) + .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) + .BDDfy(); } - [Fact] - public void should_create_options_with_specified_useproxy() - { - var fileReRoute = new FileReRoute - { - HttpHandlerOptions = new FileHttpHandlerOptions - { - UseProxy = false - } - }; - - var expectedOptions = new HttpHandlerOptions(false, false, false, false); - - this.Given(x => GivenTheFollowing(fileReRoute)) - .When(x => WhenICreateHttpHandlerOptions()) - .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) - .BDDfy(); - } - - private void GivenTheFollowing(FileReRoute fileReRoute) - { - _fileReRoute = fileReRoute; - } - - private void WhenICreateHttpHandlerOptions() - { - _httpHandlerOptions = _httpHandlerOptionsCreator.Create(_fileReRoute.HttpHandlerOptions); - } - - private void ThenTheFollowingOptionsReturned(HttpHandlerOptions expected) - { - _httpHandlerOptions.ShouldNotBeNull(); - _httpHandlerOptions.AllowAutoRedirect.ShouldBe(expected.AllowAutoRedirect); - _httpHandlerOptions.UseCookieContainer.ShouldBe(expected.UseCookieContainer); + [Fact] + public void should_use_tracing_if_real_tracer_registered() + { + var fileReRoute = new FileReRoute + { + HttpHandlerOptions = new FileHttpHandlerOptions + { + UseTracing = true + } + }; + + var expectedOptions = new HttpHandlerOptions(false, false, true, true); + + this.Given(x => GivenTheFollowing(fileReRoute)) + .And(x => GivenARealTracer()) + .When(x => WhenICreateHttpHandlerOptions()) + .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) + .BDDfy(); + } + + [Fact] + public void should_create_options_with_useCookie_false_and_allowAutoRedirect_true_as_default() + { + var fileReRoute = new FileReRoute(); + var expectedOptions = new HttpHandlerOptions(false, false, false, true); + + this.Given(x => GivenTheFollowing(fileReRoute)) + .When(x => WhenICreateHttpHandlerOptions()) + .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) + .BDDfy(); + } + + [Fact] + public void should_create_options_with_specified_useCookie_and_allowAutoRedirect() + { + var fileReRoute = new FileReRoute + { + HttpHandlerOptions = new FileHttpHandlerOptions + { + AllowAutoRedirect = false, + UseCookieContainer = false, + UseTracing = false + } + }; + + var expectedOptions = new HttpHandlerOptions(false, false, false, true); + + this.Given(x => GivenTheFollowing(fileReRoute)) + .When(x => WhenICreateHttpHandlerOptions()) + .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) + .BDDfy(); + } + + [Fact] + public void should_create_options_with_useproxy_true_as_default() + { + var fileReRoute = new FileReRoute + { + HttpHandlerOptions = new FileHttpHandlerOptions() + }; + + var expectedOptions = new HttpHandlerOptions(false, false, false, true); + + this.Given(x => GivenTheFollowing(fileReRoute)) + .When(x => WhenICreateHttpHandlerOptions()) + .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) + .BDDfy(); + } + + [Fact] + public void should_create_options_with_specified_useproxy() + { + var fileReRoute = new FileReRoute + { + HttpHandlerOptions = new FileHttpHandlerOptions + { + UseProxy = false + } + }; + + var expectedOptions = new HttpHandlerOptions(false, false, false, false); + + this.Given(x => GivenTheFollowing(fileReRoute)) + .When(x => WhenICreateHttpHandlerOptions()) + .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) + .BDDfy(); + } + + private void GivenTheFollowing(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void WhenICreateHttpHandlerOptions() + { + _httpHandlerOptions = _httpHandlerOptionsCreator.Create(_fileReRoute.HttpHandlerOptions); + } + + private void ThenTheFollowingOptionsReturned(HttpHandlerOptions expected) + { + _httpHandlerOptions.ShouldNotBeNull(); + _httpHandlerOptions.AllowAutoRedirect.ShouldBe(expected.AllowAutoRedirect); + _httpHandlerOptions.UseCookieContainer.ShouldBe(expected.UseCookieContainer); _httpHandlerOptions.UseTracing.ShouldBe(expected.UseTracing); - _httpHandlerOptions.UseProxy.ShouldBe(expected.UseProxy); - } - - private void GivenARealTracer() - { - var tracer = new RealTracer(); - _httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(tracer); - } - - class RealTracer : IServiceTracer - { - public ITracer Tracer => throw new NotImplementedException(); - - public string ServiceName => throw new NotImplementedException(); - - public string Environment => throw new NotImplementedException(); - - public string Identity => throw new NotImplementedException(); - - public ISpan Start(ISpanBuilder spanBuilder) - { - throw new NotImplementedException(); - } - } - } -} + _httpHandlerOptions.UseProxy.ShouldBe(expected.UseProxy); + } + + private void GivenARealTracer() + { + var tracer = new RealTracer(); + _serviceCollection.AddSingleton(); + _serviceProvider = _serviceCollection.BuildServiceProvider(); + _httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(_serviceProvider); + } + + class RealTracer : IServiceTracer + { + public ITracer Tracer => throw new NotImplementedException(); + + public string ServiceName => throw new NotImplementedException(); + + public string Environment => throw new NotImplementedException(); + + public string Identity => throw new NotImplementedException(); + + public ISpan Start(ISpanBuilder spanBuilder) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/test/Ocelot.UnitTests/Logging/OcelotDiagnosticListenerTests.cs b/test/Ocelot.UnitTests/Logging/OcelotDiagnosticListenerTests.cs index 3703ddb0..1a463cfa 100644 --- a/test/Ocelot.UnitTests/Logging/OcelotDiagnosticListenerTests.cs +++ b/test/Ocelot.UnitTests/Logging/OcelotDiagnosticListenerTests.cs @@ -7,6 +7,7 @@ using Xunit; using Ocelot.Middleware; using Microsoft.AspNetCore.Http; using System; +using Microsoft.Extensions.DependencyInjection; namespace Ocelot.UnitTests.Logging { @@ -15,7 +16,8 @@ namespace Ocelot.UnitTests.Logging private readonly OcelotDiagnosticListener _listener; private Mock _factory; private readonly Mock _logger; - private IServiceTracer _tracer; + private IServiceCollection _serviceCollection; + private IServiceProvider _serviceProvider; private DownstreamContext _downstreamContext; private string _name; private Exception _exception; @@ -24,9 +26,10 @@ namespace Ocelot.UnitTests.Logging { _factory = new Mock(); _logger = new Mock(); - _tracer = new FakeServiceTracer(); + _serviceCollection = new ServiceCollection(); + _serviceProvider = _serviceCollection.BuildServiceProvider(); _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _listener = new OcelotDiagnosticListener(_factory.Object, _tracer); + _listener = new OcelotDiagnosticListener(_factory.Object, _serviceProvider); } [Fact] diff --git a/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs b/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs index bd633cb3..9128c710 100644 --- a/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs +++ b/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs @@ -1,4 +1,6 @@ +using System; using Butterfly.Client.Tracing; +using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Infrastructure.RequestData; using Ocelot.Requester; @@ -11,13 +13,18 @@ namespace Ocelot.UnitTests.Requester { private TracingHandlerFactory _factory; private Mock _tracer; + private IServiceCollection _serviceCollection; + private IServiceProvider _serviceProvider; private Mock _repo; public TracingHandlerFactoryTests() { _tracer = new Mock(); + _serviceCollection = new ServiceCollection(); + _serviceCollection.AddSingleton(_tracer.Object); + _serviceProvider = _serviceCollection.BuildServiceProvider(); _repo = new Mock(); - _factory = new TracingHandlerFactory(_tracer.Object, _repo.Object); + _factory = new TracingHandlerFactory(_serviceProvider, _repo.Object); } [Fact] @@ -27,4 +34,4 @@ namespace Ocelot.UnitTests.Requester handler.ShouldBeOfType(); } } -} \ No newline at end of file +} From 29ff0045fe515ea3604e14bc0d6a4eceaa428f8b Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Mon, 30 Jul 2018 23:15:03 +0100 Subject: [PATCH 06/50] removed null admin path hack (#513) --- .../Creator/FileInternalConfigurationCreator.cs | 9 ++++++--- .../DependencyInjection/NullAdministrationPath.cs | 12 ------------ src/Ocelot/DependencyInjection/OcelotBuilder.cs | 9 ++------- src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs | 2 +- .../FileInternalConfigurationCreatorTests.cs | 6 +++--- 5 files changed, 12 insertions(+), 26 deletions(-) delete mode 100644 src/Ocelot/DependencyInjection/NullAdministrationPath.cs diff --git a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs index d393b648..89bd7a41 100644 --- a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs @@ -10,6 +10,7 @@ using Ocelot.Configuration.Validator; using Ocelot.DependencyInjection; using Ocelot.Logging; using Ocelot.Responses; +using Microsoft.Extensions.DependencyInjection; namespace Ocelot.Configuration.Creator { @@ -49,14 +50,14 @@ namespace Ocelot.Configuration.Creator IRateLimitOptionsCreator rateLimitOptionsCreator, IRegionCreator regionCreator, IHttpHandlerOptionsCreator httpHandlerOptionsCreator, - IAdministrationPath adminPath, + IServiceProvider serviceProvider, IHeaderFindAndReplaceCreator headerFAndRCreator, IDownstreamAddressesCreator downstreamAddressesCreator ) { _downstreamAddressesCreator = downstreamAddressesCreator; _headerFAndRCreator = headerFAndRCreator; - _adminPath = adminPath; + _adminPath = serviceProvider.GetService(); _regionCreator = regionCreator; _rateLimitOptionsCreator = rateLimitOptionsCreator; _requestIdKeyCreator = requestIdKeyCreator; @@ -117,8 +118,10 @@ namespace Ocelot.Configuration.Creator var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileConfiguration.GlobalConfiguration.HttpHandlerOptions); + var adminPath = _adminPath != null ? _adminPath.Path : null; + var config = new InternalConfiguration(reRoutes, - _adminPath.Path, + adminPath, serviceProviderConfiguration, fileConfiguration.GlobalConfiguration.RequestIdKey, lbOptions, diff --git a/src/Ocelot/DependencyInjection/NullAdministrationPath.cs b/src/Ocelot/DependencyInjection/NullAdministrationPath.cs deleted file mode 100644 index 7ef626b7..00000000 --- a/src/Ocelot/DependencyInjection/NullAdministrationPath.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ocelot.DependencyInjection -{ - public class NullAdministrationPath : IAdministrationPath - { - public NullAdministrationPath() - { - Path = null; - } - - public string Path {get;private set;} - } -} diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index c742fd0f..f120742d 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -152,12 +152,10 @@ namespace Ocelot.DependencyInjection _services.AddLogging(); _services.AddMiddlewareAnalysis(); _services.AddWebEncoders(); - _services.AddSingleton(new NullAdministrationPath()); _services.TryAddSingleton(); _services.TryAddSingleton(); _services.AddSingleton(); - _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); @@ -180,8 +178,7 @@ namespace Ocelot.DependencyInjection AddIdentityServer(identityServerConfiguration, administrationPath); } - var descriptor = new ServiceDescriptor(typeof(IAdministrationPath), administrationPath); - _services.Replace(descriptor); + _services.AddSingleton(administrationPath); return new OcelotAdministrationBuilder(_services, _configurationRoot); } @@ -194,9 +191,7 @@ namespace Ocelot.DependencyInjection AddIdentityServer(configureOptions); } - //todo - hack because we add this earlier so it always exists for some reason...investigate.. - var descriptor = new ServiceDescriptor(typeof(IAdministrationPath), administrationPath); - _services.Replace(descriptor); + _services.AddSingleton(administrationPath); return new OcelotAdministrationBuilder(_services, _configurationRoot); } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index d5421b12..00a2a2ae 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -139,7 +139,7 @@ private static bool AdministrationApiInUse(IAdministrationPath adminPath) { - return adminPath.GetType() != typeof(NullAdministrationPath); + return adminPath != null; } private static async Task SetFileConfigInConsul(IApplicationBuilder builder, diff --git a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs index b9d4a422..f545e04c 100644 --- a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs @@ -36,7 +36,7 @@ private readonly Mock _rateLimitOptions; private readonly Mock _regionCreator; private readonly Mock _httpHandlerOptionsCreator; - private readonly Mock _adminPath; + private readonly Mock _serviceProvider; private readonly Mock _headerFindAndReplaceCreator; private readonly Mock _downstreamAddressesCreator; @@ -54,7 +54,7 @@ _rateLimitOptions = new Mock(); _regionCreator = new Mock(); _httpHandlerOptionsCreator = new Mock(); - _adminPath = new Mock(); + _serviceProvider = new Mock(); _headerFindAndReplaceCreator = new Mock(); _downstreamAddressesCreator = new Mock(); @@ -71,7 +71,7 @@ _rateLimitOptions.Object, _regionCreator.Object, _httpHandlerOptionsCreator.Object, - _adminPath.Object, + _serviceProvider.Object, _headerFindAndReplaceCreator.Object, _downstreamAddressesCreator.Object); } From eb4b996c99359fa06022abf55c0fecbaa011fe44 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Tue, 31 Jul 2018 18:37:59 +0100 Subject: [PATCH 07/50] removed loads of stupid code (#518) --- .../Middleware/OcelotMiddlewareExtensions.cs | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 00a2a2ae..7b723917 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -20,13 +20,13 @@ using Ocelot.Middleware.Pipeline; using Pivotal.Discovery.Client; using Rafty.Concensus.Node; + using Microsoft.Extensions.DependencyInjection; public static class OcelotMiddlewareExtensions { public static async Task UseOcelot(this IApplicationBuilder builder) { await builder.UseOcelot(new OcelotPipelineConfiguration()); - return builder; } @@ -36,6 +36,7 @@ pipelineConfiguration?.Invoke(config); return await builder.UseOcelot(config); } + public static async Task UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) { var configuration = await CreateConfiguration(builder); @@ -54,6 +55,12 @@ ConfigureDiagnosticListener(builder); + return CreateOcelotPipeline(builder, pipelineConfiguration); + + } + + private static IApplicationBuilder CreateOcelotPipeline(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) + { var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices); pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration); @@ -84,8 +91,8 @@ private static bool UsingRafty(IApplicationBuilder builder) { - var possible = builder.ApplicationServices.GetService(typeof(INode)) as INode; - if (possible != null) + var node = builder.ApplicationServices.GetService(); + if (node != null) { return true; } @@ -95,10 +102,10 @@ private static void SetUpRafty(IApplicationBuilder builder) { - var applicationLifetime = (IApplicationLifetime)builder.ApplicationServices.GetService(typeof(IApplicationLifetime)); + var applicationLifetime = builder.ApplicationServices.GetService(); applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder)); - var node = (INode)builder.ApplicationServices.GetService(typeof(INode)); - var nodeId = (NodeId)builder.ApplicationServices.GetService(typeof(NodeId)); + var node = builder.ApplicationServices.GetService(); + var nodeId = builder.ApplicationServices.GetService(); node.Start(nodeId); } @@ -106,19 +113,19 @@ { // make configuration from file system? // earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this - var fileConfig = (IOptions)builder.ApplicationServices.GetService(typeof(IOptions)); + var fileConfig = builder.ApplicationServices.GetService>(); // now create the config - var internalConfigCreator = (IInternalConfigurationCreator)builder.ApplicationServices.GetService(typeof(IInternalConfigurationCreator)); + var internalConfigCreator = builder.ApplicationServices.GetService(); var internalConfig = await internalConfigCreator.Create(fileConfig.Value); // now save it in memory - var internalConfigRepo = (IInternalConfigurationRepository)builder.ApplicationServices.GetService(typeof(IInternalConfigurationRepository)); + var internalConfigRepo = builder.ApplicationServices.GetService(); internalConfigRepo.AddOrReplace(internalConfig.Data); - var fileConfigRepo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository)); + var fileConfigRepo = builder.ApplicationServices.GetService(); - var adminPath = (IAdministrationPath)builder.ApplicationServices.GetService(typeof(IAdministrationPath)); + var adminPath = builder.ApplicationServices.GetService(); if (UsingConsul(fileConfigRepo)) { @@ -129,7 +136,7 @@ { //We have to make sure the file config is set for the ocelot.env.json and ocelot.json so that if we pull it from the //admin api it works...boy this is getting a spit spags boll. - var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); + var fileConfigSetter = builder.ApplicationServices.GetService(); await SetFileConfig(fileConfigSetter, fileConfig); } @@ -234,7 +241,7 @@ builder.Map(configuration.AdministrationPath, app => { //todo - hack so we know that we are using internal identity server - var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration)); + var identityServerConfiguration = builder.ApplicationServices.GetService(); if (identityServerConfiguration != null) { app.UseIdentityServer(); @@ -248,15 +255,15 @@ private static void ConfigureDiagnosticListener(IApplicationBuilder builder) { - var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment)); - var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener)); - var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener)); + var env = builder.ApplicationServices.GetService(); + var listener = builder.ApplicationServices.GetService(); + var diagnosticListener = builder.ApplicationServices.GetService(); diagnosticListener.SubscribeWithAdapter(listener); } private static void OnShutdown(IApplicationBuilder app) { - var node = (INode)app.ApplicationServices.GetService(typeof(INode)); + var node = app.ApplicationServices.GetService(); node.Stop(); } } From b854ca63ce2a9124cf514d640e47f04b3ab8afb7 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Tue, 31 Jul 2018 19:21:12 +0100 Subject: [PATCH 08/50] use a stream rather than byte array in responder (#519) --- .../Middleware/ExceptionHandlerMiddleware.cs | 19 +- src/Ocelot/Responder/HttpContextResponder.cs | 146 +++---- .../Middleware/ResponderMiddleware.cs | 110 ++--- .../Errors/ExceptionHandlerMiddlewareTests.cs | 394 +++++++++--------- 4 files changed, 334 insertions(+), 335 deletions(-) diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs index feb64af0..58c18901 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs @@ -1,16 +1,15 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Ocelot.Configuration.Repository; -using Ocelot.Infrastructure.Extensions; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; - namespace Ocelot.Errors.Middleware { using Configuration; - + using System; + using System.Linq; + using System.Threading.Tasks; + using Ocelot.Configuration.Repository; + using Ocelot.Infrastructure.Extensions; + using Ocelot.Infrastructure.RequestData; + using Ocelot.Logging; + using Ocelot.Middleware; + /// /// Catches all unhandled exceptions thrown by middleware, logs and returns a 500 /// diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index 0b2959b4..6511590c 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -1,74 +1,74 @@ -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; -using Ocelot.Headers; -using Ocelot.Middleware; - -namespace Ocelot.Responder -{ - /// - /// Cannot unit test things in this class due to methods not being implemented - /// on .net concretes used for testing - /// - public class HttpContextResponder : IHttpResponder - { - private readonly IRemoveOutputHeaders _removeOutputHeaders; - - public HttpContextResponder(IRemoveOutputHeaders removeOutputHeaders) - { - _removeOutputHeaders = removeOutputHeaders; - } - - public async Task SetResponseOnHttpContext(HttpContext context, DownstreamResponse response) - { - _removeOutputHeaders.Remove(response.Headers); - - foreach (var httpResponseHeader in response.Headers) - { - AddHeaderIfDoesntExist(context, httpResponseHeader); - } - - foreach (var httpResponseHeader in response.Content.Headers) - { - AddHeaderIfDoesntExist(context, new Header(httpResponseHeader.Key, httpResponseHeader.Value)); - } - - var content = await response.Content.ReadAsByteArrayAsync(); - - AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ content.Length.ToString() }) ); - - context.Response.OnStarting(state => - { - var httpContext = (HttpContext)state; - - httpContext.Response.StatusCode = (int)response.StatusCode; - - return Task.CompletedTask; - }, context); - - using (Stream stream = new MemoryStream(content)) - { - if (response.StatusCode != HttpStatusCode.NotModified && context.Response.ContentLength != 0) - { - await stream.CopyToAsync(context.Response.Body); - } - } - } - - public void SetErrorResponseOnContext(HttpContext context, int statusCode) +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using Ocelot.Headers; +using Ocelot.Middleware; + +namespace Ocelot.Responder +{ + /// + /// Cannot unit test things in this class due to methods not being implemented + /// on .net concretes used for testing + /// + public class HttpContextResponder : IHttpResponder + { + private readonly IRemoveOutputHeaders _removeOutputHeaders; + + public HttpContextResponder(IRemoveOutputHeaders removeOutputHeaders) { - context.Response.StatusCode = statusCode; - } - - private static void AddHeaderIfDoesntExist(HttpContext context, Header httpResponseHeader) - { - if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key)) - { - context.Response.Headers.Add(httpResponseHeader.Key, new StringValues(httpResponseHeader.Values.ToArray())); - } - } - } -} + _removeOutputHeaders = removeOutputHeaders; + } + + public async Task SetResponseOnHttpContext(HttpContext context, DownstreamResponse response) + { + _removeOutputHeaders.Remove(response.Headers); + + foreach (var httpResponseHeader in response.Headers) + { + AddHeaderIfDoesntExist(context, httpResponseHeader); + } + + foreach (var httpResponseHeader in response.Content.Headers) + { + AddHeaderIfDoesntExist(context, new Header(httpResponseHeader.Key, httpResponseHeader.Value)); + } + + var content = await response.Content.ReadAsStreamAsync(); + + AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ content.Length.ToString() }) ); + + context.Response.OnStarting(state => + { + var httpContext = (HttpContext)state; + + httpContext.Response.StatusCode = (int)response.StatusCode; + + return Task.CompletedTask; + }, context); + + using(content) + { + if (response.StatusCode != HttpStatusCode.NotModified && context.Response.ContentLength != 0) + { + await content.CopyToAsync(context.Response.Body); + } + } + } + + public void SetErrorResponseOnContext(HttpContext context, int statusCode) + { + context.Response.StatusCode = statusCode; + } + + private static void AddHeaderIfDoesntExist(HttpContext context, Header httpResponseHeader) + { + if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key)) + { + context.Response.Headers.Add(httpResponseHeader.Key, new StringValues(httpResponseHeader.Values.ToArray())); + } + } + } +} diff --git a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs index 0487d811..ec6696c5 100644 --- a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs +++ b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs @@ -1,55 +1,55 @@ -using Microsoft.AspNetCore.Http; -using Ocelot.Errors; -using Ocelot.Logging; -using Ocelot.Middleware; -using System.Collections.Generic; -using System.Threading.Tasks; -using Ocelot.Infrastructure.Extensions; - -namespace Ocelot.Responder.Middleware -{ - /// - /// Completes and returns the request and request body, if any pipeline errors occured then sets the appropriate HTTP status code instead. - /// - public class ResponderMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IHttpResponder _responder; - private readonly IErrorsToHttpStatusCodeMapper _codeMapper; - - public ResponderMiddleware(OcelotRequestDelegate next, - IHttpResponder responder, - IOcelotLoggerFactory loggerFactory, - IErrorsToHttpStatusCodeMapper codeMapper - ) - :base(loggerFactory.CreateLogger()) - { - _next = next; - _responder = responder; - _codeMapper = codeMapper; - } - - public async Task Invoke(DownstreamContext context) - { - await _next.Invoke(context); - - if (context.IsError) - { - Logger.LogWarning($"{context.Errors.ToErrorString()} errors found in {MiddlewareName}. Setting error response for request path:{context.HttpContext.Request.Path}, request method: {context.HttpContext.Request.Method}"); - - SetErrorResponse(context.HttpContext, context.Errors); - } - else - { - Logger.LogDebug("no pipeline errors, setting and returning completed response"); - await _responder.SetResponseOnHttpContext(context.HttpContext, context.DownstreamResponse); - } - } - - private void SetErrorResponse(HttpContext context, List errors) - { - var statusCode = _codeMapper.Map(errors); - _responder.SetErrorResponseOnContext(context, statusCode); - } - } -} +using Microsoft.AspNetCore.Http; +using Ocelot.Errors; +using Ocelot.Logging; +using Ocelot.Middleware; +using System.Collections.Generic; +using System.Threading.Tasks; +using Ocelot.Infrastructure.Extensions; + +namespace Ocelot.Responder.Middleware +{ + /// + /// Completes and returns the request and request body, if any pipeline errors occured then sets the appropriate HTTP status code instead. + /// + public class ResponderMiddleware : OcelotMiddleware + { + private readonly OcelotRequestDelegate _next; + private readonly IHttpResponder _responder; + private readonly IErrorsToHttpStatusCodeMapper _codeMapper; + + public ResponderMiddleware(OcelotRequestDelegate next, + IHttpResponder responder, + IOcelotLoggerFactory loggerFactory, + IErrorsToHttpStatusCodeMapper codeMapper + ) + :base(loggerFactory.CreateLogger()) + { + _next = next; + _responder = responder; + _codeMapper = codeMapper; + } + + public async Task Invoke(DownstreamContext context) + { + await _next.Invoke(context); + + if (context.IsError) + { + Logger.LogWarning($"{context.Errors.ToErrorString()} errors found in {MiddlewareName}. Setting error response for request path:{context.HttpContext.Request.Path}, request method: {context.HttpContext.Request.Method}"); + + SetErrorResponse(context.HttpContext, context.Errors); + } + else + { + Logger.LogDebug("no pipeline errors, setting and returning completed response"); + await _responder.SetResponseOnHttpContext(context.HttpContext, context.DownstreamResponse); + } + } + + private void SetErrorResponse(HttpContext context, List errors) + { + var statusCode = _codeMapper.Map(errors); + _responder.SetErrorResponseOnContext(context, statusCode); + } + } +} diff --git a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs index ce88b468..0ca0b659 100644 --- a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs @@ -1,197 +1,197 @@ -namespace Ocelot.UnitTests.Errors -{ - using System; - using System.Net; - using System.Threading.Tasks; - using Ocelot.Errors.Middleware; - using Ocelot.Logging; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Configuration; - using Ocelot.Errors; - using Ocelot.Infrastructure.RequestData; - using Ocelot.Middleware; - using Ocelot.Configuration.Repository; - - public class ExceptionHandlerMiddlewareTests - { - bool _shouldThrowAnException; - private readonly Mock _configRepo; - private readonly Mock _repo; - private Mock _loggerFactory; - private Mock _logger; - private readonly ExceptionHandlerMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - - public ExceptionHandlerMiddlewareTests() - { - _configRepo = new Mock(); - _repo = new Mock(); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = async context => { - await Task.CompletedTask; - - if (_shouldThrowAnException) - { - throw new Exception("BOOM"); - } - - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.OK; - }; - _middleware = new ExceptionHandlerMiddleware(_next, _loggerFactory.Object, _configRepo.Object, _repo.Object); - } - - [Fact] - public void NoDownstreamException() - { - var config = new InternalConfiguration(null, null, null, null, null, null, null, null); - - this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) - .And(_ => GivenTheConfigurationIs(config)) - .When(_ => WhenICallTheMiddleware()) - .Then(_ => ThenTheResponseIsOk()) - .And(_ => TheAspDotnetRequestIdIsSet()) - .BDDfy(); - } - - [Fact] - public void DownstreamException() - { - var config = new InternalConfiguration(null, null, null, null, null, null, null, null); - - this.Given(_ => GivenAnExceptionWillBeThrownDownstream()) - .And(_ => GivenTheConfigurationIs(config)) - .When(_ => WhenICallTheMiddleware()) - .Then(_ => ThenTheResponseIsError()) - .BDDfy(); - } - - [Fact] - public void ShouldSetRequestId() - { - var config = new InternalConfiguration(null, null, null, "requestidkey", null, null, null, null); - - this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) - .And(_ => GivenTheConfigurationIs(config)) - .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) - .Then(_ => ThenTheResponseIsOk()) - .And(_ => TheRequestIdIsSet("RequestId", "1234")) - .BDDfy(); - } - - [Fact] - public void ShouldSetAspDotNetRequestId() - { - var config = new InternalConfiguration(null, null, null, null, null, null, null, null); - - this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) - .And(_ => GivenTheConfigurationIs(config)) - .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) - .Then(_ => ThenTheResponseIsOk()) - .And(_ => TheAspDotnetRequestIdIsSet()) - .BDDfy(); - } - - [Fact] - public void should_throw_exception_if_config_provider_returns_error() - { - this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) - .And(_ => GivenTheConfigReturnsError()) - .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) - .Then(_ => ThenAnExceptionIsThrown()) - .BDDfy(); - } - - [Fact] - public void should_throw_exception_if_config_provider_throws() - { - this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) - .And(_ => GivenTheConfigThrows()) - .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) - .Then(_ => ThenAnExceptionIsThrown()) - .BDDfy(); - } - - private void WhenICallTheMiddlewareWithTheRequestIdKey(string key, string value) - { - _downstreamContext.HttpContext.Request.Headers.Add(key, value); - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void WhenICallTheMiddleware() - { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenTheConfigThrows() - { - var ex = new Exception("outer", new Exception("inner")); - _configRepo - .Setup(x => x.Get()).Throws(ex); - } - - private void ThenAnExceptionIsThrown() - { - _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(500); - } - - private void GivenTheConfigReturnsError() - { - var response = new Responses.ErrorResponse(new FakeError()); - _configRepo - .Setup(x => x.Get()).Returns(response); - } - - private void TheRequestIdIsSet(string key, string value) - { - _repo.Verify(x => x.Add(key, value), Times.Once); - } - - private void GivenTheConfigurationIs(IInternalConfiguration config) - { - var response = new Responses.OkResponse(config); - _configRepo - .Setup(x => x.Get()).Returns(response); - } - - private void GivenAnExceptionWillNotBeThrownDownstream() - { - _shouldThrowAnException = false; - } - - private void GivenAnExceptionWillBeThrownDownstream() - { - _shouldThrowAnException = true; - } - - private void ThenTheResponseIsOk() - { - _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(200); - } - - private void ThenTheResponseIsError() - { - _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(500); - } - - private void TheAspDotnetRequestIdIsSet() - { - _repo.Verify(x => x.Add(It.IsAny(), It.IsAny()), Times.Once); - } - - class FakeError : Error - { - internal FakeError() - : base("meh", OcelotErrorCode.CannotAddDataError) - { - } - } - } -} +namespace Ocelot.UnitTests.Errors +{ + using System; + using System.Net; + using System.Threading.Tasks; + using Ocelot.Errors.Middleware; + using Ocelot.Logging; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Configuration; + using Ocelot.Errors; + using Ocelot.Infrastructure.RequestData; + using Ocelot.Middleware; + using Ocelot.Configuration.Repository; + + public class ExceptionHandlerMiddlewareTests + { + bool _shouldThrowAnException; + private readonly Mock _configRepo; + private readonly Mock _repo; + private Mock _loggerFactory; + private Mock _logger; + private readonly ExceptionHandlerMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; + + public ExceptionHandlerMiddlewareTests() + { + _configRepo = new Mock(); + _repo = new Mock(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = async context => { + await Task.CompletedTask; + + if (_shouldThrowAnException) + { + throw new Exception("BOOM"); + } + + context.HttpContext.Response.StatusCode = (int)HttpStatusCode.OK; + }; + _middleware = new ExceptionHandlerMiddleware(_next, _loggerFactory.Object, _configRepo.Object, _repo.Object); + } + + [Fact] + public void NoDownstreamException() + { + var config = new InternalConfiguration(null, null, null, null, null, null, null, null); + + this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) + .And(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenICallTheMiddleware()) + .Then(_ => ThenTheResponseIsOk()) + .And(_ => TheAspDotnetRequestIdIsSet()) + .BDDfy(); + } + + [Fact] + public void DownstreamException() + { + var config = new InternalConfiguration(null, null, null, null, null, null, null, null); + + this.Given(_ => GivenAnExceptionWillBeThrownDownstream()) + .And(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenICallTheMiddleware()) + .Then(_ => ThenTheResponseIsError()) + .BDDfy(); + } + + [Fact] + public void ShouldSetRequestId() + { + var config = new InternalConfiguration(null, null, null, "requestidkey", null, null, null, null); + + this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) + .And(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) + .Then(_ => ThenTheResponseIsOk()) + .And(_ => TheRequestIdIsSet("RequestId", "1234")) + .BDDfy(); + } + + [Fact] + public void ShouldSetAspDotNetRequestId() + { + var config = new InternalConfiguration(null, null, null, null, null, null, null, null); + + this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) + .And(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) + .Then(_ => ThenTheResponseIsOk()) + .And(_ => TheAspDotnetRequestIdIsSet()) + .BDDfy(); + } + + [Fact] + public void should_throw_exception_if_config_provider_returns_error() + { + this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) + .And(_ => GivenTheConfigReturnsError()) + .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) + .Then(_ => ThenAnExceptionIsThrown()) + .BDDfy(); + } + + [Fact] + public void should_throw_exception_if_config_provider_throws() + { + this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) + .And(_ => GivenTheConfigThrows()) + .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) + .Then(_ => ThenAnExceptionIsThrown()) + .BDDfy(); + } + + private void WhenICallTheMiddlewareWithTheRequestIdKey(string key, string value) + { + _downstreamContext.HttpContext.Request.Headers.Add(key, value); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + + private void GivenTheConfigThrows() + { + var ex = new Exception("outer", new Exception("inner")); + _configRepo + .Setup(x => x.Get()).Throws(ex); + } + + private void ThenAnExceptionIsThrown() + { + _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(500); + } + + private void GivenTheConfigReturnsError() + { + var response = new Responses.ErrorResponse(new FakeError()); + _configRepo + .Setup(x => x.Get()).Returns(response); + } + + private void TheRequestIdIsSet(string key, string value) + { + _repo.Verify(x => x.Add(key, value), Times.Once); + } + + private void GivenTheConfigurationIs(IInternalConfiguration config) + { + var response = new Responses.OkResponse(config); + _configRepo + .Setup(x => x.Get()).Returns(response); + } + + private void GivenAnExceptionWillNotBeThrownDownstream() + { + _shouldThrowAnException = false; + } + + private void GivenAnExceptionWillBeThrownDownstream() + { + _shouldThrowAnException = true; + } + + private void ThenTheResponseIsOk() + { + _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(200); + } + + private void ThenTheResponseIsError() + { + _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(500); + } + + private void TheAspDotnetRequestIdIsSet() + { + _repo.Verify(x => x.Add(It.IsAny(), It.IsAny()), Times.Once); + } + + class FakeError : Error + { + internal FakeError() + : base("meh", OcelotErrorCode.CannotAddDataError) + { + } + } + } +} From 43ad065e4a92a35af1f79f5dea4381b485fdab4e Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Tue, 31 Jul 2018 21:21:46 +0100 Subject: [PATCH 09/50] #500 validate that there is an address in consul service, also made this clear in docs (#520) --- docs/features/servicediscovery.rst | 13 ++++- ...ableToFindServiceDiscoveryProviderError.cs | 12 ----- .../ConsulServiceDiscoveryProvider.cs | 2 +- .../ConsulServiceDiscoveryProviderTests.cs | 50 ++++++++++++++++++- 4 files changed, 62 insertions(+), 15 deletions(-) delete mode 100644 src/Ocelot/ServiceDiscovery/Errors/UnableToFindServiceDiscoveryProviderError.cs diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index e6cb3695..d115b9c2 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -63,7 +63,7 @@ You services need to be added to Consul something like below (c# style but hopef is not to add http or https to the Address field. I have been contacted before about not accepting scheme in Address and accepting scheme in address. After reading `this `_ I don't think the scheme should be in there. -.. code-block: json +.. code-block: csharp new AgentService() { @@ -73,6 +73,17 @@ in address. After reading `this ID = "some-id", } +Or + +.. code-block:: json + + "Service": { + "ID": "some-id", + "Service": "some-service-name", + "Address": "localhost", + "Port": 8080 + } + ACL Token --------- diff --git a/src/Ocelot/ServiceDiscovery/Errors/UnableToFindServiceDiscoveryProviderError.cs b/src/Ocelot/ServiceDiscovery/Errors/UnableToFindServiceDiscoveryProviderError.cs deleted file mode 100644 index a31ed2ee..00000000 --- a/src/Ocelot/ServiceDiscovery/Errors/UnableToFindServiceDiscoveryProviderError.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.ServiceDiscovery.Errors -{ - public class UnableToFindServiceDiscoveryProviderError : Error - { - public UnableToFindServiceDiscoveryProviderError(string message) - : base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError) - { - } - } -} diff --git a/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs index 88bcc5d5..e8abe460 100644 --- a/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs +++ b/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs @@ -59,7 +59,7 @@ namespace Ocelot.ServiceDiscovery.Providers private bool IsValid(ServiceEntry serviceEntry) { - if (serviceEntry.Service.Address.Contains("http://") || serviceEntry.Service.Address.Contains("https://") || serviceEntry.Service.Port <= 0) + if (string.IsNullOrEmpty(serviceEntry.Service.Address) || serviceEntry.Service.Address.Contains("http://") || serviceEntry.Service.Address.Contains("https://") || serviceEntry.Service.Port <= 0) { return false; } diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs index b60330d8..4bc05efb 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs @@ -31,7 +31,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery private readonly Mock _factory; private readonly Mock _logger; private string _receivedToken; - private IConsulClientFactory _clientFactory; + private readonly IConsulClientFactory _clientFactory; public ConsulServiceDiscoveryProviderTests() { @@ -134,6 +134,41 @@ namespace Ocelot.UnitTests.ServiceDiscovery .BDDfy(); } + [Fact] + public void should_not_return_services_with_empty_address() + { + var serviceEntryOne = new ServiceEntry() + { + Service = new AgentService() + { + Service = _serviceName, + Address = "", + Port = 50881, + ID = Guid.NewGuid().ToString(), + Tags = new string[0] + }, + }; + + var serviceEntryTwo = new ServiceEntry() + { + Service = new AgentService() + { + Service = _serviceName, + Address = null, + Port = 50888, + ID = Guid.NewGuid().ToString(), + Tags = new string[0] + }, + }; + + this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName)) + .And(x => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) + .When(x => WhenIGetTheServices()) + .Then(x => ThenTheCountIs(0)) + .And(x => ThenTheLoggerHasBeenCalledCorrectlyForEmptyAddress()) + .BDDfy(); + } + [Fact] public void should_not_return_services_with_invalid_port() { @@ -182,6 +217,19 @@ namespace Ocelot.UnitTests.ServiceDiscovery Times.Once); } + private void ThenTheLoggerHasBeenCalledCorrectlyForEmptyAddress() + { + _logger.Verify( + x => x.LogWarning( + "Unable to use service Address: and Port: 50881 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"), + Times.Once); + + _logger.Verify( + x => x.LogWarning( + "Unable to use service Address: and Port: 50888 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"), + Times.Once); + } + private void ThenTheLoggerHasBeenCalledCorrectlyForInvalidPorts() { _logger.Verify( From 92043bad903bf09fefc5dfaecbfdbe38e24d35d1 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Tue, 31 Jul 2018 21:27:27 +0100 Subject: [PATCH 10/50] #515 added detail about needing to set base url for administration api usage --- docs/features/administration.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/features/administration.rst b/docs/features/administration.rst index 4563ba5c..33a1e1cb 100644 --- a/docs/features/administration.rst +++ b/docs/features/administration.rst @@ -55,10 +55,30 @@ The secret is the client secret that Ocelot's internal IdentityServer will use t .AddAdministration("/administration", "secret"); } +In order for the administration API to work, Ocelot / IdentityServer must be able to call itself for validation. This means that you need to add the base url of Ocelot +to global configuration if it is not default (http://localhost:5000). This can be done as follows.. + +If you want to run on a different host and port locally.. + +.. code-block:: json + + "GlobalConfiguration": { + "BaseUrl": "http://localhost:55580" + } + +or if Ocelot is exposed via dns + +.. code-block:: json + + "GlobalConfiguration": { + "BaseUrl": "http://mydns.com" + } + Now if you went with the configuration options above and want to access the API you can use the postman scripts called ocelot.postman_collection.json in the solution to change the Ocelot configuration. Obviously these will need to be changed if you are running Ocelot on a different url to http://localhost:5000. + The scripts show you how to request a bearer token from ocelot and then use it to GET the existing configuration and POST a configuration. From 46132b6fa8c50961b39255174a86961d7f91cf6f Mon Sep 17 00:00:00 2001 From: aqa510415008 <510415008@qq.com> Date: Thu, 2 Aug 2018 06:54:01 +0800 Subject: [PATCH 11/50] Fix configuration error, throw error message (#522) Fix configuration error, throw error message --- src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 7b723917..76a23001 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -118,6 +118,11 @@ // now create the config var internalConfigCreator = builder.ApplicationServices.GetService(); var internalConfig = await internalConfigCreator.Create(fileConfig.Value); + //Configuration error, throw error message + if (internalConfig.IsError) + { + ThrowToStopOcelotStarting(internalConfig); + } // now save it in memory var internalConfigRepo = builder.ApplicationServices.GetService(); From 95a7c821fdb441994388728812eea9a36ce34ffa Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Thu, 2 Aug 2018 18:27:19 +0100 Subject: [PATCH 12/50] add v1 tracer interface so we can break out tracing (#523) --- src/Ocelot/Logging/ITracer.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/Ocelot/Logging/ITracer.cs diff --git a/src/Ocelot/Logging/ITracer.cs b/src/Ocelot/Logging/ITracer.cs new file mode 100644 index 00000000..9e035d75 --- /dev/null +++ b/src/Ocelot/Logging/ITracer.cs @@ -0,0 +1,19 @@ +namespace Ocelot.Logging +{ + using System; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + + public interface ITracer + { + void Event(HttpContext httpContext, string @event); + + Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken, + Action addTraceIdToRepo, + Func> baseSendAsync); + } +} From 24f8a18579d4a51d01de34f81c5f6cdba134ba90 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Thu, 2 Aug 2018 20:42:57 +0100 Subject: [PATCH 13/50] make stringextensions available --- src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs b/src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs index 3c85ce5c..df9d24da 100644 --- a/src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs +++ b/src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs @@ -3,7 +3,7 @@ using System.Linq; namespace Ocelot.Infrastructure.Extensions { - internal static class StringValuesExtensions + public static class StringValuesExtensions { public static string GetValue(this StringValues stringValues) { From 37fb32b7f5721fa876b8ef3a3c6cb9e9c7dba025 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Fri, 3 Aug 2018 08:11:47 +0100 Subject: [PATCH 14/50] broke out butterfly into seperate project (#521) * broke out butterfly into seperate project * nearly did it... * updated docs as I have broken the butterfly code off into a seperate dll --- docs/features/tracing.rst | 14 +- .../Creator/HttpHandlerOptionsCreator.cs | 7 +- .../DependencyInjection/IOcelotBuilder.cs | 3 - .../DependencyInjection/OcelotBuilder.cs | 8 - .../Logging/OcelotDiagnosticListener.cs | 38 +-- src/Ocelot/Ocelot.csproj | 6 +- .../DelegatingHandlerHandlerFactory.cs | 1 - .../Requester/OcelotHttpTracingHandler.cs | 62 +--- src/Ocelot/Requester/TracingHandlerFactory.cs | 6 +- .../ButterflyTracingTests.cs | 284 ------------------ test/Ocelot.AcceptanceTests/Steps.cs | 39 +-- .../HttpHandlerOptionsCreatorTests.cs | 28 +- .../DependencyInjection/OcelotBuilderTests.cs | 29 +- .../Requester/TracingHandlerFactoryTests.cs | 26 +- 14 files changed, 66 insertions(+), 485 deletions(-) delete mode 100644 test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs diff --git a/docs/features/tracing.rst b/docs/features/tracing.rst index ae38c9ea..bda43627 100644 --- a/docs/features/tracing.rst +++ b/docs/features/tracing.rst @@ -1,19 +1,29 @@ Tracing ======= -Ocelot providers tracing functionality from the excellent `Butterfly `_ project. +This page details how to perform distributed tracing with Ocelot. At the moment we only support Butterfly but other tracers might just work without +anything Ocelot specific. + +Butterfly +^^^^^^^^^ + +Ocelot providers tracing functionality from the excellent `Butterfly `_ project. The code for the Ocelot integration +can be found `here `_. In order to use the tracing please read the Butterfly documentation. In ocelot you need to do the following if you wish to trace a ReRoute. + ``Install-Package Ocelot.Tracing.Butterfly`` + In your ConfigureServices method .. code-block:: csharp services .AddOcelot() - .AddOpenTracing(option => + // this comes from Ocelot.Tracing.Butterfly package + .AddButterfly(option => { //this is the url that the butterfly collector server is running on... option.CollectorUrl = "http://localhost:9618"; diff --git a/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs b/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs index 37b0a097..f55e35ca 100644 --- a/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs @@ -1,18 +1,17 @@ namespace Ocelot.Configuration.Creator { using System; - using Butterfly.Client.Tracing; + using Logging; using Microsoft.Extensions.DependencyInjection; using Ocelot.Configuration.File; - using Ocelot.Requester; public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator { - private readonly IServiceTracer _tracer; + private readonly ITracer _tracer; public HttpHandlerOptionsCreator(IServiceProvider services) { - _tracer = services.GetService(); + _tracer = services.GetService(); } public HttpHandlerOptions Create(FileHttpHandlerOptions options) diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index 9a4a60b8..b6684013 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -1,4 +1,3 @@ -using Butterfly.Client.AspNetCore; using CacheManager.Core; using System; using System.Net.Http; @@ -17,8 +16,6 @@ namespace Ocelot.DependencyInjection IOcelotBuilder AddCacheManager(Action settings); - IOcelotBuilder AddOpenTracing(Action settings); - IOcelotAdministrationBuilder AddAdministration(string path, string secret); IOcelotAdministrationBuilder AddAdministration(string path, Action configOptions); diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index f120742d..6d4873d5 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -40,10 +40,8 @@ namespace Ocelot.DependencyInjection using Ocelot.Configuration; using Microsoft.Extensions.DependencyInjection.Extensions; using System.Net.Http; - using Butterfly.Client.AspNetCore; using Ocelot.Infrastructure; using Ocelot.Infrastructure.Consul; - using Butterfly.Client.Tracing; using Ocelot.Middleware.Multiplexer; using ServiceDiscovery.Providers; using Steeltoe.Common.Discovery; @@ -228,12 +226,6 @@ namespace Ocelot.DependencyInjection return this; } - public IOcelotBuilder AddOpenTracing(Action settings) - { - _services.AddButterfly(settings); - return this; - } - public IOcelotBuilder AddStoreOcelotConfigurationInConsul() { _services.AddHostedService(); diff --git a/src/Ocelot/Logging/OcelotDiagnosticListener.cs b/src/Ocelot/Logging/OcelotDiagnosticListener.cs index 15952ce0..48c1a35c 100644 --- a/src/Ocelot/Logging/OcelotDiagnosticListener.cs +++ b/src/Ocelot/Logging/OcelotDiagnosticListener.cs @@ -2,26 +2,20 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DiagnosticAdapter; using Microsoft.Extensions.DependencyInjection; -using Butterfly.Client.AspNetCore; -using Butterfly.OpenTracing; using Ocelot.Middleware; -using Butterfly.Client.Tracing; -using System.Linq; -using System.Collections.Generic; -using Ocelot.Infrastructure.Extensions; -using Ocelot.Requester; namespace Ocelot.Logging { public class OcelotDiagnosticListener { - private readonly IServiceTracer _tracer; private readonly IOcelotLogger _logger; + private readonly ITracer _tracer; - public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceProvider services) + public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceProvider serviceProvider) { - _tracer = services.GetService(); _logger = factory.CreateLogger(); + _tracer = serviceProvider.GetService(); + } [DiagnosticName("Ocelot.MiddlewareException")] @@ -67,29 +61,7 @@ namespace Ocelot.Logging private void Event(HttpContext httpContext, string @event) { - // todo - if the user isnt using tracing the code gets here and will blow up on - // _tracer.Tracer.TryExtract.. - if(_tracer == null) - { - return; - } - - var span = httpContext.GetSpan(); - - if(span == null) - { - var spanBuilder = new SpanBuilder($"server {httpContext.Request.Method} {httpContext.Request.Path}"); - if (_tracer.Tracer.TryExtract(out var spanContext, httpContext.Request.Headers, (c, k) => c[k].GetValue(), - c => c.Select(x => new KeyValuePair(x.Key, x.Value.GetValue())).GetEnumerator())) - { - spanBuilder.AsChildOf(spanContext); - } - - span = _tracer.Start(spanBuilder); - httpContext.SetSpan(span); - } - - span?.Log(LogField.CreateNew().Event(@event)); + _tracer?.Event(httpContext, @event); } } } diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index 4d13aeda..30e6771d 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 2.0.0 @@ -25,10 +25,6 @@ True - - - NU1701 - diff --git a/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs index 340afb47..a4263ef1 100644 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs +++ b/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; -using Butterfly.Client.Tracing; using Microsoft.Extensions.DependencyInjection; using Ocelot.Configuration; using Ocelot.Logging; diff --git a/src/Ocelot/Requester/OcelotHttpTracingHandler.cs b/src/Ocelot/Requester/OcelotHttpTracingHandler.cs index fd6dce2b..b5585d57 100644 --- a/src/Ocelot/Requester/OcelotHttpTracingHandler.cs +++ b/src/Ocelot/Requester/OcelotHttpTracingHandler.cs @@ -1,21 +1,19 @@ -using System; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Butterfly.Client.Tracing; -using Butterfly.OpenTracing; -using Ocelot.Infrastructure.RequestData; - -namespace Ocelot.Requester +namespace Ocelot.Requester { + using Logging; + using System; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Ocelot.Infrastructure.RequestData; + public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler { - private readonly IServiceTracer _tracer; + private readonly ITracer _tracer; private readonly IRequestScopedDataRepository _repo; - private const string PrefixSpanId = "ot-spanId"; public OcelotHttpTracingHandler( - IServiceTracer tracer, + ITracer tracer, IRequestScopedDataRepository repo, HttpMessageHandler httpMessageHandler = null) { @@ -28,46 +26,8 @@ namespace Ocelot.Requester HttpRequestMessage request, CancellationToken cancellationToken) { - return _tracer.ChildTraceAsync($"httpclient {request.Method}", DateTimeOffset.UtcNow, span => TracingSendAsync(span, request, cancellationToken)); - } - - protected virtual async Task TracingSendAsync( - ISpan span, - HttpRequestMessage request, - CancellationToken cancellationToken) - { - if (request.Headers.Contains(PrefixSpanId)) - { - request.Headers.Remove(PrefixSpanId); - request.Headers.TryAddWithoutValidation(PrefixSpanId, span.SpanContext.SpanId); - } - - _repo.Add("TraceId", span.SpanContext.TraceId); - span.Tags.Client().Component("HttpClient") - .HttpMethod(request.Method.Method) - .HttpUrl(request.RequestUri.OriginalString) - .HttpHost(request.RequestUri.Host) - .HttpPath(request.RequestUri.PathAndQuery) - .PeerAddress(request.RequestUri.OriginalString) - .PeerHostName(request.RequestUri.Host) - .PeerPort(request.RequestUri.Port); - - _tracer.Tracer.Inject(span.SpanContext, request.Headers, (c, k, v) => - { - if (!c.Contains(k)) - { - c.Add(k, v); - } - }); - - span.Log(LogField.CreateNew().ClientSend()); - - var responseMessage = await base.SendAsync(request, cancellationToken); - - span.Log(LogField.CreateNew().ClientReceive()); - - return responseMessage; + return _tracer.SendAsync(request, cancellationToken, x => _repo.Add("TraceId", x), (r,c) => base.SendAsync(r, c)); } } } diff --git a/src/Ocelot/Requester/TracingHandlerFactory.cs b/src/Ocelot/Requester/TracingHandlerFactory.cs index 943de460..2740ef96 100644 --- a/src/Ocelot/Requester/TracingHandlerFactory.cs +++ b/src/Ocelot/Requester/TracingHandlerFactory.cs @@ -1,13 +1,13 @@ namespace Ocelot.Requester { using System; - using Butterfly.Client.Tracing; + using Logging; using Ocelot.Infrastructure.RequestData; using Microsoft.Extensions.DependencyInjection; public class TracingHandlerFactory : ITracingHandlerFactory { - private readonly IServiceTracer _tracer; + private readonly ITracer _tracer; private readonly IRequestScopedDataRepository _repo; public TracingHandlerFactory( @@ -15,7 +15,7 @@ namespace Ocelot.Requester IRequestScopedDataRepository repo) { _repo = repo; - _tracer = services.GetService(); + _tracer = services.GetService(); } public ITracingHandler Get() diff --git a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs deleted file mode 100644 index 51f459ef..00000000 --- a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using Butterfly.Client.AspNetCore; -using static Rafty.Infrastructure.Wait; - -namespace Ocelot.AcceptanceTests -{ - using Xunit.Abstractions; - - public class ButterflyTracingTests : IDisposable - { - private IWebHost _serviceOneBuilder; - private IWebHost _serviceTwoBuilder; - private IWebHost _fakeButterfly; - private readonly Steps _steps; - private string _downstreamPathOne; - private string _downstreamPathTwo; - private int _butterflyCalled; - private readonly ITestOutputHelper _output; - - public ButterflyTracingTests(ITestOutputHelper output) - { - _output = output; - _steps = new Steps(); - } - - [Fact] - public void should_forward_tracing_information_from_ocelot_and_downstream_services() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/values", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51887, - } - }, - UpstreamPathTemplate = "/api001/values", - UpstreamHttpMethod = new List { "Get" }, - HttpHandlerOptions = new FileHttpHandlerOptions - { - UseTracing = true - }, - QoSOptions = new FileQoSOptions - { - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak = 10, - TimeoutValue = 5000 - } - }, - new FileReRoute - { - DownstreamPathTemplate = "/api/values", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51388, - } - }, - UpstreamPathTemplate = "/api002/values", - UpstreamHttpMethod = new List { "Get" }, - HttpHandlerOptions = new FileHttpHandlerOptions - { - UseTracing = true - }, - QoSOptions = new FileQoSOptions - { - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak = 10, - TimeoutValue = 5000 - } - } - } - }; - - var butterflyUrl = "http://localhost:9618"; - - this.Given(x => GivenFakeButterfly(butterflyUrl)) - .And(x => GivenServiceOneIsRunning("http://localhost:51887", "/api/values", 200, "Hello from Laura", butterflyUrl)) - .And(x => GivenServiceTwoIsRunning("http://localhost:51388", "/api/values", 200, "Hello from Tom", butterflyUrl)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api002/values")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) - .BDDfy(); - - var commandOnAllStateMachines = WaitFor(10000).Until(() => _butterflyCalled >= 4); - - _output.WriteLine($"_butterflyCalled is {_butterflyCalled}"); - - commandOnAllStateMachines.ShouldBeTrue(); - } - - [Fact] - public void should_return_tracing_header() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/values", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51387, - } - }, - UpstreamPathTemplate = "/api001/values", - UpstreamHttpMethod = new List { "Get" }, - HttpHandlerOptions = new FileHttpHandlerOptions - { - UseTracing = true - }, - QoSOptions = new FileQoSOptions - { - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak = 10, - TimeoutValue = 5000 - }, - DownstreamHeaderTransform = new Dictionary() - { - {"Trace-Id", "{TraceId}"}, - {"Tom", "Laura"} - } - } - } - }; - - var butterflyUrl = "http://localhost:9618"; - - this.Given(x => GivenFakeButterfly(butterflyUrl)) - .And(x => GivenServiceOneIsRunning("http://localhost:51387", "/api/values", 200, "Hello from Laura", butterflyUrl)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .And(x => _steps.ThenTheTraceHeaderIsSet("Trace-Id")) - .And(x => _steps.ThenTheResponseHeaderIs("Tom", "Laura")) - .BDDfy(); - } - - private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody, string butterflyUrl) - { - _serviceOneBuilder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .ConfigureServices(services => { - services.AddButterfly(option => - { - option.CollectorUrl = butterflyUrl; - option.Service = "Service One"; - option.IgnoredRoutesRegexPatterns = new string[0]; - }); - }) - .Configure(app => - { - app.UsePathBase(basePath); - app.Run(async context => - { - _downstreamPathOne = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - - if(_downstreamPathOne != basePath) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - }); - }) - .Build(); - - _serviceOneBuilder.Start(); - } - - private void GivenFakeButterfly(string baseUrl) - { - _fakeButterfly = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.Run(async context => - { - _butterflyCalled++; - await context.Response.WriteAsync("OK..."); - }); - }) - .Build(); - - _fakeButterfly.Start(); - } - - private void GivenServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody, string butterflyUrl) - { - _serviceTwoBuilder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .ConfigureServices(services => { - services.AddButterfly(option => - { - option.CollectorUrl = butterflyUrl; - option.Service = "Service Two"; - option.IgnoredRoutesRegexPatterns = new string[0]; - }); - }) - .Configure(app => - { - app.UsePathBase(basePath); - app.Run(async context => - { - _downstreamPathTwo = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - - if(_downstreamPathTwo != basePath) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - }); - }) - .Build(); - - _serviceTwoBuilder.Start(); - } - - internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPathOne, string expectedDownstreamPath) - { - _downstreamPathOne.ShouldBe(expectedDownstreamPathOne); - _downstreamPathTwo.ShouldBe(expectedDownstreamPath); - } - - public void Dispose() - { - _serviceOneBuilder?.Dispose(); - _serviceTwoBuilder?.Dispose(); - _fakeButterfly?.Dispose(); - _steps.Dispose(); - } - } -} diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 7f5e296a..9dcb8bc5 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -31,6 +31,7 @@ using static Ocelot.Infrastructure.Wait; namespace Ocelot.AcceptanceTests { + using Butterfly; using Configuration.Repository; using Microsoft.Net.Http.Headers; using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue; @@ -146,44 +147,6 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } - internal void GivenOcelotIsRunningUsingButterfly(string butterflyUrl) - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddOcelot() - .AddOpenTracing(option => - { - //this is the url that the butterfly collector server is running on... - option.CollectorUrl = butterflyUrl; - option.Service = "Ocelot"; - }); - }) - .Configure(app => - { - app.Use(async (context, next) => - { - await next.Invoke(); - }); - app.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - internal void GivenIWait(int wait) { Thread.Sleep(wait); diff --git a/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs index aea0e659..e20aca02 100644 --- a/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs @@ -1,6 +1,4 @@ using System; -using Butterfly.Client.Tracing; -using Butterfly.OpenTracing; using Microsoft.Extensions.DependencyInjection; using Ocelot.Configuration; using Ocelot.Configuration.Creator; @@ -12,6 +10,12 @@ using Xunit; namespace Ocelot.UnitTests.Configuration { + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Ocelot.Logging; + public class HttpHandlerOptionsCreatorTests { private IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; @@ -155,23 +159,21 @@ namespace Ocelot.UnitTests.Configuration private void GivenARealTracer() { - var tracer = new RealTracer(); - _serviceCollection.AddSingleton(); + var tracer = new FakeTracer(); + _serviceCollection.AddSingleton(); _serviceProvider = _serviceCollection.BuildServiceProvider(); _httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(_serviceProvider); } - class RealTracer : IServiceTracer + class FakeTracer : ITracer { - public ITracer Tracer => throw new NotImplementedException(); + public void Event(HttpContext httpContext, string @event) + { + throw new NotImplementedException(); + } - public string ServiceName => throw new NotImplementedException(); - - public string Environment => throw new NotImplementedException(); - - public string Identity => throw new NotImplementedException(); - - public ISpan Start(ISpanBuilder spanBuilder) + public Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken, Action addTraceIdToRepo, + Func> baseSendAsync) { throw new NotImplementedException(); } diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 358dab18..368124d3 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -23,6 +23,8 @@ using Ocelot.Middleware.Multiplexer; namespace Ocelot.UnitTests.DependencyInjection { + using Butterfly; + public class OcelotBuilderTests { private readonly IServiceCollection _services; @@ -140,15 +142,6 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } - [Fact] - public void should_set_up_tracing() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpOpentracing()) - .When(x => WhenIAccessOcelotHttpTracingHandler()) - .BDDfy(); - } - [Fact] public void should_set_up_without_passing_in_config() { @@ -387,24 +380,6 @@ namespace Ocelot.UnitTests.DependencyInjection } } - private void WhenISetUpOpentracing() - { - try - { - _ocelotBuilder.AddOpenTracing( - option => - { - option.CollectorUrl = "http://localhost:9618"; - option.Service = "Ocelot.ManualTest"; - } - ); - } - catch (Exception e) - { - _ex = e; - } - } - private void WhenIAccessLoggerFactory() { try diff --git a/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs b/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs index 9128c710..146af701 100644 --- a/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs +++ b/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs @@ -1,27 +1,27 @@ -using System; -using Butterfly.Client.Tracing; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Requester; -using Shouldly; -using Xunit; - namespace Ocelot.UnitTests.Requester { + using System; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Infrastructure.RequestData; + using Ocelot.Requester; + using Shouldly; + using Xunit; + using Ocelot.Logging; + public class TracingHandlerFactoryTests { - private TracingHandlerFactory _factory; - private Mock _tracer; + private readonly TracingHandlerFactory _factory; + private Mock _tracer; private IServiceCollection _serviceCollection; private IServiceProvider _serviceProvider; private Mock _repo; public TracingHandlerFactoryTests() { - _tracer = new Mock(); + _tracer = new Mock(); _serviceCollection = new ServiceCollection(); - _serviceCollection.AddSingleton(_tracer.Object); + _serviceCollection.AddSingleton(_tracer.Object); _serviceProvider = _serviceCollection.BuildServiceProvider(); _repo = new Mock(); _factory = new TracingHandlerFactory(_serviceProvider, _repo.Object); From 89f0cc786aa0895a9945eeea9a8ba6ad5ba21fb9 Mon Sep 17 00:00:00 2001 From: JanKolk Date: Sat, 4 Aug 2018 09:02:30 +0200 Subject: [PATCH 15/50] #524 Solved - Add request headers to websocket connection (#528) --- .../Middleware/WebSocketsProxyMiddleware.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs b/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs index 68b2df48..033653ca 100644 --- a/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs +++ b/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs @@ -14,7 +14,7 @@ namespace Ocelot.WebSockets.Middleware public WebSocketsProxyMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory) - :base(loggerFactory.CreateLogger()) + : base(loggerFactory.CreateLogger()) { _next = next; } @@ -29,6 +29,17 @@ namespace Ocelot.WebSockets.Middleware var wsToUpstreamClient = await context.WebSockets.AcceptWebSocketAsync(); var wsToDownstreamService = new ClientWebSocket(); + + foreach (var requestHeader in context.Request.Headers) + { + // Do not copy the Sec-Websocket headers because it is specified by the own connection it will fail when you copy this one. + if (requestHeader.Key.StartsWith("Sec-WebSocket")) + { + continue; + } + wsToDownstreamService.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value); + } + var uri = new Uri(serverEndpoint); await wsToDownstreamService.ConnectAsync(uri, CancellationToken.None); From ad8025df1ba7b38d7821971ec83815519babeb10 Mon Sep 17 00:00:00 2001 From: Marcelo Castagna Date: Sat, 4 Aug 2018 04:17:31 -0300 Subject: [PATCH 16/50] Reload config on change (#527) * Reload config on change. Added test case. * added testing for adding the json with reloadOnChange = false --- .../Middleware/OcelotMiddlewareExtensions.cs | 20 ++++-- .../ConfigurationReloadTests.cs | 68 +++++++++++++++++++ test/Ocelot.AcceptanceTests/Steps.cs | 41 +++++++++++ 3 files changed, 122 insertions(+), 7 deletions(-) create mode 100644 test/Ocelot.AcceptanceTests/ConfigurationReloadTests.cs diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 76a23001..9f5b9a20 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -113,12 +113,12 @@ { // make configuration from file system? // earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this - var fileConfig = builder.ApplicationServices.GetService>(); + var fileConfig = builder.ApplicationServices.GetService>(); // now create the config var internalConfigCreator = builder.ApplicationServices.GetService(); - var internalConfig = await internalConfigCreator.Create(fileConfig.Value); - //Configuration error, throw error message + var internalConfig = await internalConfigCreator.Create(fileConfig.CurrentValue); + //Configuration error, throw error message if (internalConfig.IsError) { ThrowToStopOcelotStarting(internalConfig); @@ -128,6 +128,12 @@ var internalConfigRepo = builder.ApplicationServices.GetService(); internalConfigRepo.AddOrReplace(internalConfig.Data); + fileConfig.OnChange(async (config) => + { + var newInternalConfig = await internalConfigCreator.Create(config); + internalConfigRepo.AddOrReplace(newInternalConfig.Data); + }); + var fileConfigRepo = builder.ApplicationServices.GetService(); var adminPath = builder.ApplicationServices.GetService(); @@ -155,7 +161,7 @@ } private static async Task SetFileConfigInConsul(IApplicationBuilder builder, - IFileConfigurationRepository fileConfigRepo, IOptions fileConfig, + IFileConfigurationRepository fileConfigRepo, IOptionsMonitor fileConfig, IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo) { // get the config from consul. @@ -168,7 +174,7 @@ else if (ConfigNotStoredInConsul(fileConfigFromConsul)) { //there was no config in consul set the file in config in consul - await fileConfigRepo.Set(fileConfig.Value); + await fileConfigRepo.Set(fileConfig.CurrentValue); } else { @@ -197,9 +203,9 @@ } } - private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptions fileConfig) + private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptionsMonitor fileConfig) { - var response = await fileConfigSetter.Set(fileConfig.Value); + var response = await fileConfigSetter.Set(fileConfig.CurrentValue); if (IsError(response)) { diff --git a/test/Ocelot.AcceptanceTests/ConfigurationReloadTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationReloadTests.cs new file mode 100644 index 00000000..f5a23938 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/ConfigurationReloadTests.cs @@ -0,0 +1,68 @@ +using Ocelot.Configuration.File; +using Ocelot.Configuration.Setter; +using Ocelot.Middleware; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Text; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class ConfigurationReloadTests : IDisposable + { + private FileConfiguration _initialConfig; + private FileConfiguration _anotherConfig; + private Steps _steps; + + public ConfigurationReloadTests() + { + _steps = new Steps(); + + _initialConfig = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + RequestIdKey = "initialKey" + } + }; + + _anotherConfig = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + RequestIdKey = "someOtherKey" + } + }; + } + + [Fact] + public void should_reload_config_on_change() + { + this.Given(x => _steps.GivenThereIsAConfiguration(_initialConfig)) + .And(x => _steps.GivenOcelotIsRunningReloadingConfig(true)) + .And(x => _steps.GivenThereIsAConfiguration(_anotherConfig)) + .And(x => _steps.GivenIWait(2500)) + .And(x => _steps.ThenConfigShouldBe(_anotherConfig)) + .BDDfy(); + } + + [Fact] + public void should_not_reload_config_on_change() + { + + this.Given(x => _steps.GivenThereIsAConfiguration(_initialConfig)) + .And(x => _steps.GivenOcelotIsRunningReloadingConfig(false)) + .And(x => _steps.GivenThereIsAConfiguration(_anotherConfig)) + .And(x => _steps.GivenIWait(2500)) + .And(x => _steps.ThenConfigShouldBe(_initialConfig)) + .BDDfy(); + } + + public void Dispose() + { + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 9dcb8bc5..63d3a540 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -34,6 +34,7 @@ namespace Ocelot.AcceptanceTests using Butterfly; using Configuration.Repository; using Microsoft.Net.Http.Headers; + using Ocelot.Configuration.Creator; using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue; public class Steps : IDisposable @@ -55,6 +56,18 @@ namespace Ocelot.AcceptanceTests _random = new Random(); } + public async Task ThenConfigShouldBe(FileConfiguration fileConfig) + { + var internalConfigCreator = _ocelotServer.Host.Services.GetService(); + + var internalConfigRepo = _ocelotServer.Host.Services.GetService(); + var internalConfig = internalConfigRepo.Get(); + + var config = await internalConfigCreator.Create(fileConfig); + + internalConfig.Data.RequestId.ShouldBe(config.Data.RequestId); + } + public async Task StartFakeOcelotWithWebSockets() { _ocelotBuilder = new WebHostBuilder(); @@ -116,6 +129,34 @@ namespace Ocelot.AcceptanceTests File.WriteAllText(configurationPath, jsonConfiguration); } + public void GivenOcelotIsRunningReloadingConfig(bool shouldReload) + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: shouldReload); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot(); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + /// /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. /// From a66c7989e2a6e75c1ef4d9e01fc5607859fb0340 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 4 Aug 2018 08:21:28 +0100 Subject: [PATCH 17/50] docs for reloading json --- docs/features/configuration.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 220ae2bc..3f92f1ba 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -146,6 +146,16 @@ I guess it means if you want to use Ocelot to its fullest you take on Consul as This feature has a 3 second ttl cache before making a new request to your local consul agent. +Reload JSON config on change +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Ocelot supports reloading the json configuration file on change. e.g. the following will recreate Ocelots internal configuration when the ocelot.json file is updated +manully. + +.. code-block:: json + + config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true); + Configuration Key ----------------- From 0e7665064ec91b704448b75c57cc911c5520ff36 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 4 Aug 2018 08:22:17 +0100 Subject: [PATCH 18/50] #344 how to signalr (#530) --- docs/features/websockets.rst | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/features/websockets.rst b/docs/features/websockets.rst index 624f42a9..5b587919 100644 --- a/docs/features/websockets.rst +++ b/docs/features/websockets.rst @@ -35,6 +35,52 @@ With this configuration set Ocelot will match any websocket traffic that comes i Ocelot will receive messages from the upstream client, proxy these to the downstream service, receive messages from the downstream service and proxy these to the upstream client. +SignalR +^^^^^^^ + +Ocelot supports proxying SignalR. This functionality was requested in `Issue 344 `_. + +In order to get websocket proxying working with Ocelot you need to do the following. + +Install Microsoft.AspNetCore.SignalR.Client 1.0.2 you can try other packages but this one is tested. + +Do not run it in IISExpress or install the websockets feature in the IIS features + +In your Configure method you need to tell your application to use SignalR. + +.. code-block:: csharp + + Configure(app => + { + app.UseWebSockets(); + app.UseOcelot().Wait(); + }) + +Then in your ocelot.json add the following to proxy a ReRoute using SignalR. Note normal Ocelot routing rules apply the main thing is the scheme which is set to "ws". + +.. code-block:: json + + { + "ReRoutes": [ + { + "DownstreamPathTemplate": "/{catchAll}", + "DownstreamScheme": "ws", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 50000 + } + ], + "UpstreamPathTemplate": "/gateway/{catchAll}", + "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE", "OPTIONS" ] + } + ] +} + +With this configuration set Ocelot will match any SignalR traffic that comes in on / and proxy it to localhost:5001/ws. To make this clearer +Ocelot will receive messages from the upstream client, proxy these to the downstream service, receive messages from the downstream service and +proxy these to the upstream client. + Supported ^^^^^^^^^ From 9ae4ab926004f537c8e828a0fcf3755bf685c901 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sat, 4 Aug 2018 15:15:41 +0100 Subject: [PATCH 19/50] correct spelling --- docs/features/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 3f92f1ba..11c69a0d 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -150,7 +150,7 @@ Reload JSON config on change ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ocelot supports reloading the json configuration file on change. e.g. the following will recreate Ocelots internal configuration when the ocelot.json file is updated -manully. +manually. .. code-block:: json From 2673aebee295aa511397271a2985ea7c75b4bb9f Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 11 Aug 2018 11:53:43 +0100 Subject: [PATCH 20/50] ignore .DS_Store --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d1d0ee7d..e314ed94 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ *.user *.userosscache *.sln.docstates -.DS_Store +*.DS_Store # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs From d4b65198f3651e3a4b1eab64cf105ad3c95033cf Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 11 Aug 2018 18:21:07 +0530 Subject: [PATCH 21/50] #529 removed cache manager specific code (#535) --- src/Ocelot/Cache/IOcelotCache.cs | 21 +- .../Cache/Middleware/OutputCacheMiddleware.cs | 24 +- src/Ocelot/Cache/OcelotCacheManagerCache.cs | 44 ---- src/Ocelot/Cache/Regions.cs | 26 +- src/Ocelot/Configuration/CacheOptions.cs | 5 +- .../DependencyInjection/IOcelotBuilder.cs | 3 - .../DependencyInjection/OcelotBuilder.cs | 223 ++++++++--------- src/Ocelot/Ocelot.csproj | 3 - .../Caching/InMemoryJsonHandle.cs | 137 ----------- test/Ocelot.AcceptanceTests/CachingTests.cs | 225 ------------------ .../ConfigurationInConsulTests.cs | 70 ++---- .../Ocelot.AcceptanceTests.csproj | 1 - test/Ocelot.AcceptanceTests/Steps.cs | 210 +++------------- .../AdministrationTests.cs | 29 --- .../ThreadSafeHeadersTests.cs | 12 - .../AuthenticationMiddlewareTests.cs | 11 +- .../Cache/CacheManagerCacheTests.cs | 103 -------- .../OutputCacheMiddlewareRealCacheTests.cs | 95 -------- .../Cache/OutputCacheMiddlewareTests.cs | 16 +- .../DependencyInjection/OcelotBuilderTests.cs | 44 ---- .../OcelotPipelineExtensionsTests.cs | 4 +- 21 files changed, 210 insertions(+), 1096 deletions(-) delete mode 100644 src/Ocelot/Cache/OcelotCacheManagerCache.cs delete mode 100644 test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs delete mode 100644 test/Ocelot.AcceptanceTests/CachingTests.cs delete mode 100644 test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs delete mode 100644 test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs diff --git a/src/Ocelot/Cache/IOcelotCache.cs b/src/Ocelot/Cache/IOcelotCache.cs index b445b647..e70afec1 100644 --- a/src/Ocelot/Cache/IOcelotCache.cs +++ b/src/Ocelot/Cache/IOcelotCache.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; namespace Ocelot.Cache { @@ -9,5 +8,25 @@ namespace Ocelot.Cache void AddAndDelete(string key, T value, TimeSpan ttl, string region); T Get(string key, string region); void ClearRegion(string region); + } + + public class NoCache : IOcelotCache + { + public void Add(string key, T value, TimeSpan ttl, string region) + { + } + + public void AddAndDelete(string key, T value, TimeSpan ttl, string region) + { + } + + public void ClearRegion(string region) + { + } + + public T Get(string key, string region) + { + return default(T); + } } } diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 8a27ccba..00c2e322 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -1,29 +1,25 @@ -using System; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Ocelot.Logging; -using Ocelot.Middleware; -using System.IO; -using Ocelot.Middleware.Multiplexer; - -namespace Ocelot.Cache.Middleware +namespace Ocelot.Cache.Middleware { + using System; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + using Ocelot.Logging; + using Ocelot.Middleware; + using System.IO; + public class OutputCacheMiddleware : OcelotMiddleware { private readonly OcelotRequestDelegate _next; private readonly IOcelotCache _outputCache; - private readonly IRegionCreator _regionCreator; public OutputCacheMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, - IOcelotCache outputCache, - IRegionCreator regionCreator) + IOcelotCache outputCache) :base(loggerFactory.CreateLogger()) { _next = next; _outputCache = outputCache; - _regionCreator = regionCreator; } public async Task Invoke(DownstreamContext context) diff --git a/src/Ocelot/Cache/OcelotCacheManagerCache.cs b/src/Ocelot/Cache/OcelotCacheManagerCache.cs deleted file mode 100644 index 29f1ed33..00000000 --- a/src/Ocelot/Cache/OcelotCacheManagerCache.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -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, string region) - { - _cacheManager.Add(new CacheItem(key, region, value, ExpirationMode.Absolute, ttl)); - } - - public void AddAndDelete(string key, T value, TimeSpan ttl, string region) - { - var exists = _cacheManager.Get(key); - - if (exists != null) - { - _cacheManager.Remove(key); - } - - Add(key, value, ttl, region); - } - - public T Get(string key, string region) - { - return _cacheManager.Get(key, region); - } - - public void ClearRegion(string region) - { - _cacheManager.ClearRegion(region); - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Cache/Regions.cs b/src/Ocelot/Cache/Regions.cs index baec6087..2ef0f6a8 100644 --- a/src/Ocelot/Cache/Regions.cs +++ b/src/Ocelot/Cache/Regions.cs @@ -1,14 +1,14 @@ -using System.Collections.Generic; - -namespace Ocelot.Cache -{ - public class Regions - { - public Regions(List value) - { - Value = value; - } - - public List Value {get;private set;} - } +namespace Ocelot.Cache +{ + using System.Collections.Generic; + + public class Regions + { + public Regions(List value) + { + Value = value; + } + + public List Value { get; } + } } diff --git a/src/Ocelot/Configuration/CacheOptions.cs b/src/Ocelot/Configuration/CacheOptions.cs index a3a926a4..46c49280 100644 --- a/src/Ocelot/Configuration/CacheOptions.cs +++ b/src/Ocelot/Configuration/CacheOptions.cs @@ -8,7 +8,8 @@ Region = region; } - public int TtlSeconds { get; private set; } - public string Region {get;private set;} + public int TtlSeconds { get; private set; } + + public string Region { get; private set; } } } diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index b6684013..21e52f0b 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -1,4 +1,3 @@ -using CacheManager.Core; using System; using System.Net.Http; using IdentityServer4.AccessTokenValidation; @@ -14,8 +13,6 @@ namespace Ocelot.DependencyInjection IConfiguration Configuration { get; } IOcelotBuilder AddStoreOcelotConfigurationInConsul(); - IOcelotBuilder AddCacheManager(Action settings); - IOcelotAdministrationBuilder AddAdministration(string path, string secret); IOcelotAdministrationBuilder AddAdministration(string path, Action configOptions); diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 6d4873d5..727241a0 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -1,6 +1,5 @@ namespace Ocelot.DependencyInjection { - using CacheManager.Core; using IdentityServer4.Models; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; @@ -50,118 +49,110 @@ namespace Ocelot.DependencyInjection public class OcelotBuilder : IOcelotBuilder { - private readonly IServiceCollection _services; - private readonly IConfiguration _configurationRoot; - - public IServiceCollection Services => _services; - - public IConfiguration Configuration => _configurationRoot; + public IServiceCollection Services { get; } + public IConfiguration Configuration { get; } public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot) { - _configurationRoot = configurationRoot; - _services = services; - - //add default cache settings... - Action defaultCachingSettings = x => - { - x.WithDictionaryHandle(); - }; + Configuration = configurationRoot; + Services = services; - AddCacheManager(defaultCachingSettings); + Services.Configure(configurationRoot); + + //default no caches... + Services.TryAddSingleton, NoCache>(); + Services.TryAddSingleton, NoCache>(); - //add ocelot services... - _services.Configure(configurationRoot); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.AddSingleton(); - _services.AddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); if (UsingEurekaServiceDiscoveryProvider(configurationRoot)) { - _services.AddDiscoveryClient(configurationRoot); + Services.AddDiscoveryClient(configurationRoot); } else { - _services.TryAddSingleton(); + Services.TryAddSingleton(); } - _services.TryAddSingleton(); + Services.TryAddSingleton(); // see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc // could maybe use a scoped data repository - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.AddMemoryCache(); - _services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.AddMemoryCache(); + Services.TryAddSingleton(); //add asp.net services.. var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly; - _services.AddMvcCore() + Services.AddMvcCore() .AddApplicationPart(assembly) .AddControllersAsServices() .AddAuthorization() .AddJsonFormatters(); - _services.AddLogging(); - _services.AddMiddlewareAnalysis(); - _services.AddWebEncoders(); + Services.AddLogging(); + Services.AddMiddlewareAnalysis(); + Services.AddWebEncoders(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.AddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.AddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); } public IOcelotAdministrationBuilder AddAdministration(string path, string secret) @@ -176,8 +167,8 @@ namespace Ocelot.DependencyInjection AddIdentityServer(identityServerConfiguration, administrationPath); } - _services.AddSingleton(administrationPath); - return new OcelotAdministrationBuilder(_services, _configurationRoot); + Services.AddSingleton(administrationPath); + return new OcelotAdministrationBuilder(Services, Configuration); } public IOcelotAdministrationBuilder AddAdministration(string path, Action configureOptions) @@ -189,21 +180,21 @@ namespace Ocelot.DependencyInjection AddIdentityServer(configureOptions); } - _services.AddSingleton(administrationPath); - return new OcelotAdministrationBuilder(_services, _configurationRoot); + Services.AddSingleton(administrationPath); + return new OcelotAdministrationBuilder(Services, Configuration); } public IOcelotBuilder AddSingletonDefinedAggregator() where T : class, IDefinedAggregator { - _services.AddSingleton(); + Services.AddSingleton(); return this; } public IOcelotBuilder AddTransientDefinedAggregator() where T : class, IDefinedAggregator { - _services.AddTransient(); + Services.AddTransient(); return this; } @@ -212,15 +203,15 @@ namespace Ocelot.DependencyInjection { if(global) { - _services.AddTransient(); - _services.AddTransient(s => { + Services.AddTransient(); + Services.AddTransient(s => { var service = s.GetService(); return new GlobalDelegatingHandler(service); }); } else { - _services.AddTransient(); + Services.AddTransient(); } return this; @@ -228,59 +219,33 @@ namespace Ocelot.DependencyInjection public IOcelotBuilder AddStoreOcelotConfigurationInConsul() { - _services.AddHostedService(); - _services.AddSingleton(); - return this; - } - - public IOcelotBuilder AddCacheManager(Action settings) - { - var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", settings); - var ocelotOutputCacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); - - _services.RemoveAll(typeof(ICacheManager)); - _services.RemoveAll(typeof(IOcelotCache)); - _services.AddSingleton>(cacheManagerOutputCache); - _services.AddSingleton>(ocelotOutputCacheManager); - - var ocelotConfigCacheManagerOutputCache = CacheFactory.Build("OcelotConfigurationCache", settings); - var ocelotConfigCacheManager = new OcelotCacheManagerCache(ocelotConfigCacheManagerOutputCache); - _services.RemoveAll(typeof(ICacheManager)); - _services.RemoveAll(typeof(IOcelotCache)); - _services.AddSingleton>(ocelotConfigCacheManagerOutputCache); - _services.AddSingleton>(ocelotConfigCacheManager); - - var fileConfigCacheManagerOutputCache = CacheFactory.Build("FileConfigurationCache", settings); - var fileConfigCacheManager = new OcelotCacheManagerCache(fileConfigCacheManagerOutputCache); - _services.RemoveAll(typeof(ICacheManager)); - _services.RemoveAll(typeof(IOcelotCache)); - _services.AddSingleton>(fileConfigCacheManagerOutputCache); - _services.AddSingleton>(fileConfigCacheManager); + Services.AddHostedService(); + Services.AddSingleton(); return this; } private void AddIdentityServer(Action configOptions) { - _services + Services .AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) .AddIdentityServerAuthentication(configOptions); } private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath) { - _services.TryAddSingleton(identityServerConfiguration); - var identityServerBuilder = _services + Services.TryAddSingleton(identityServerConfiguration); + var identityServerBuilder = Services .AddIdentityServer(o => { o.IssuerUri = "Ocelot"; }) .AddInMemoryApiResources(Resources(identityServerConfiguration)) .AddInMemoryClients(Client(identityServerConfiguration)); - var urlFinder = new BaseUrlFinder(_configurationRoot); + var urlFinder = new BaseUrlFinder(Configuration); var baseSchemeUrlAndPort = urlFinder.Find(); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - _services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) + Services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) .AddIdentityServerAuthentication(o => { o.Authority = baseSchemeUrlAndPort + adminPath.Path; diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index 30e6771d..e4aaee61 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -45,9 +45,6 @@ all - - - diff --git a/test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs b/test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs deleted file mode 100644 index 3f160a7e..00000000 --- a/test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs +++ /dev/null @@ -1,137 +0,0 @@ -using CacheManager.Core; -using CacheManager.Core.Internal; -using CacheManager.Core.Logging; -using System; -using System.Collections.Concurrent; -using System.Linq; -using static CacheManager.Core.Utility.Guard; - -namespace Ocelot.AcceptanceTests.Caching -{ - public class InMemoryJsonHandle : BaseCacheHandle - { - private readonly ICacheSerializer _serializer; - private readonly ConcurrentDictionary> _cache; - - public InMemoryJsonHandle( - ICacheManagerConfiguration managerConfiguration, - CacheHandleConfiguration configuration, - ICacheSerializer serializer, - ILoggerFactory loggerFactory) : base(managerConfiguration, configuration) - { - _cache = new ConcurrentDictionary>(); - _serializer = serializer; - Logger = loggerFactory.CreateLogger(this); - } - - public override int Count => _cache.Count; - - protected override ILogger Logger { get; } - - public override void Clear() => _cache.Clear(); - - public override void ClearRegion(string region) - { - NotNullOrWhiteSpace(region, nameof(region)); - - var key = string.Concat(region, ":"); - foreach (var item in _cache.Where(p => p.Key.StartsWith(key, StringComparison.OrdinalIgnoreCase))) - { - _cache.TryRemove(item.Key, out Tuple val); - } - } - - public override bool Exists(string key) - { - NotNullOrWhiteSpace(key, nameof(key)); - - return _cache.ContainsKey(key); - } - - public override bool Exists(string key, string region) - { - NotNullOrWhiteSpace(region, nameof(region)); - var fullKey = GetKey(key, region); - return _cache.ContainsKey(fullKey); - } - - protected override bool AddInternalPrepared(CacheItem item) - { - NotNull(item, nameof(item)); - - var key = GetKey(item.Key, item.Region); - - var serializedItem = _serializer.SerializeCacheItem(item); - - return _cache.TryAdd(key, new Tuple(item.Value.GetType(), serializedItem)); - } - - protected override CacheItem GetCacheItemInternal(string key) => GetCacheItemInternal(key, null); - - protected override CacheItem GetCacheItemInternal(string key, string region) - { - var fullKey = GetKey(key, region); - - CacheItem deserializedResult = null; - - if (_cache.TryGetValue(fullKey, out Tuple result)) - { - deserializedResult = _serializer.DeserializeCacheItem(result.Item2, result.Item1); - - if (deserializedResult.ExpirationMode != ExpirationMode.None && IsExpired(deserializedResult, DateTime.UtcNow)) - { - _cache.TryRemove(fullKey, out Tuple removeResult); - TriggerCacheSpecificRemove(key, region, CacheItemRemovedReason.Expired, deserializedResult.Value); - return null; - } - } - - return deserializedResult; - } - - protected override void PutInternalPrepared(CacheItem item) - { - NotNull(item, nameof(item)); - - var serializedItem = _serializer.SerializeCacheItem(item); - - _cache[GetKey(item.Key, item.Region)] = new Tuple(item.Value.GetType(), serializedItem); - } - - protected override bool RemoveInternal(string key) => RemoveInternal(key, null); - - protected override bool RemoveInternal(string key, string region) - { - var fullKey = GetKey(key, region); - return _cache.TryRemove(fullKey, out Tuple val); - } - - private static string GetKey(string key, string region) - { - NotNullOrWhiteSpace(key, nameof(key)); - - if (string.IsNullOrWhiteSpace(region)) - { - return key; - } - - return string.Concat(region, ":", key); - } - - private static bool IsExpired(CacheItem item, DateTime now) - { - if (item.ExpirationMode == ExpirationMode.Absolute - && item.CreatedUtc.Add(item.ExpirationTimeout) < now) - { - return true; - } - else if (item.ExpirationMode == ExpirationMode.Sliding - && item.LastAccessedUtc.Add(item.ExpirationTimeout) < now) - { - return true; - } - - return false; - } - } -} diff --git a/test/Ocelot.AcceptanceTests/CachingTests.cs b/test/Ocelot.AcceptanceTests/CachingTests.cs deleted file mode 100644 index bd8f0882..00000000 --- a/test/Ocelot.AcceptanceTests/CachingTests.cs +++ /dev/null @@ -1,225 +0,0 @@ -namespace Ocelot.AcceptanceTests -{ - using System; - using System.Collections.Generic; - using System.Net; - using System.Threading; - using Microsoft.AspNetCore.Http; - using Ocelot.Configuration.File; - using TestStack.BDDfy; - using Xunit; - - public class CachingTests : IDisposable - { - private readonly Steps _steps; - private readonly ServiceHandler _serviceHandler; - - public CachingTests() - { - _serviceHandler = new ServiceHandler(); - _steps = new Steps(); - } - - [Fact] - public void should_return_cached_response() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51899, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 100 - } - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura", null, null)) - .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:51899", 200, "Hello from Tom")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .And(x => _steps.ThenTheContentLengthIs(16)) - .BDDfy(); - } - - [Fact] - public void should_return_cached_response_with_expires_header() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 52839, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 100 - } - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:52839", 200, "Hello from Laura", "Expires", "-1")) - .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:52839", 200, "Hello from Tom")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .And(x => _steps.ThenTheContentLengthIs(16)) - .And(x => _steps.ThenTheResponseBodyHeaderIs("Expires", "-1")) - .BDDfy(); - } - - [Fact] - public void should_return_cached_response_when_using_jsonserialized_cache() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51899, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 100 - } - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura", null, null)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingJsonSerializedCache()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .Given(x => x.GivenTheServiceNowReturns("http://localhost:51899", 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 - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51899, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 1 - } - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", 200, "Hello from Laura", null, null)) - .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:51899", 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) - { - _serviceHandler.Dispose(); - GivenThereIsAServiceRunningOn(url, statusCode, responseBody, null, null); - } - - private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody, string key, string value) - { - _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => - { - if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(key)) - { - context.Response.Headers.Add(key, value); - } - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - } - - public void Dispose() - { - _serviceHandler?.Dispose(); - _steps.Dispose(); - } - } -} diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index 23ce3d11..e44f7496 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -17,6 +17,8 @@ using static Ocelot.Infrastructure.Wait; namespace Ocelot.AcceptanceTests { + using Cache; + public class ConfigurationInConsulTests : IDisposable { private IWebHost _builder; @@ -76,51 +78,6 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } - [Fact] - public void should_return_response_200_with_simple_url_when_using_jsonserialized_cache() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51779, - } - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = 9502 - } - } - }; - - var fakeConsulServiceDiscoveryUrl = "http://localhost:9502"; - - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, "")) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - [Fact] public void should_load_configuration_out_of_consul() { @@ -485,5 +442,28 @@ namespace Ocelot.AcceptanceTests _builder?.Dispose(); _steps.Dispose(); } + + class FakeCache : IOcelotCache + { + public void Add(string key, FileConfiguration value, TimeSpan ttl, string region) + { + throw new NotImplementedException(); + } + + public void AddAndDelete(string key, FileConfiguration value, TimeSpan ttl, string region) + { + throw new NotImplementedException(); + } + + public FileConfiguration Get(string key, string region) + { + throw new NotImplementedException(); + } + + public void ClearRegion(string region) + { + throw new NotImplementedException(); + } + } } } diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index c4e902fd..02082c9f 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -32,7 +32,6 @@ - diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 63d3a540..c67c953b 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -1,40 +1,35 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; -using CacheManager.Core; -using IdentityServer4.AccessTokenValidation; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using Shouldly; -using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; -using Ocelot.AcceptanceTests.Caching; -using System.IO.Compression; -using System.Text; -using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; -using Ocelot.Requester; -using Ocelot.Middleware.Multiplexer; -using static Ocelot.Infrastructure.Wait; - -namespace Ocelot.AcceptanceTests +namespace Ocelot.AcceptanceTests { - using Butterfly; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Threading; + using System.Threading.Tasks; + using IdentityServer4.AccessTokenValidation; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.TestHost; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; + using Newtonsoft.Json; + using Ocelot.Configuration.File; + using Ocelot.DependencyInjection; + using Ocelot.Middleware; + using Shouldly; + using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; + using System.IO.Compression; + using System.Text; + using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; + using Ocelot.Middleware.Multiplexer; + using static Ocelot.Infrastructure.Wait; using Configuration.Repository; - using Microsoft.Net.Http.Headers; using Ocelot.Configuration.Creator; + using CookieHeaderValue = System.Net.Http.Headers.CookieHeaderValue; using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue; public class Steps : IDisposable @@ -44,7 +39,6 @@ namespace Ocelot.AcceptanceTests private HttpResponseMessage _response; private HttpContent _postContent; private BearerToken _token; - public HttpClient OcelotClient => _ocelotClient; public string RequestIdKey = "OcRequestId"; private readonly Random _random; private IWebHostBuilder _webHostBuilder; @@ -428,55 +422,6 @@ namespace Ocelot.AcceptanceTests header.First().ShouldBe(value); } - public void ThenTheResponseBodyHeaderIs(string key, string value) - { - var header = _response.Content.Headers.GetValues(key); - header.First().ShouldBe(value); - } - - public void ThenTheTraceHeaderIsSet(string key) - { - var header = _response.Headers.GetValues(key); - header.First().ShouldNotBeNullOrEmpty(); - } - - public void GivenOcelotIsRunningUsingJsonSerializedCache() - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", false, false); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddOcelot() - .AddCacheManager((x) => - { - x.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithJsonSerializer() - .WithHandle(typeof(InMemoryJsonHandle<>)); - }); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - public void GivenOcelotIsRunningUsingConsulToStoreConfig() { _webHostBuilder = new WebHostBuilder(); @@ -505,69 +450,6 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } - public void GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache() - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddOcelot() - .AddCacheManager((x) => - { - x.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithJsonSerializer() - .WithHandle(typeof(InMemoryJsonHandle<>)); - }) - .AddStoreOcelotConfigurationInConsul(); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - internal void ThenTheResponseShouldBe(FileConfiguration expecteds) - { - var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); - - response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); - response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); - response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for (var i = 0; i < response.ReRoutes.Count; i++) - { - for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++) - { - var result = response.ReRoutes[i].DownstreamHostAndPorts[j]; - var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; - result.Host.ShouldBe(expected.Host); - result.Port.ShouldBe(expected.Port); - } - - response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); - response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); - response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].UpstreamPathTemplate); - response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.ReRoutes[i].UpstreamHttpMethod); - } - } - /// /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. /// @@ -590,15 +472,6 @@ namespace Ocelot.AcceptanceTests .UseConfiguration(configuration) .ConfigureServices(s => { - Action settings = (x) => - { - x.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithDictionaryHandle(); - }; - s.AddOcelot(configuration); }) .ConfigureLogging(l => @@ -688,26 +561,6 @@ namespace Ocelot.AcceptanceTests } } - public void GivenIHaveAnOcelotToken(string adminPath) - { - var tokenUrl = $"{adminPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "admin"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "admin"), - new KeyValuePair("username", "admin"), - new KeyValuePair("password", "admin"), - new KeyValuePair("grant_type", "password") - }; - var content = new FormUrlEncodedContent(formData); - - var response = _ocelotClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - } - public void VerifyIdentiryServerStarted(string url) { using (var httpClient = new HttpClient()) @@ -883,11 +736,6 @@ namespace Ocelot.AcceptanceTests _response.Headers.GetValues(RequestIdKey).First().ShouldBe(expected); } - public void ThenTheContentLengthIs(int expected) - { - _response.Content.Headers.ContentLength.ShouldBe(expected); - } - public void WhenIMakeLotsOfDifferentRequestsToTheApiGateway() { int numberOfRequests = 100; diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index cf382b63..f3835b0c 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -5,7 +5,6 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Security.Claims; -using CacheManager.Core; using IdentityServer4.AccessTokenValidation; using IdentityServer4.Models; using IdentityServer4.Test; @@ -557,17 +556,7 @@ namespace Ocelot.IntegrationTests }) .ConfigureServices(x => { - Action settings = (s) => - { - s.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithDictionaryHandle(); - }; - x.AddOcelot() - .AddCacheManager(settings) .AddAdministration("/administration", "secret"); }) .Configure(app => @@ -682,10 +671,6 @@ namespace Ocelot.IntegrationTests .ConfigureServices(x => { x.AddSingleton(_webHostBuilder); x.AddOcelot() - .AddCacheManager(c => - { - c.WithDictionaryHandle(); - }) .AddAdministration("/administration", configOptions); }) .Configure(app => { @@ -714,17 +699,7 @@ namespace Ocelot.IntegrationTests }) .ConfigureServices(x => { - Action settings = (s) => - { - s.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithDictionaryHandle(); - }; - x.AddOcelot() - .AddCacheManager(settings) .AddAdministration("/administration", "secret"); }) .Configure(app => @@ -755,10 +730,6 @@ namespace Ocelot.IntegrationTests .ConfigureServices(x => { x.AddSingleton(_webHostBuilder); x.AddOcelot() - .AddCacheManager(c => - { - c.WithDictionaryHandle(); - }) .AddAdministration("/administration", "secret"); }) .Configure(app => { diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index 61e9c17f..a1a29f52 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -4,7 +4,6 @@ using System.IO; using System.Net.Http; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Ocelot.Configuration.File; using Shouldly; @@ -13,7 +12,6 @@ using Xunit; using Microsoft.AspNetCore.Http; using System.Threading.Tasks; using System.Collections.Concurrent; -using CacheManager.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Ocelot.DependencyInjection; @@ -113,17 +111,7 @@ namespace Ocelot.IntegrationTests }) .ConfigureServices(x => { - Action settings = (s) => - { - s.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithDictionaryHandle(); - }; - x.AddOcelot() - .AddCacheManager(settings) .AddAdministration("/administration", "secret"); }) .Configure(app => diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs index e287d5f4..1be11401 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs @@ -1,5 +1,6 @@ -using Ocelot.Configuration; -using Ocelot.Middleware; +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] namespace Ocelot.UnitTests.Authentication { @@ -15,14 +16,16 @@ namespace Ocelot.UnitTests.Authentication using Shouldly; using TestStack.BDDfy; using Xunit; + using Ocelot.Configuration; + using Ocelot.Middleware; public class AuthenticationMiddlewareTests { private AuthenticationMiddleware _middleware; - private Mock _factory; + private readonly Mock _factory; private Mock _logger; private OcelotRequestDelegate _next; - private DownstreamContext _downstreamContext; + private readonly DownstreamContext _downstreamContext; public AuthenticationMiddlewareTests() { diff --git a/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs b/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs deleted file mode 100644 index 4b891d73..00000000 --- a/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs +++ /dev/null @@ -1,103 +0,0 @@ -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; - private string _region; - - public CacheManagerCacheTests() - { - _mockCacheManager = new Mock>(); - _ocelotOcelotCacheManager = new OcelotCacheManagerCache(_mockCacheManager.Object); - } - - [Fact] - public void should_get_from_cache() - { - this.Given(x => x.GivenTheFollowingIsCached("someKey", "someRegion", "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(); - } - - [Fact] - public void should_delete_key_from_cache() - { - this.Given(_ => GivenTheFollowingRegion("fookey")) - .When(_ => WhenIDeleteTheRegion("fookey")) - .Then(_ => ThenTheRegionIsDeleted("fookey")) - .BDDfy(); - } - - private void WhenIDeleteTheRegion(string region) - { - _ocelotOcelotCacheManager.ClearRegion(region); - } - - private void ThenTheRegionIsDeleted(string region) - { - _mockCacheManager - .Verify(x => x.ClearRegion(region), Times.Once); - } - - private void GivenTheFollowingRegion(string key) - { - _ocelotOcelotCacheManager.Add(key, "doesnt matter", TimeSpan.FromSeconds(10), "region"); - } - - private void WhenIAddToTheCache(string key, string value, TimeSpan ttlSeconds) - { - _key = key; - _value = value; - _ttlSeconds = ttlSeconds; - _ocelotOcelotCacheManager.Add(_key, _value, _ttlSeconds, "region"); - } - - 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, _region); - } - - private void GivenTheFollowingIsCached(string key, string region, string value) - { - _key = key; - _value = value; - _region = region; - _mockCacheManager - .Setup(x => x.Get(It.IsAny(), It.IsAny())) - .Returns(value); - } - } -} diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs deleted file mode 100644 index b3bbface..00000000 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs +++ /dev/null @@ -1,95 +0,0 @@ -namespace Ocelot.UnitTests.Cache -{ - using System.Linq; - using System.Net; - using System.Net.Http.Headers; - using CacheManager.Core; - using Shouldly; - using System.Collections.Generic; - using System.Net.Http; - using System.Threading.Tasks; - using Moq; - using Ocelot.Cache; - using Ocelot.Cache.Middleware; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.Logging; - using Ocelot.Middleware; - using TestStack.BDDfy; - using Xunit; - using Microsoft.AspNetCore.Http; - using Ocelot.Middleware.Multiplexer; - - public class OutputCacheMiddlewareRealCacheTests - { - private readonly IOcelotCache _cacheManager; - private readonly OutputCacheMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - private Mock _loggerFactory; - private IRegionCreator _regionCreator; - private Mock _logger; - - public OutputCacheMiddlewareRealCacheTests() - { - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _regionCreator = new RegionCreator(); - var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", x => - { - x.WithDictionaryHandle(); - }); - _cacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _downstreamContext.DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123")); - _next = context => Task.CompletedTask; - _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager, _regionCreator); - } - - [Fact] - public void should_cache_content_headers() - { - var content = new StringContent("{\"Test\": 1}") - { - Headers = { ContentType = new MediaTypeHeaderValue("application/json")} - }; - - var response = new DownstreamResponse(content, HttpStatusCode.OK, new List>>()); - - this.Given(x => x.GivenResponseIsNotCached(response)) - .And(x => x.GivenTheDownstreamRouteIs()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheContentTypeHeaderIsCached()) - .BDDfy(); - } - - private void WhenICallTheMiddleware() - { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void ThenTheContentTypeHeaderIsCached() - { - var result = _cacheManager.Get("GET-https://some.url/blah?abcd=123", "kanken"); - var header = result.ContentHeaders["Content-Type"]; - header.First().ShouldBe("application/json"); - } - - private void GivenResponseIsNotCached(DownstreamResponse response) - { - _downstreamContext.DownstreamResponse = response; - } - - private void GivenTheDownstreamRouteIs() - { - var reRoute = new DownstreamReRouteBuilder() - .WithIsCached(true) - .WithCacheOptions(new CacheOptions(100, "kanken")) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build(); - - _downstreamContext.DownstreamReRoute = reRoute; - } - } -} diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index c0b59350..d13b22f2 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -16,24 +16,22 @@ using TestStack.BDDfy; using Xunit; using System.Net; + using Microsoft.Extensions.DependencyInjection; using Ocelot.Middleware; - using Ocelot.Middleware.Multiplexer; public class OutputCacheMiddlewareTests { - private readonly Mock> _cacheManager; + private readonly Mock> _cache; private readonly Mock _loggerFactory; private Mock _logger; private OutputCacheMiddleware _middleware; private readonly DownstreamContext _downstreamContext; private readonly OcelotRequestDelegate _next; private CachedResponse _response; - private readonly IRegionCreator _regionCreator; public OutputCacheMiddlewareTests() { - _cacheManager = new Mock>(); - _regionCreator = new RegionCreator(); + _cache = new Mock>(); _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _loggerFactory = new Mock(); _logger = new Mock(); @@ -91,14 +89,14 @@ private void WhenICallTheMiddleware() { - _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager.Object, _regionCreator); + _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cache.Object); _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } private void GivenThereIsACachedResponse(CachedResponse response) { _response = response; - _cacheManager + _cache .Setup(x => x.Get(It.IsAny(), It.IsAny())) .Returns(_response); } @@ -127,13 +125,13 @@ private void ThenTheCacheGetIsCalledCorrectly() { - _cacheManager + _cache .Verify(x => x.Get(It.IsAny(), It.IsAny()), Times.Once); } private void ThenTheCacheAddIsCalledCorrectly() { - _cacheManager + _cache .Verify(x => x.Add(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } } diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 368124d3..9b80757c 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; -using CacheManager.Core; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.Extensions.Configuration; @@ -81,16 +80,6 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } - [Fact] - public void should_set_up_cache_manager() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpCacheManager()) - .Then(x => ThenAnExceptionIsntThrown()) - .And(x => OnlyOneVersionOfEachCacheIsRegistered()) - .BDDfy(); - } - [Fact] public void should_set_up_consul() { @@ -282,24 +271,6 @@ namespace Ocelot.UnitTests.DependencyInjection first.ShouldBe(second); } - private void OnlyOneVersionOfEachCacheIsRegistered() - { - var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); - var outputCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); - 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() { try @@ -365,21 +336,6 @@ namespace Ocelot.UnitTests.DependencyInjection } } - private void WhenISetUpCacheManager() - { - try - { - _ocelotBuilder.AddCacheManager(x => { - x.WithMaxRetries(_maxRetries); - x.WithDictionaryHandle(); - }); - } - catch (Exception e) - { - _ex = e; - } - } - private void WhenIAccessLoggerFactory() { try diff --git a/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs index fd6fd8bf..0de6cae2 100644 --- a/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs +++ b/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs @@ -1,7 +1,9 @@ namespace Ocelot.UnitTests.Middleware { + using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Ocelot.Cache; using Ocelot.DependencyInjection; using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamUrlCreator.Middleware; @@ -40,7 +42,6 @@ namespace Ocelot.UnitTests.Middleware .BDDfy(); } - private void ThenThePipelineIsBuilt() { _handlers.ShouldNotBeNull(); @@ -67,7 +68,6 @@ namespace Ocelot.UnitTests.Middleware _handlers = _builder.BuildOcelotPipeline(new OcelotPipelineConfiguration()); } - private void GivenTheDepedenciesAreSetUp() { IConfigurationBuilder test = new ConfigurationBuilder(); From a46462506dfc9508dd95c217aad55c9849b1c561 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 11 Aug 2018 19:30:26 +0530 Subject: [PATCH 22/50] #529 updated docs (#536) --- docs/features/caching.rst | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/docs/features/caching.rst b/docs/features/caching.rst index fd780b1e..f7ba7719 100644 --- a/docs/features/caching.rst +++ b/docs/features/caching.rst @@ -6,7 +6,15 @@ the `CacheManager `_ project. This is a that is solving a lot of caching problems. I would reccomend using this package to cache with Ocelot. -The following example shows how to add CacheManager to Ocelot so that you can do output caching. The first thing you need to do is add the following to your ConfigureServices.. +The following example shows how to add CacheManager to Ocelot so that you can do output caching. + +First of all add the following NuGet package. + + ``Install-Package Ocelot.Cache.CacheManager`` + +This will give you access to the Ocelot cache manager extension methods. + +The second thing you need to do something like the following to your ConfigureServices.. .. code-block:: csharp @@ -16,7 +24,7 @@ The following example shows how to add CacheManager to Ocelot so that you can do x.WithDictionaryHandle(); }) -In order to use caching on a route in your ReRoute configuration add this setting. +Finally in order to use caching on a route in your ReRoute configuration add this setting. .. code-block:: json @@ -24,11 +32,23 @@ In order to use caching on a route in your ReRoute configuration add this settin In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds. -If you look at the example `here `_ 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 +If you look at the example `here `_ you can see how the cache manager +is setup and then passed into the Ocelot AddCacheManager 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. You can also clear the cache for a region by calling Ocelot's administration API. +Your own caching +^^^^^^^^^^^^^^^^ + +If you want to add your own caching method implement the following interfaces and register them in DI +e.g. ``services.AddSingleton, MyCache>()`` + +``IOcelotCache`` this is for output caching. + +``IOcelotCache`` this is for caching the file configuration if you are calling something remote to get your config such as Consul. + +Please dig into the Ocelot source code to find more. I would really appreciate it if anyone wants to implement Redis, memcache etc.. + From afe322693e041dfa009fbdb27427afe15e07e6f5 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 11 Aug 2018 20:47:40 +0530 Subject: [PATCH 23/50] added nuget package url (#537) --- src/Ocelot/Ocelot.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index e4aaee61..28c7c3d8 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -12,6 +12,7 @@ API Gateway;.NET core https://github.com/TomPallister/Ocelot https://github.com/TomPallister/Ocelot + http://threemammals.com/images/ocelot_logo.png win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 false false From a91235b40521a8b96f321c8e15a8f4e5f2c9896c Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 12 Aug 2018 14:46:46 +0530 Subject: [PATCH 24/50] #529 removed consul deps and introduced delegate to find service discovery provider (#538) --- .../ConsulFileConfigurationRepository.cs | 97 --- .../DependencyInjection/IOcelotBuilder.cs | 3 +- .../DependencyInjection/OcelotBuilder.cs | 9 - .../Consul/ConsulClientFactory.cs | 22 - .../Consul/IConsulClientFactory.cs | 10 - .../Middleware/OcelotMiddlewareExtensions.cs | 3 +- src/Ocelot/Ocelot.csproj | 1 - .../ConsulRegistryConfiguration.cs | 18 - .../ConsulServiceDiscoveryProvider.cs | 77 --- .../PolingConsulServiceDiscoveryProvider.cs | 56 -- .../ServiceDiscoveryProviderFactory.cs | 59 +- .../ConfigurationInConsulTests.cs | 469 --------------- .../Ocelot.AcceptanceTests.csproj | 1 - .../ServiceDiscoveryTests.cs | 564 ------------------ test/Ocelot.AcceptanceTests/Steps.cs | 46 -- .../TwoDownstreamServicesTests.cs | 151 ----- test/Ocelot.AcceptanceTests/WebSocketTests.cs | 94 --- .../Ocelot.IntegrationTests.csproj | 1 - .../Ocelot.ManualTest.csproj | 1 - .../ConsulFileConfigurationRepositoryTests.cs | 263 -------- .../DependencyInjection/OcelotBuilderTests.cs | 62 +- .../Infrastructure/InMemoryBusTests.cs | 2 +- .../ConsulServiceDiscoveryProviderTests.cs | 297 --------- ...lingConsulServiceDiscoveryProviderTests.cs | 80 --- ...viceFabricServiceDiscoveryProviderTests.cs | 17 +- .../ServiceProviderFactoryTests.cs | 83 +-- 26 files changed, 101 insertions(+), 2385 deletions(-) delete mode 100644 src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs delete mode 100644 src/Ocelot/Infrastructure/Consul/ConsulClientFactory.cs delete mode 100644 src/Ocelot/Infrastructure/Consul/IConsulClientFactory.cs delete mode 100644 src/Ocelot/ServiceDiscovery/Configuration/ConsulRegistryConfiguration.cs delete mode 100644 src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs delete mode 100644 src/Ocelot/ServiceDiscovery/Providers/PolingConsulServiceDiscoveryProvider.cs delete mode 100644 test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs delete mode 100644 test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs delete mode 100644 test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationRepositoryTests.cs delete mode 100644 test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs delete mode 100644 test/Ocelot.UnitTests/ServiceDiscovery/PollingConsulServiceDiscoveryProviderTests.cs diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs deleted file mode 100644 index d8a352a7..00000000 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs +++ /dev/null @@ -1,97 +0,0 @@ -namespace Ocelot.Configuration.Repository -{ - using System; - using System.Text; - using System.Threading.Tasks; - using Consul; - using Newtonsoft.Json; - using Ocelot.Configuration.File; - using Ocelot.Infrastructure.Consul; - using Ocelot.Logging; - using Ocelot.Responses; - using Ocelot.ServiceDiscovery.Configuration; - - public class ConsulFileConfigurationRepository : IFileConfigurationRepository - { - private readonly IConsulClient _consul; - private readonly string _configurationKey; - private readonly Cache.IOcelotCache _cache; - private readonly IOcelotLogger _logger; - - public ConsulFileConfigurationRepository( - Cache.IOcelotCache cache, - IInternalConfigurationRepository repo, - IConsulClientFactory factory, - IOcelotLoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - _cache = cache; - - var internalConfig = repo.Get(); - - _configurationKey = "InternalConfiguration"; - - string token = null; - - if (!internalConfig.IsError) - { - token = internalConfig.Data.ServiceProviderConfiguration.Token; - _configurationKey = !string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey) ? - internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey : _configurationKey; - } - - var config = new ConsulRegistryConfiguration(internalConfig.Data.ServiceProviderConfiguration.Host, - internalConfig.Data.ServiceProviderConfiguration.Port, _configurationKey, token); - - _consul = factory.Get(config); - } - - public async Task> Get() - { - var config = _cache.Get(_configurationKey, _configurationKey); - - if (config != null) - { - return new OkResponse(config); - } - - var queryResult = await _consul.KV.Get(_configurationKey); - - if (queryResult.Response == null) - { - return new OkResponse(null); - } - - var bytes = queryResult.Response.Value; - - var json = Encoding.UTF8.GetString(bytes); - - var consulConfig = JsonConvert.DeserializeObject(json); - - return new OkResponse(consulConfig); - } - - public async Task Set(FileConfiguration ocelotConfiguration) - { - var json = JsonConvert.SerializeObject(ocelotConfiguration, Formatting.Indented); - - var bytes = Encoding.UTF8.GetBytes(json); - - var kvPair = new KVPair(_configurationKey) - { - Value = bytes - }; - - var result = await _consul.KV.Put(kvPair); - - if (result.Response) - { - _cache.AddAndDelete(_configurationKey, ocelotConfiguration, TimeSpan.FromSeconds(3), _configurationKey); - - return new OkResponse(); - } - - return new ErrorResponse(new UnableToSetConfigInConsulError($"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}")); - } - } -} diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index 21e52f0b..2a5abd05 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -10,8 +10,8 @@ namespace Ocelot.DependencyInjection public interface IOcelotBuilder { IServiceCollection Services { get; } + IConfiguration Configuration { get; } - IOcelotBuilder AddStoreOcelotConfigurationInConsul(); IOcelotAdministrationBuilder AddAdministration(string path, string secret); @@ -22,6 +22,7 @@ namespace Ocelot.DependencyInjection IOcelotBuilder AddSingletonDefinedAggregator() where T : class, IDefinedAggregator; + IOcelotBuilder AddTransientDefinedAggregator() where T : class, IDefinedAggregator; } diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 727241a0..d6d87c33 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -40,7 +40,6 @@ namespace Ocelot.DependencyInjection using Microsoft.Extensions.DependencyInjection.Extensions; using System.Net.Http; using Ocelot.Infrastructure; - using Ocelot.Infrastructure.Consul; using Ocelot.Middleware.Multiplexer; using ServiceDiscovery.Providers; using Steeltoe.Common.Discovery; @@ -148,7 +147,6 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); - Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); @@ -217,13 +215,6 @@ namespace Ocelot.DependencyInjection return this; } - public IOcelotBuilder AddStoreOcelotConfigurationInConsul() - { - Services.AddHostedService(); - Services.AddSingleton(); - return this; - } - private void AddIdentityServer(Action configOptions) { Services diff --git a/src/Ocelot/Infrastructure/Consul/ConsulClientFactory.cs b/src/Ocelot/Infrastructure/Consul/ConsulClientFactory.cs deleted file mode 100644 index 26799934..00000000 --- a/src/Ocelot/Infrastructure/Consul/ConsulClientFactory.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using Consul; -using Ocelot.ServiceDiscovery.Configuration; - -namespace Ocelot.Infrastructure.Consul -{ - public class ConsulClientFactory : IConsulClientFactory - { - public IConsulClient Get(ConsulRegistryConfiguration config) - { - return new ConsulClient(c => - { - c.Address = new Uri($"http://{config.Host}:{config.Port}"); - - if (!string.IsNullOrEmpty(config?.Token)) - { - c.Token = config.Token; - } - }); - } - } -} diff --git a/src/Ocelot/Infrastructure/Consul/IConsulClientFactory.cs b/src/Ocelot/Infrastructure/Consul/IConsulClientFactory.cs deleted file mode 100644 index 43428686..00000000 --- a/src/Ocelot/Infrastructure/Consul/IConsulClientFactory.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Consul; -using Ocelot.ServiceDiscovery.Configuration; - -namespace Ocelot.Infrastructure.Consul -{ - public interface IConsulClientFactory - { - IConsulClient Get(ConsulRegistryConfiguration config); - } -} diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 9f5b9a20..6f76ebf3 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -242,7 +242,8 @@ private static bool UsingConsul(IFileConfigurationRepository fileConfigRepo) { - return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository); + //todo - remove coupling by string + return fileConfigRepo.GetType().Name == "ConsulFileConfigurationRepository"; } private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration) diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index 28c7c3d8..38de9a00 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -46,7 +46,6 @@ all - diff --git a/src/Ocelot/ServiceDiscovery/Configuration/ConsulRegistryConfiguration.cs b/src/Ocelot/ServiceDiscovery/Configuration/ConsulRegistryConfiguration.cs deleted file mode 100644 index b99f43cc..00000000 --- a/src/Ocelot/ServiceDiscovery/Configuration/ConsulRegistryConfiguration.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Ocelot.ServiceDiscovery.Configuration -{ - public class ConsulRegistryConfiguration - { - public ConsulRegistryConfiguration(string host, int port, string keyOfServiceInConsul, string token) - { - Host = string.IsNullOrEmpty(host) ? "localhost" : host; - Port = port > 0 ? port : 8500; - KeyOfServiceInConsul = keyOfServiceInConsul; - Token = token; - } - - public string KeyOfServiceInConsul { get; } - public string Host { get; } - public int Port { get; } - public string Token { get; } - } -} diff --git a/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs deleted file mode 100644 index e8abe460..00000000 --- a/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Consul; -using Ocelot.Infrastructure.Consul; -using Ocelot.Infrastructure.Extensions; -using Ocelot.Logging; -using Ocelot.ServiceDiscovery.Configuration; -using Ocelot.Values; - -namespace Ocelot.ServiceDiscovery.Providers -{ - public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider - { - private readonly ConsulRegistryConfiguration _config; - private readonly IOcelotLogger _logger; - private readonly IConsulClient _consul; - private const string VersionPrefix = "version-"; - - public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration config, IOcelotLoggerFactory factory, IConsulClientFactory clientFactory) - {; - _logger = factory.CreateLogger(); - - _config = config; - _consul = clientFactory.Get(_config); - } - - public async Task> Get() - { - var queryResult = await _consul.Health.Service(_config.KeyOfServiceInConsul, string.Empty, true); - - var services = new List(); - - foreach (var serviceEntry in queryResult.Response) - { - if (IsValid(serviceEntry)) - { - services.Add(BuildService(serviceEntry)); - } - else - { - _logger.LogWarning($"Unable to use service Address: {serviceEntry.Service.Address} and Port: {serviceEntry.Service.Port} as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"); - } - } - - return services.ToList(); - } - - private Service BuildService(ServiceEntry serviceEntry) - { - return new Service( - serviceEntry.Service.Service, - new ServiceHostAndPort(serviceEntry.Service.Address, serviceEntry.Service.Port), - serviceEntry.Service.ID, - GetVersionFromStrings(serviceEntry.Service.Tags), - serviceEntry.Service.Tags ?? Enumerable.Empty()); - } - - private bool IsValid(ServiceEntry serviceEntry) - { - if (string.IsNullOrEmpty(serviceEntry.Service.Address) || serviceEntry.Service.Address.Contains("http://") || serviceEntry.Service.Address.Contains("https://") || serviceEntry.Service.Port <= 0) - { - return false; - } - - return true; - } - - private string GetVersionFromStrings(IEnumerable strings) - { - return strings - ?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal)) - .TrimStart(VersionPrefix); - } - } -} diff --git a/src/Ocelot/ServiceDiscovery/Providers/PolingConsulServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/Providers/PolingConsulServiceDiscoveryProvider.cs deleted file mode 100644 index 4206f7c1..00000000 --- a/src/Ocelot/ServiceDiscovery/Providers/PolingConsulServiceDiscoveryProvider.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Consul; -using Ocelot.Infrastructure.Consul; -using Ocelot.Infrastructure.Extensions; -using Ocelot.Logging; -using Ocelot.ServiceDiscovery.Configuration; -using Ocelot.Values; - -namespace Ocelot.ServiceDiscovery.Providers -{ - public class PollingConsulServiceDiscoveryProvider : IServiceDiscoveryProvider - { - private readonly IOcelotLogger _logger; - private readonly IServiceDiscoveryProvider _consulServiceDiscoveryProvider; - private readonly Timer _timer; - private bool _polling; - private List _services; - private string _keyOfServiceInConsul; - - public PollingConsulServiceDiscoveryProvider(int pollingInterval, string keyOfServiceInConsul, IOcelotLoggerFactory factory, IServiceDiscoveryProvider consulServiceDiscoveryProvider) - {; - _logger = factory.CreateLogger(); - _keyOfServiceInConsul = keyOfServiceInConsul; - _consulServiceDiscoveryProvider = consulServiceDiscoveryProvider; - _services = new List(); - - _timer = new Timer(async x => - { - if(_polling) - { - return; - } - - _polling = true; - await Poll(); - _polling = false; - - }, null, pollingInterval, pollingInterval); - } - - public Task> Get() - { - return Task.FromResult(_services); - } - - private async Task Poll() - { - _services = await _consulServiceDiscoveryProvider.Get(); - } - } -} diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index 88b4ce79..9d3aef25 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -1,26 +1,31 @@ -using System.Collections.Generic; -using Ocelot.Configuration; -using Ocelot.Infrastructure.Consul; -using Ocelot.Logging; -using Ocelot.ServiceDiscovery.Configuration; -using Ocelot.ServiceDiscovery.Providers; -using Ocelot.Values; - namespace Ocelot.ServiceDiscovery -{ +{ + using System.Collections.Generic; + using Ocelot.Configuration; + using Ocelot.Logging; + using Ocelot.ServiceDiscovery.Configuration; + using Ocelot.ServiceDiscovery.Providers; + using Ocelot.Values; + using System; + using System.Linq; + using Microsoft.Extensions.DependencyInjection; using Steeltoe.Common.Discovery; public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory { private readonly IOcelotLoggerFactory _factory; - private readonly IConsulClientFactory _consulFactory; private readonly IDiscoveryClient _eurekaClient; + private readonly List _delegates; + private readonly IServiceProvider _provider; - public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IConsulClientFactory consulFactory, IDiscoveryClient eurekaClient) + public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IDiscoveryClient eurekaClient, IServiceProvider provider) { _factory = factory; - _consulFactory = consulFactory; _eurekaClient = eurekaClient; + _provider = provider; + _delegates = provider + .GetServices() + .ToList(); } public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute) @@ -42,29 +47,27 @@ namespace Ocelot.ServiceDiscovery return new ConfigurationServiceProvider(services); } - private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration serviceConfig, string serviceName) + private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration config, string key) { - if (serviceConfig.Type?.ToLower() == "servicefabric") + if (config.Type?.ToLower() == "servicefabric") { - var config = new ServiceFabricConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName); - return new ServiceFabricServiceDiscoveryProvider(config); + var sfConfig = new ServiceFabricConfiguration(config.Host, config.Port, key); + return new ServiceFabricServiceDiscoveryProvider(sfConfig); } - if (serviceConfig.Type?.ToLower() == "eureka") + if (config.Type?.ToLower() == "eureka") { - return new EurekaServiceDiscoveryProvider(serviceName, _eurekaClient); - } - - var consulRegistryConfiguration = new ConsulRegistryConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName, serviceConfig.Token); - - var consulServiceDiscoveryProvider = new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory, _consulFactory); - - if (serviceConfig.Type?.ToLower() == "pollconsul") - { - return new PollingConsulServiceDiscoveryProvider(serviceConfig.PollingInterval, consulRegistryConfiguration.KeyOfServiceInConsul, _factory, consulServiceDiscoveryProvider); + return new EurekaServiceDiscoveryProvider(key, _eurekaClient); } - return consulServiceDiscoveryProvider; + // Todo - dont just hardcode this...only expect Consul at the momement so works. + var finderDelegate = _delegates.FirstOrDefault(); + + var provider = finderDelegate?.Invoke(_provider, config, key); + + return provider; } } + + public delegate IServiceDiscoveryProvider ServiceDiscoveryFinderDelegate(IServiceProvider provider, ServiceProviderConfiguration config, string key); } diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs deleted file mode 100644 index e44f7496..00000000 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ /dev/null @@ -1,469 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Text; -using Consul; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using static Ocelot.Infrastructure.Wait; - -namespace Ocelot.AcceptanceTests -{ - using Cache; - - public class ConfigurationInConsulTests : IDisposable - { - private IWebHost _builder; - private readonly Steps _steps; - private IWebHost _fakeConsulBuilder; - private FileConfiguration _config; - private List _consulServices; - - public ConfigurationInConsulTests() - { - _consulServices = new List(); - _steps = new Steps(); - } - - [Fact] - public void should_return_response_200_with_simple_url() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51779, - } - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = 9500 - } - } - }; - - var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; - - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, "")) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_load_configuration_out_of_consul() - { - var consulPort = 8500; - - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - - var consulConfig = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/status", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51779, - } - }, - UpstreamPathTemplate = "/cs/status", - UpstreamHttpMethod = new List {"Get"} - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) - .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, "")) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_load_configuration_out_of_consul_if_it_is_changed() - { - var consulPort = 8506; - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - - var consulConfig = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/status", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51780, - } - }, - UpstreamPathTemplate = "/cs/status", - UpstreamHttpMethod = new List {"Get"} - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - var secondConsulConfig = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/status", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51780, - } - }, - UpstreamPathTemplate = "/cs/status/awesome", - UpstreamHttpMethod = new List {"Get"} - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) - .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, "")) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51780", "/status", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) - .And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status")) - .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .When(x => GivenTheConsulConfigurationIs(secondConsulConfig)) - .Then(x => ThenTheConfigIsUpdatedInOcelot()) - .BDDfy(); - } - - [Fact] - public void should_handle_request_to_consul_for_downstream_service_and_make_request_no_re_routes_and_rate_limit() - { - const int consulPort = 8523; - const string serviceName = "web"; - const int downstreamServicePort = 8187; - var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = downstreamServicePort, - ID = "web_90_0_2_224_8080", - Tags = new[] {"version-v1"} - }, - }; - - var consulConfig = new FileConfiguration - { - DynamicReRoutes = new List - { - new FileDynamicReRoute - { - ServiceName = serviceName, - RateLimitRule = new FileRateLimitRule() - { - EnableRateLimiting = true, - ClientWhitelist = new List(), - Limit = 3, - Period = "1s", - PeriodTimespan = 1000 - } - } - }, - GlobalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Host = "localhost", - Port = consulPort - }, - RateLimitOptions = new FileRateLimitOptions() - { - ClientIdHeader = "ClientId", - DisableRateLimitHeaders = false, - QuotaExceededMessage = "", - RateLimitCounterPrefix = "", - HttpStatusCode = 428 - }, - DownstreamScheme = "http", - } - }; - - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/something", 200, "Hello from Laura")) - .And(x => GivenTheConsulConfigurationIs(consulConfig)) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something",1)) - .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something", 2)) - .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something",1)) - .Then(x => _steps.ThenTheStatusCodeShouldBe(428)) - .BDDfy(); - } - - private void ThenTheConfigIsUpdatedInOcelot() - { - var result = WaitFor(20000).Until(() => { - try - { - _steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome"); - _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK); - _steps.ThenTheResponseBodyShouldBe("Hello from Laura"); - return true; - } - catch (Exception) - { - return false; - } - }); - result.ShouldBeTrue(); - } - - private void GivenTheConsulConfigurationIs(FileConfiguration config) - { - _config = config; - } - - private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries) - { - foreach(var serviceEntry in serviceEntries) - { - _consulServices.Add(serviceEntry); - } - } - - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName) - { - _fakeConsulBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") - { - var json = JsonConvert.SerializeObject(_config); - - var bytes = Encoding.UTF8.GetBytes(json); - - var base64 = Convert.ToBase64String(bytes); - - var kvp = new FakeConsulGetResponse(base64); - - await context.Response.WriteJsonAsync(new FakeConsulGetResponse[] { kvp }); - } - else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") - { - try - { - var reader = new StreamReader(context.Request.Body); - - var json = reader.ReadToEnd(); - - _config = JsonConvert.DeserializeObject(json); - - var response = JsonConvert.SerializeObject(true); - - await context.Response.WriteAsync(response); - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } - } - else if (context.Request.Path.Value == $"/v1/health/service/{serviceName}") - { - await context.Response.WriteJsonAsync(_consulServices); - } - }); - }) - .Build(); - - _fakeConsulBuilder.Start(); - } - - public class FakeConsulGetResponse - { - public FakeConsulGetResponse(string value) - { - Value = value; - } - - public int CreateIndex => 100; - public int ModifyIndex => 200; - public int LockIndex => 200; - public string Key => "InternalConfiguration"; - public int Flags => 0; - public string Value { get; private set; } - public string Session => "adf4238a-882b-9ddc-4a9d-5b6758e4159e"; - } - - private void GivenThereIsAServiceRunningOn(string url, string basePath, int statusCode, string responseBody) - { - _builder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.UsePathBase(basePath); - - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _builder.Start(); - } - - public void Dispose() - { - _builder?.Dispose(); - _steps.Dispose(); - } - - class FakeCache : IOcelotCache - { - public void Add(string key, FileConfiguration value, TimeSpan ttl, string region) - { - throw new NotImplementedException(); - } - - public void AddAndDelete(string key, FileConfiguration value, TimeSpan ttl, string region) - { - throw new NotImplementedException(); - } - - public FileConfiguration Get(string key, string region) - { - throw new NotImplementedException(); - } - - public void ClearRegion(string region) - { - throw new NotImplementedException(); - } - } - } -} diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 02082c9f..778fe2ef 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -50,7 +50,6 @@ - diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index ac588258..8d6324d3 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -2,12 +2,9 @@ namespace Ocelot.AcceptanceTests { using System; using System.Collections.Generic; - using System.Linq; using System.Net; - using Consul; using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; - using Shouldly; using TestStack.BDDfy; using Xunit; using Newtonsoft.Json; @@ -16,20 +13,13 @@ namespace Ocelot.AcceptanceTests public class ServiceDiscoveryTests : IDisposable { private readonly Steps _steps; - private readonly List _consulServices; private readonly List _eurekaInstances; - private int _counterOne; - private int _counterTwo; - private static readonly object SyncLock = new object(); - private string _downstreamPath; - private string _receivedToken; private readonly ServiceHandler _serviceHandler; public ServiceDiscoveryTests() { _serviceHandler = new ServiceHandler(); _steps = new Steps(); - _consulServices = new List(); _eurekaInstances = new List(); } @@ -80,479 +70,6 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } - [Fact] - public void should_use_consul_service_discovery_and_load_balance_request() - { - var consulPort = 8502; - var serviceName = "product"; - var downstreamServiceOneUrl = "http://localhost:50881"; - var downstreamServiceTwoUrl = "http://localhost:50882"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = 50881, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - var serviceEntryTwo = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = 50882, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - ServiceName = serviceName, - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }, - UseServiceDiscovery = true, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200)) - .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200)) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50)) - .Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50)) - .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(24, 26)) - .BDDfy(); - } - - [Fact] - public void should_handle_request_to_consul_for_downstream_service_and_make_request() - { - const int consulPort = 8505; - const string serviceName = "web"; - const string downstreamServiceOneUrl = "http://localhost:8080"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = 8080, - ID = "web_90_0_2_224_8080", - Tags = new[] {"version-v1"} - }, - }; - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/home", - DownstreamScheme = "http", - UpstreamPathTemplate = "/home", - UpstreamHttpMethod = new List { "Get", "Options" }, - ServiceName = serviceName, - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }, - UseServiceDiscovery = true, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura")) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/home")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_handle_request_to_consul_for_downstream_service_and_make_request_no_re_routes() - { - const int consulPort = 8513; - const string serviceName = "web"; - const int downstreamServicePort = 8087; - var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = downstreamServicePort, - ID = "web_90_0_2_224_8080", - Tags = new[] {"version-v1"} - }, - }; - - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Host = "localhost", - Port = consulPort - }, - DownstreamScheme = "http", - HttpHandlerOptions = new FileHttpHandlerOptions - { - AllowAutoRedirect = true, - UseCookieContainer = true, - UseTracing = false - }, - QoSOptions = new FileQoSOptions - { - TimeoutValue = 100, - DurationOfBreak = 1000, - ExceptionsAllowedBeforeBreaking = 1 - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/something", 200, "Hello from Laura")) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/web/something")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_use_consul_service_discovery_and_load_balance_request_no_re_routes() - { - var consulPort = 8510; - var serviceName = "product"; - var serviceOnePort = 50888; - var serviceTwoPort = 50889; - var downstreamServiceOneUrl = $"http://localhost:{serviceOnePort}"; - var downstreamServiceTwoUrl = $"http://localhost:{serviceTwoPort}"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = serviceOnePort, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - var serviceEntryTwo = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = serviceTwoPort, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - }, - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }, - DownstreamScheme = "http" - } - }; - - this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200)) - .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200)) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes($"/{serviceName}/", 50)) - .Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50)) - .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(24, 26)) - .BDDfy(); - } - - [Fact] - public void should_use_token_to_make_request_to_consul() - { - var token = "abctoken"; - var consulPort = 8515; - var serviceName = "web"; - var downstreamServiceOneUrl = "http://localhost:8081"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = 8081, - ID = "web_90_0_2_224_8080", - Tags = new[] { "version-v1" } - }, - }; - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/home", - DownstreamScheme = "http", - UpstreamPathTemplate = "/home", - UpstreamHttpMethod = new List { "Get", "Options" }, - ServiceName = serviceName, - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }, - UseServiceDiscovery = true, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort, - Token = token - } - } - }; - - this.Given(_ => GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura")) - .And(_ => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) - .And(_ => _steps.GivenThereIsAConfiguration(configuration)) - .And(_ => _steps.GivenOcelotIsRunning()) - .When(_ => _steps.WhenIGetUrlOnTheApiGateway("/home")) - .Then(_ => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(_ => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .And(_ => _receivedToken.ShouldBe(token)) - .BDDfy(); - } - - [Fact] - public void should_send_request_to_service_after_it_becomes_available_in_consul() - { - var consulPort = 8501; - var serviceName = "product"; - var downstreamServiceOneUrl = "http://localhost:50879"; - var downstreamServiceTwoUrl = "http://localhost:50880"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = 50879, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - var serviceEntryTwo = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = 50880, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - ServiceName = serviceName, - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }, - UseServiceDiscovery = true, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200)) - .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200)) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 10)) - .And(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(10)) - .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(4, 6)) - .And(x => WhenIRemoveAService(serviceEntryTwo)) - .And(x => GivenIResetCounters()) - .And(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 10)) - .And(x => ThenOnlyOneServiceHasBeenCalled()) - .And(x => WhenIAddAServiceBackIn(serviceEntryTwo)) - .And(x => GivenIResetCounters()) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 10)) - .Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(10)) - .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(4, 6)) - .BDDfy(); - } - - [Fact] - public void should_handle_request_to_poll_consul_for_downstream_service_and_make_request() - { - const int consulPort = 8518; - const string serviceName = "web"; - const int downstreamServicePort = 8082; - var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = downstreamServicePort, - ID = $"web_90_0_2_224_{downstreamServicePort}", - Tags = new[] {"version-v1"} - }, - }; - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/home", - DownstreamScheme = "http", - UpstreamPathTemplate = "/home", - UpstreamHttpMethod = new List { "Get", "Options" }, - ServiceName = serviceName, - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }, - UseServiceDiscovery = true, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort, - Type = "PollConsul", - PollingInterval = 0 - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura")) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayWaitingForTheResponseToBeOk("/home")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - private void WhenIAddAServiceBackIn(ServiceEntry serviceEntryTwo) - { - _consulServices.Add(serviceEntryTwo); - } - - private void ThenOnlyOneServiceHasBeenCalled() - { - _counterOne.ShouldBe(10); - _counterTwo.ShouldBe(0); - } - - private void WhenIRemoveAService(ServiceEntry serviceEntryTwo) - { - _consulServices.Remove(serviceEntryTwo); - } - - private void GivenIResetCounters() - { - _counterOne = 0; - _counterTwo = 0; - } - - private void ThenBothServicesCalledRealisticAmountOfTimes(int bottom, int top) - { - _counterOne.ShouldBeInRange(bottom, top); - _counterOne.ShouldBeInRange(bottom, top); - } - - private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected) - { - var total = _counterOne + _counterTwo; - total.ShouldBe(expected); - } - - private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries) - { - foreach(var serviceEntry in serviceEntries) - { - _consulServices.Add(serviceEntry); - } - } - private void GivenTheServicesAreRegisteredWithEureka(params IServiceInstance[] serviceInstances) { foreach (var instance in serviceInstances) @@ -631,68 +148,6 @@ namespace Ocelot.AcceptanceTests }); } - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName) - { - _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => - { - if (context.Request.Path.Value == $"/v1/health/service/{serviceName}") - { - if (context.Request.Headers.TryGetValue("X-Consul-Token", out var values)) - { - _receivedToken = values.First(); - } - - await context.Response.WriteJsonAsync(_consulServices); - } - }); - } - - private void GivenProductServiceOneIsRunning(string url, int statusCode) - { - _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => - { - try - { - string response; - lock (SyncLock) - { - _counterOne++; - response = _counterOne.ToString(); - } - - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(response); - } - catch (Exception exception) - { - await context.Response.WriteAsync(exception.StackTrace); - } - }); - } - - private void GivenProductServiceTwoIsRunning(string url, int statusCode) - { - _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => - { - try - { - string response; - lock (SyncLock) - { - _counterTwo++; - response = _counterTwo.ToString(); - } - - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(response); - } - catch (Exception exception) - { - await context.Response.WriteAsync(exception.StackTrace); - } - }); - } - private void GivenEurekaProductServiceOneIsRunning(string url) { _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => @@ -709,25 +164,6 @@ namespace Ocelot.AcceptanceTests }); } - private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) - { - _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => - { - _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - - if (_downstreamPath != basePath) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - }); - } - public void Dispose() { _serviceHandler?.Dispose(); diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index c67c953b..7841083b 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -422,34 +422,6 @@ header.First().ShouldBe(value); } - public void GivenOcelotIsRunningUsingConsulToStoreConfig() - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddOcelot().AddStoreOcelotConfigurationInConsul(); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - /// /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. /// @@ -576,24 +548,6 @@ _response = _ocelotClient.GetAsync(url).Result; } - public void WhenIGetUrlOnTheApiGatewayWaitingForTheResponseToBeOk(string url) - { - var result = WaitFor(2000).Until(() => { - try - { - _response = _ocelotClient.GetAsync(url).Result; - _response.EnsureSuccessStatusCode(); - return true; - } - catch(Exception) - { - return false; - } - }); - - result.ShouldBeTrue(); - } - public void WhenIGetUrlOnTheApiGateway(string url, string cookie, string value) { var request = _ocelotServer.CreateRequest(url); diff --git a/test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs b/test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs deleted file mode 100644 index f7529808..00000000 --- a/test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs +++ /dev/null @@ -1,151 +0,0 @@ -namespace Ocelot.AcceptanceTests -{ - using System; - using System.Collections.Generic; - using System.Net; - using Consul; - using Microsoft.AspNetCore.Http; - using Ocelot.Configuration.File; - using TestStack.BDDfy; - using Xunit; - - public class TwoDownstreamServicesTests : IDisposable - { - private readonly Steps _steps; - private readonly List _serviceEntries; - private string _downstreamPathOne; - private string _downstreamPathTwo; - private readonly ServiceHandler _serviceHandler; - - public TwoDownstreamServicesTests() - { - _serviceHandler = new ServiceHandler(); - _steps = new Steps(); - _serviceEntries = new List(); - } - - [Fact] - public void should_fix_issue_194() - { - var consulPort = 8503; - var downstreamServiceOneUrl = "http://localhost:8362"; - var downstreamServiceTwoUrl = "http://localhost:8330"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/user/{user}", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 8362, - } - }, - UpstreamPathTemplate = "/api/user/{user}", - UpstreamHttpMethod = new List { "Get" }, - }, - new FileReRoute - { - DownstreamPathTemplate = "/api/product/{product}", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 8330, - } - }, - UpstreamPathTemplate = "/api/product/{product}", - UpstreamHttpMethod = new List { "Get" }, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, "/api/user/info", 200, "user")) - .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, "/api/product/info", 200, "product")) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/user/info?id=1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("user")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/product/info?id=1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("product")) - .BDDfy(); - } - - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) - { - _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => - { - if (context.Request.Path.Value == "/v1/health/service/product") - { - await context.Response.WriteJsonAsync(_serviceEntries); - } - }); - } - - private void GivenProductServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) - { - _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => - { - _downstreamPathOne = !string.IsNullOrEmpty(context.Request.PathBase.Value) - ? context.Request.PathBase.Value - : context.Request.Path.Value; - - if (_downstreamPathOne != basePath) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - }); - } - - private void GivenProductServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) - { - _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => - { - _downstreamPathTwo = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - - if (_downstreamPathTwo != basePath) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - }); - } - - public void Dispose() - { - _serviceHandler?.Dispose(); - _steps.Dispose(); - } - } -} diff --git a/test/Ocelot.AcceptanceTests/WebSocketTests.cs b/test/Ocelot.AcceptanceTests/WebSocketTests.cs index 3036d07a..77661613 100644 --- a/test/Ocelot.AcceptanceTests/WebSocketTests.cs +++ b/test/Ocelot.AcceptanceTests/WebSocketTests.cs @@ -6,8 +6,6 @@ namespace Ocelot.AcceptanceTests using System.Text; using System.Threading; using System.Threading.Tasks; - using Consul; - using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; using Shouldly; using TestStack.BDDfy; @@ -17,7 +15,6 @@ namespace Ocelot.AcceptanceTests { private readonly List _secondRecieved; private readonly List _firstRecieved; - private readonly List _serviceEntries; private readonly Steps _steps; private readonly ServiceHandler _serviceHandler; @@ -27,7 +24,6 @@ namespace Ocelot.AcceptanceTests _steps = new Steps(); _firstRecieved = new List(); _secondRecieved = new List(); - _serviceEntries = new List(); } [Fact] @@ -109,77 +105,6 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } - [Fact] - public void should_proxy_websocket_input_to_downstream_service_and_use_service_discovery_and_load_balancer() - { - var downstreamPort = 5007; - var downstreamHost = "localhost"; - - var secondDownstreamPort = 5008; - var secondDownstreamHost = "localhost"; - - var serviceName = "websockets"; - var consulPort = 8509; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = downstreamHost, - Port = downstreamPort, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - var serviceEntryTwo = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = secondDownstreamHost, - Port = secondDownstreamPort, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - var config = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/", - DownstreamPathTemplate = "/ws", - DownstreamScheme = "ws", - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "RoundRobin" }, - ServiceName = serviceName, - UseServiceDiscovery = true - } - }, - GlobalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Host = "localhost", - Port = consulPort, - Type = "consul" - } - } - }; - - this.Given(_ => _steps.GivenThereIsAConfiguration(config)) - .And(_ => _steps.StartFakeOcelotWithWebSockets()) - .And(_ => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) - .And(_ => StartFakeDownstreamService($"http://{downstreamHost}:{downstreamPort}", "/ws")) - .And(_ => StartSecondFakeDownstreamService($"http://{secondDownstreamHost}:{secondDownstreamPort}", "/ws")) - .When(_ => WhenIStartTheClients()) - .Then(_ => ThenBothDownstreamServicesAreCalled()) - .BDDfy(); - } - private void ThenBothDownstreamServicesAreCalled() { _firstRecieved.Count.ShouldBe(10); @@ -195,25 +120,6 @@ namespace Ocelot.AcceptanceTests }); } - private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries) - { - foreach (var serviceEntry in serviceEntries) - { - _serviceEntries.Add(serviceEntry); - } - } - - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName) - { - _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => - { - if (context.Request.Path.Value == $"/v1/health/service/{serviceName}") - { - await context.Response.WriteJsonAsync(_serviceEntries); - } - }); - } - private async Task WhenIStartTheClients() { var firstClient = StartClient("ws://localhost:5000/"); diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj index 19bf854f..62e7371c 100644 --- a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj @@ -42,7 +42,6 @@ - diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj index d9ddff9c..5c3c5cfe 100644 --- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj +++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj @@ -36,7 +36,6 @@ - all diff --git a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationRepositoryTests.cs deleted file mode 100644 index d26a69fc..00000000 --- a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationRepositoryTests.cs +++ /dev/null @@ -1,263 +0,0 @@ -namespace Ocelot.UnitTests.Configuration -{ - using Xunit; - using TestStack.BDDfy; - using Shouldly; - using Ocelot.Configuration.Repository; - using Moq; - using Ocelot.Infrastructure.Consul; - using Ocelot.Logging; - using Ocelot.Configuration.File; - using Ocelot.Cache; - using System; - using System.Collections.Generic; - using Ocelot.Responses; - using System.Threading.Tasks; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.ServiceDiscovery.Configuration; - using Consul; - using Newtonsoft.Json; - using System.Text; - using System.Threading; - using System.Linq; - - public class ConsulFileConfigurationRepositoryTests - { - private ConsulFileConfigurationRepository _repo; - private Mock> _cache; - private Mock _internalRepo; - private Mock _factory; - private Mock _loggerFactory; - private Mock _client; - private Mock _kvEndpoint; - private FileConfiguration _fileConfiguration; - private Response _setResult; - private Response _getResult; - - public ConsulFileConfigurationRepositoryTests() - { - _cache = new Mock>(); - _internalRepo = new Mock(); - _loggerFactory = new Mock(); - - _factory = new Mock(); - _client = new Mock(); - _kvEndpoint = new Mock(); - - _client - .Setup(x => x.KV) - .Returns(_kvEndpoint.Object); - - _factory - .Setup(x => x.Get(It.IsAny())) - .Returns(_client.Object); - - _internalRepo - .Setup(x => x.Get()) - .Returns(new OkResponse(new InternalConfiguration(new List(), "", new ServiceProviderConfigurationBuilder().Build(), "", It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))); - - _repo = new ConsulFileConfigurationRepository(_cache.Object, _internalRepo.Object, _factory.Object, _loggerFactory.Object); - } - - [Fact] - public void should_set_config() - { - var config = FakeFileConfiguration(); - - this.Given(_ => GivenIHaveAConfiguration(config)) - .And(_ => GivenWritingToConsulSucceeds()) - .When(_ => WhenISetTheConfiguration()) - .Then(_ => ThenTheConfigurationIsStoredAs(config)) - .BDDfy(); - } - - [Fact] - public void should_get_config() - { - var config = FakeFileConfiguration(); - - this.Given(_ => GivenIHaveAConfiguration(config)) - .And(_ => GivenFetchFromConsulSucceeds()) - .When(_ => WhenIGetTheConfiguration()) - .Then(_ => ThenTheConfigurationIs(config)) - .BDDfy(); - } - - [Fact] - public void should_get_null_config() - { - this.Given(_ => GivenFetchFromConsulReturnsNull()) - .When(_ => WhenIGetTheConfiguration()) - .Then(_ => ThenTheConfigurationIsNull()) - .BDDfy(); - } - - [Fact] - public void should_get_config_from_cache() - { - var config = FakeFileConfiguration(); - - this.Given(_ => GivenIHaveAConfiguration(config)) - .And(_ => GivenFetchFromCacheSucceeds()) - .When(_ => WhenIGetTheConfiguration()) - .Then(_ => ThenTheConfigurationIs(config)) - .BDDfy(); - } - - [Fact] - public void should_set_config_key() - { - var config = FakeFileConfiguration(); - - this.Given(_ => GivenIHaveAConfiguration(config)) - .And(_ => GivenTheConfigKeyComesFromFileConfig("Tom")) - .And(_ => GivenFetchFromConsulSucceeds()) - .When(_ => WhenIGetTheConfiguration()) - .And(_ => ThenTheConfigKeyIs("Tom")) - .BDDfy(); - } - - [Fact] - public void should_set_default_config_key() - { - var config = FakeFileConfiguration(); - - this.Given(_ => GivenIHaveAConfiguration(config)) - .And(_ => GivenFetchFromConsulSucceeds()) - .When(_ => WhenIGetTheConfiguration()) - .And(_ => ThenTheConfigKeyIs("InternalConfiguration")) - .BDDfy(); - } - - private void ThenTheConfigKeyIs(string expected) - { - _kvEndpoint - .Verify(x => x.Get(expected, It.IsAny()), Times.Once); - } - - private void GivenTheConfigKeyComesFromFileConfig(string key) - { - _internalRepo - .Setup(x => x.Get()) - .Returns(new OkResponse(new InternalConfiguration(new List(), "", - new ServiceProviderConfigurationBuilder().WithConfigurationKey(key).Build(), "", - new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(), - new HttpHandlerOptionsBuilder().Build()))); - - _repo = new ConsulFileConfigurationRepository(_cache.Object, _internalRepo.Object, _factory.Object, _loggerFactory.Object); - } - - private void ThenTheConfigurationIsNull() - { - _getResult.Data.ShouldBeNull(); - } - - private void ThenTheConfigurationIs(FileConfiguration config) - { - var expected = JsonConvert.SerializeObject(config, Formatting.Indented); - var result = JsonConvert.SerializeObject(_getResult.Data, Formatting.Indented); - result.ShouldBe(expected); - } - - private async Task WhenIGetTheConfiguration() - { - _getResult = await _repo.Get(); - } - - private void GivenWritingToConsulSucceeds() - { - var response = new WriteResult(); - response.Response = true; - - _kvEndpoint - .Setup(x => x.Put(It.IsAny(), It.IsAny())).ReturnsAsync(response); - } - - private void GivenFetchFromCacheSucceeds() - { - _cache.Setup(x => x.Get(It.IsAny(), It.IsAny())).Returns(_fileConfiguration); - } - - private void GivenFetchFromConsulReturnsNull() - { - QueryResult result = new QueryResult(); - - _kvEndpoint - .Setup(x => x.Get(It.IsAny(), It.IsAny())) - .ReturnsAsync(result); - } - - private void GivenFetchFromConsulSucceeds() - { - var json = JsonConvert.SerializeObject(_fileConfiguration, Formatting.Indented); - - var bytes = Encoding.UTF8.GetBytes(json); - - var kvp = new KVPair("OcelotConfiguration"); - kvp.Value = bytes; - - var query = new QueryResult(); - query.Response = kvp; - - _kvEndpoint - .Setup(x => x.Get(It.IsAny(), It.IsAny())) - .ReturnsAsync(query); - } - - private void ThenTheConfigurationIsStoredAs(FileConfiguration config) - { - var json = JsonConvert.SerializeObject(config, Formatting.Indented); - - var bytes = Encoding.UTF8.GetBytes(json); - - _kvEndpoint - .Verify(x => x.Put(It.Is(k => k.Value.SequenceEqual(bytes)), It.IsAny()), Times.Once); - } - - private async Task WhenISetTheConfiguration() - { - _setResult = await _repo.Set(_fileConfiguration); - } - - private void GivenIHaveAConfiguration(FileConfiguration config) - { - _fileConfiguration = config; - } - - private FileConfiguration FakeFileConfiguration() - { - var reRoutes = new List - { - new FileReRoute - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "123.12.12.12", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/asdfs/test/{test}" - } - }; - - var globalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Port = 198, - Host = "blah" - } - }; - - return new FileConfiguration - { - GlobalConfiguration = globalConfiguration, - ReRoutes = reRoutes - }; - } - } -} diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 9b80757c..419fd2e0 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -1,28 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Internal; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Cache; -using Ocelot.Configuration; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Setter; -using Ocelot.DependencyInjection; -using Ocelot.Requester; -using Ocelot.UnitTests.Requester; -using Shouldly; -using IdentityServer4.AccessTokenValidation; -using TestStack.BDDfy; -using Xunit; -using static Ocelot.UnitTests.Middleware.UserDefinedResponseAggregatorTests; -using Ocelot.Middleware.Multiplexer; - namespace Ocelot.UnitTests.DependencyInjection { - using Butterfly; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Hosting.Internal; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.Configuration.Setter; + using Ocelot.DependencyInjection; + using Ocelot.Requester; + using Ocelot.UnitTests.Requester; + using Shouldly; + using IdentityServer4.AccessTokenValidation; + using TestStack.BDDfy; + using Xunit; + using static Ocelot.UnitTests.Middleware.UserDefinedResponseAggregatorTests; + using Ocelot.Middleware.Multiplexer; public class OcelotBuilderTests { @@ -80,15 +75,6 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } - [Fact] - public void should_set_up_consul() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpConsul()) - .Then(x => ThenAnExceptionIsntThrown()) - .BDDfy(); - } - [Fact] public void should_set_up_rafty() { @@ -271,18 +257,6 @@ namespace Ocelot.UnitTests.DependencyInjection first.ShouldBe(second); } - private void WhenISetUpConsul() - { - try - { - _ocelotBuilder.AddStoreOcelotConfigurationInConsul(); - } - catch (Exception e) - { - _ex = e; - } - } - private void WhenISetUpRafty() { try diff --git a/test/Ocelot.UnitTests/Infrastructure/InMemoryBusTests.cs b/test/Ocelot.UnitTests/Infrastructure/InMemoryBusTests.cs index 35849dbf..ed8f3e00 100644 --- a/test/Ocelot.UnitTests/Infrastructure/InMemoryBusTests.cs +++ b/test/Ocelot.UnitTests/Infrastructure/InMemoryBusTests.cs @@ -22,7 +22,7 @@ namespace Ocelot.UnitTests.Infrastructure called = true; }); _bus.Publish(new object(), 1); - await Task.Delay(10); + await Task.Delay(100); called.ShouldBeTrue(); } diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs deleted file mode 100644 index 4bc05efb..00000000 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs +++ /dev/null @@ -1,297 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Consul; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Moq; -using Ocelot.Infrastructure.Consul; -using Ocelot.Logging; -using Ocelot.ServiceDiscovery.Configuration; -using Ocelot.ServiceDiscovery.Providers; -using Ocelot.Values; -using Xunit; -using TestStack.BDDfy; -using Shouldly; - -namespace Ocelot.UnitTests.ServiceDiscovery -{ - public class ConsulServiceDiscoveryProviderTests : IDisposable - { - private IWebHost _fakeConsulBuilder; - private readonly List _serviceEntries; - private ConsulServiceDiscoveryProvider _provider; - private readonly string _serviceName; - private readonly int _port; - private readonly string _consulHost; - private readonly string _fakeConsulServiceDiscoveryUrl; - private List _services; - private readonly Mock _factory; - private readonly Mock _logger; - private string _receivedToken; - private readonly IConsulClientFactory _clientFactory; - - public ConsulServiceDiscoveryProviderTests() - { - _serviceName = "test"; - _port = 8500; - _consulHost = "localhost"; - _fakeConsulServiceDiscoveryUrl = $"http://{_consulHost}:{_port}"; - _serviceEntries = new List(); - - _factory = new Mock(); - _clientFactory = new ConsulClientFactory(); - _logger = new Mock(); - _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - - var config = new ConsulRegistryConfiguration(_consulHost, _port, _serviceName, null); - _provider = new ConsulServiceDiscoveryProvider(config, _factory.Object, _clientFactory); - } - - [Fact] - public void should_return_service_from_consul() - { - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = _serviceName, - Address = "localhost", - Port = 50881, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - this.Given(x =>GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName)) - .And(x => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) - .When(x => WhenIGetTheServices()) - .Then(x => ThenTheCountIs(1)) - .BDDfy(); - } - - [Fact] - public void should_use_token() - { - var token = "test token"; - var config = new ConsulRegistryConfiguration(_consulHost, _port, _serviceName, token); - _provider = new ConsulServiceDiscoveryProvider(config, _factory.Object, _clientFactory); - - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = _serviceName, - Address = "localhost", - Port = 50881, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - this.Given(_ => GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName)) - .And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) - .When(_ => WhenIGetTheServices()) - .Then(_ => ThenTheCountIs(1)) - .And(_ => _receivedToken.ShouldBe(token)) - .BDDfy(); - } - - [Fact] - public void should_not_return_services_with_invalid_address() - { - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = _serviceName, - Address = "http://localhost", - Port = 50881, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - var serviceEntryTwo = new ServiceEntry() - { - Service = new AgentService() - { - Service = _serviceName, - Address = "http://localhost", - Port = 50888, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName)) - .And(x => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) - .When(x => WhenIGetTheServices()) - .Then(x => ThenTheCountIs(0)) - .And(x => ThenTheLoggerHasBeenCalledCorrectlyForInvalidAddress()) - .BDDfy(); - } - - [Fact] - public void should_not_return_services_with_empty_address() - { - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = _serviceName, - Address = "", - Port = 50881, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - var serviceEntryTwo = new ServiceEntry() - { - Service = new AgentService() - { - Service = _serviceName, - Address = null, - Port = 50888, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName)) - .And(x => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) - .When(x => WhenIGetTheServices()) - .Then(x => ThenTheCountIs(0)) - .And(x => ThenTheLoggerHasBeenCalledCorrectlyForEmptyAddress()) - .BDDfy(); - } - - [Fact] - public void should_not_return_services_with_invalid_port() - { - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = _serviceName, - Address = "localhost", - Port = -1, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - var serviceEntryTwo = new ServiceEntry() - { - Service = new AgentService() - { - Service = _serviceName, - Address = "localhost", - Port = 0, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName)) - .And(x => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) - .When(x => WhenIGetTheServices()) - .Then(x => ThenTheCountIs(0)) - .And(x => ThenTheLoggerHasBeenCalledCorrectlyForInvalidPorts()) - .BDDfy(); - } - - private void ThenTheLoggerHasBeenCalledCorrectlyForInvalidAddress() - { - _logger.Verify( - x => x.LogWarning( - "Unable to use service Address: http://localhost and Port: 50881 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"), - Times.Once); - - _logger.Verify( - x => x.LogWarning( - "Unable to use service Address: http://localhost and Port: 50888 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"), - Times.Once); - } - - private void ThenTheLoggerHasBeenCalledCorrectlyForEmptyAddress() - { - _logger.Verify( - x => x.LogWarning( - "Unable to use service Address: and Port: 50881 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"), - Times.Once); - - _logger.Verify( - x => x.LogWarning( - "Unable to use service Address: and Port: 50888 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"), - Times.Once); - } - - private void ThenTheLoggerHasBeenCalledCorrectlyForInvalidPorts() - { - _logger.Verify( - x => x.LogWarning( - "Unable to use service Address: localhost and Port: -1 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"), - Times.Once); - - _logger.Verify( - x => x.LogWarning( - "Unable to use service Address: localhost and Port: 0 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"), - Times.Once); - } - - private void ThenTheCountIs(int count) - { - _services.Count.ShouldBe(count); - } - - private void WhenIGetTheServices() - { - _services = _provider.Get().GetAwaiter().GetResult(); - } - - private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries) - { - foreach (var serviceEntry in serviceEntries) - { - _serviceEntries.Add(serviceEntry); - } - } - - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName) - { - _fakeConsulBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - if (context.Request.Path.Value == $"/v1/health/service/{serviceName}") - { - if (context.Request.Headers.TryGetValue("X-Consul-Token", out var values)) - { - _receivedToken = values.First(); - } - - await context.Response.WriteJsonAsync(_serviceEntries); - } - }); - }) - .Build(); - - _fakeConsulBuilder.Start(); - } - - public void Dispose() - { - _fakeConsulBuilder?.Dispose(); - } - } -} diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/PollingConsulServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/PollingConsulServiceDiscoveryProviderTests.cs deleted file mode 100644 index 86d523e2..00000000 --- a/test/Ocelot.UnitTests/ServiceDiscovery/PollingConsulServiceDiscoveryProviderTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -namespace Ocelot.UnitTests.ServiceDiscovery -{ - using System; - using System.Collections.Generic; - using Moq; - using Ocelot.Logging; - using Ocelot.ServiceDiscovery.Providers; - using Ocelot.Values; - using Xunit; - using TestStack.BDDfy; - using Shouldly; - using static Ocelot.Infrastructure.Wait; - - public class PollingConsulServiceDiscoveryProviderTests - { - private readonly int _delay; - private PollingConsulServiceDiscoveryProvider _provider; - private readonly List _services; - private readonly Mock _factory; - private readonly Mock _logger; - private readonly Mock _consulServiceDiscoveryProvider; - private List _result; - - public PollingConsulServiceDiscoveryProviderTests() - { - _services = new List(); - _delay = 1; - _factory = new Mock(); - _logger = new Mock(); - _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _consulServiceDiscoveryProvider = new Mock(); - } - - [Fact] - public void should_return_service_from_consul() - { - var service = new Service("", new ServiceHostAndPort("", 0), "", "", new List()); - - this.Given(x => GivenConsulReturns(service)) - .When(x => WhenIGetTheServices(1)) - .Then(x => ThenTheCountIs(1)) - .BDDfy(); - } - - private void GivenConsulReturns(Service service) - { - _services.Add(service); - _consulServiceDiscoveryProvider.Setup(x => x.Get()).ReturnsAsync(_services); - } - - private void ThenTheCountIs(int count) - { - _result.Count.ShouldBe(count); - } - - private void WhenIGetTheServices(int expected) - { - _provider = new PollingConsulServiceDiscoveryProvider(_delay, "", _factory.Object, _consulServiceDiscoveryProvider.Object); - - var result = WaitFor(3000).Until(() => { - try - { - _result = _provider.Get().GetAwaiter().GetResult(); - if(_result.Count == expected) - { - return true; - } - - return false; - } - catch(Exception) - { - return false; - } - }); - - result.ShouldBeTrue(); - } - } -} diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceFabricServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceFabricServiceDiscoveryProviderTests.cs index eabe72d7..a9020e3e 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceFabricServiceDiscoveryProviderTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceFabricServiceDiscoveryProviderTests.cs @@ -1,23 +1,12 @@ -using Ocelot.ServiceDiscovery.Configuration; -using Ocelot.ServiceDiscovery.Providers; - -namespace Ocelot.UnitTests.ServiceDiscovery +namespace Ocelot.UnitTests.ServiceDiscovery { - using System; using System.Collections.Generic; - using System.IO; - using System.Text; - using Consul; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Logging; - using Ocelot.ServiceDiscovery; using Ocelot.Values; using Xunit; using TestStack.BDDfy; using Shouldly; + using Ocelot.ServiceDiscovery.Configuration; + using Ocelot.ServiceDiscovery.Providers; public class ServiceFabricServiceDiscoveryProviderTests { diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index 9ef05f3c..edf18cdd 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -1,39 +1,41 @@ -using System; -using System.Collections.Generic; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Infrastructure.Consul; -using Ocelot.Logging; -using Ocelot.ServiceDiscovery; -using Ocelot.ServiceDiscovery.Providers; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - namespace Ocelot.UnitTests.ServiceDiscovery { - using Pivotal.Discovery.Client; + using System; + using System.Threading.Tasks; + using Microsoft.Extensions.DependencyInjection; using Steeltoe.Common.Discovery; + using Values; + using System.Collections.Generic; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.Logging; + using Ocelot.ServiceDiscovery; + using Ocelot.ServiceDiscovery.Providers; + using Shouldly; + using TestStack.BDDfy; + using Xunit; public class ServiceProviderFactoryTests { private ServiceProviderConfiguration _serviceConfig; private IServiceDiscoveryProvider _result; - private readonly ServiceDiscoveryProviderFactory _factory; + private ServiceDiscoveryProviderFactory _factory; private DownstreamReRoute _reRoute; private Mock _loggerFactory; private Mock _discoveryClient; private Mock _logger; + private IServiceProvider _provider; + private IServiceCollection _collection; public ServiceProviderFactoryTests() { _loggerFactory = new Mock(); _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _discoveryClient = new Mock(); - var consulClient = new Mock(); - _factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, consulClient.Object, _discoveryClient.Object); + _collection = new ServiceCollection(); + _provider = _collection.BuildServiceProvider(); + _factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, _discoveryClient.Object, _provider); } [Fact] @@ -72,7 +74,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery } [Fact] - public void should_return_consul_service_provider() + public void should_call_delegate() { var reRoute = new DownstreamReRouteBuilder() .WithServiceName("product") @@ -83,27 +85,9 @@ namespace Ocelot.UnitTests.ServiceDiscovery .Build(); this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) + .And(x => GivenAFakeDelegate()) .When(x => x.WhenIGetTheServiceProvider()) - .Then(x => x.ThenTheServiceProviderIs()) - .BDDfy(); - } - - [Fact] - public void should_return_polling_consul_service_provider() - { - var reRoute = new DownstreamReRouteBuilder() - .WithServiceName("product") - .WithUseServiceDiscovery(true) - .Build(); - - var serviceConfig = new ServiceProviderConfigurationBuilder() - .WithType("PollConsul") - .WithPollingInterval(100000) - .Build(); - - this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) - .When(x => x.WhenIGetTheServiceProvider()) - .Then(x => x.ThenTheServiceProviderIs()) + .Then(x => x.ThenTheDelegateIsCalled()) .BDDfy(); } @@ -143,6 +127,27 @@ namespace Ocelot.UnitTests.ServiceDiscovery .BDDfy(); } + private void GivenAFakeDelegate() + { + ServiceDiscoveryFinderDelegate fake = (provider, config, name) => new Fake(); + _collection.AddSingleton(fake); + _provider = _collection.BuildServiceProvider(); + _factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, _discoveryClient.Object, _provider); + } + + class Fake : IServiceDiscoveryProvider + { + public Task> Get() + { + return null; + } + } + + private void ThenTheDelegateIsCalled() + { + _result.GetType().Name.ShouldBe("Fake"); + } + private void ThenTheFollowingServicesAreReturned(List downstreamAddresses) { var result = (ConfigurationServiceProvider)_result; From 87348e5d1bbfcc5239def16dbb3c48c4a3d2888a Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 12 Aug 2018 17:28:41 +0530 Subject: [PATCH 25/50] Feature/removed consul and its deps to other package (#539) * #529 removed consul deps and introduced delegate to find service discovery provider * +semver: breaking moved consul configuration to package..introduced mechanism for packages to configure Ocelot pipeline --- docs/features/configuration.rst | 9 ++- docs/features/servicediscovery.rst | 11 +++ .../Repository/FileConfigurationPoller.cs | 4 +- .../UnableToSetConfigInConsulError.cs | 12 ---- src/Ocelot/Errors/OcelotErrorCode.cs | 1 - .../OcelotMiddlewareConfigurationDelegate.cs | 7 ++ .../Middleware/OcelotMiddlewareExtensions.cs | 67 ++----------------- .../ServiceDiscoveryFinderDelegate.cs | 8 +++ .../ServiceDiscoveryProviderFactory.cs | 16 +++-- .../FileConfigurationPollerTests.cs | 12 ++-- .../ErrorsToHttpStatusCodeMapperTests.cs | 3 +- 11 files changed, 58 insertions(+), 92 deletions(-) delete mode 100644 src/Ocelot/Configuration/Repository/UnableToSetConfigInConsulError.cs create mode 100644 src/Ocelot/Middleware/OcelotMiddlewareConfigurationDelegate.cs create mode 100644 src/Ocelot/ServiceDiscovery/ServiceDiscoveryFinderDelegate.cs diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 11c69a0d..bad1e6c5 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -121,13 +121,18 @@ At the moment there is no validation at this stage it only happens when Ocelot v Store configuration in consul ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in consul KV store. +The first thing you need to do is install the NuGet package that provides Consul support in Ocelot. + +``Install-Package Ocelot.Provider.Consul`` + +Then you add the following when you register your services Ocelot will attempt to store and retrieve its configuration in consul KV store. .. code-block:: csharp services .AddOcelot() - .AddStoreOcelotConfigurationInConsul(); + .AddConsul() + .AddConfigStoredInConsul(); You also need to add the following to your ocelot.json. This is how Ocelot finds your Consul agent and interacts to load and store the configuration from Consul. diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index d115b9c2..3a88fb47 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -11,6 +11,17 @@ you specify a ServiceName for at ReRoute level. Consul ^^^^^^ +The first thing you need to do is install the NuGet package that provides Consul support in Ocelot. + +``Install-Package Ocelot.Provider.Consul`` + +Then add the following to your ConfigureServices method. + +.. code-block:: csharp + + s.AddOcelot() + .AddConsul(); + The following is required in the GlobalConfiguration. The Provider is required and if you do not specify a host and port the Consul default will be used. diff --git a/src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs b/src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs index 74bbf002..84fc7918 100644 --- a/src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs +++ b/src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs @@ -67,7 +67,7 @@ namespace Ocelot.Configuration.Repository private async Task Poll() { - _logger.LogInformation("Started polling consul"); + _logger.LogInformation("Started polling"); var fileConfig = await _repo.Get(); @@ -91,7 +91,7 @@ namespace Ocelot.Configuration.Repository _previousAsJson = asJson; } - _logger.LogInformation("Finished polling consul"); + _logger.LogInformation("Finished polling"); } /// diff --git a/src/Ocelot/Configuration/Repository/UnableToSetConfigInConsulError.cs b/src/Ocelot/Configuration/Repository/UnableToSetConfigInConsulError.cs deleted file mode 100644 index 52b5c372..00000000 --- a/src/Ocelot/Configuration/Repository/UnableToSetConfigInConsulError.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.Configuration.Repository -{ - public class UnableToSetConfigInConsulError : Error - { - public UnableToSetConfigInConsulError(string message) - : base(message, OcelotErrorCode.UnableToSetConfigInConsulError) - { - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index 8109bb52..7a2dd030 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -29,7 +29,6 @@ UnableToFindLoadBalancerError, RequestTimedOutError, UnableToFindQoSProviderError, - UnableToSetConfigInConsulError, UnmappableRequestError, RateLimitOptionsError, PathTemplateDoesntStartWithForwardSlash, diff --git a/src/Ocelot/Middleware/OcelotMiddlewareConfigurationDelegate.cs b/src/Ocelot/Middleware/OcelotMiddlewareConfigurationDelegate.cs new file mode 100644 index 00000000..f0dbb5c1 --- /dev/null +++ b/src/Ocelot/Middleware/OcelotMiddlewareConfigurationDelegate.cs @@ -0,0 +1,7 @@ +namespace Ocelot.Middleware +{ + using System.Threading.Tasks; + using Microsoft.AspNetCore.Builder; + + public delegate Task OcelotMiddlewareConfigurationDelegate(IApplicationBuilder builder); +} diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 6f76ebf3..f6bb2905 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -134,16 +134,17 @@ internalConfigRepo.AddOrReplace(newInternalConfig.Data); }); - var fileConfigRepo = builder.ApplicationServices.GetService(); - var adminPath = builder.ApplicationServices.GetService(); - if (UsingConsul(fileConfigRepo)) + var configurations = builder.ApplicationServices.GetServices(); + + // Todo - this has just been added for consul so far...will there be an ordering problem in the future? Should refactor all config into this pattern? + foreach (var configuration in configurations) { - //Lots of jazz happens in here..check it out if you are using consul to store your config. - await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo); + await configuration(builder); } - else if(AdministrationApiInUse(adminPath)) + + if(AdministrationApiInUse(adminPath)) { //We have to make sure the file config is set for the ocelot.env.json and ocelot.json so that if we pull it from the //admin api it works...boy this is getting a spit spags boll. @@ -160,49 +161,6 @@ return adminPath != null; } - private static async Task SetFileConfigInConsul(IApplicationBuilder builder, - IFileConfigurationRepository fileConfigRepo, IOptionsMonitor fileConfig, - IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo) - { - // get the config from consul. - var fileConfigFromConsul = await fileConfigRepo.Get(); - - if (IsError(fileConfigFromConsul)) - { - ThrowToStopOcelotStarting(fileConfigFromConsul); - } - else if (ConfigNotStoredInConsul(fileConfigFromConsul)) - { - //there was no config in consul set the file in config in consul - await fileConfigRepo.Set(fileConfig.CurrentValue); - } - else - { - // create the internal config from consul data - var internalConfig = await internalConfigCreator.Create(fileConfigFromConsul.Data); - - if (IsError(internalConfig)) - { - ThrowToStopOcelotStarting(internalConfig); - } - else - { - // add the internal config to the internal repo - var response = internalConfigRepo.AddOrReplace(internalConfig.Data); - - if (IsError(response)) - { - ThrowToStopOcelotStarting(response); - } - } - - if (IsError(internalConfig)) - { - ThrowToStopOcelotStarting(internalConfig); - } - } - } - private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptionsMonitor fileConfig) { var response = await fileConfigSetter.Set(fileConfig.CurrentValue); @@ -213,11 +171,6 @@ } } - private static bool ConfigNotStoredInConsul(Responses.Response fileConfigFromConsul) - { - return fileConfigFromConsul.Data == null; - } - private static bool IsError(Response response) { return response == null || response.IsError; @@ -240,12 +193,6 @@ throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}"); } - private static bool UsingConsul(IFileConfigurationRepository fileConfigRepo) - { - //todo - remove coupling by string - return fileConfigRepo.GetType().Name == "ConsulFileConfigurationRepository"; - } - private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration) { if (!string.IsNullOrEmpty(configuration.AdministrationPath)) diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryFinderDelegate.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryFinderDelegate.cs new file mode 100644 index 00000000..e3b0f20c --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryFinderDelegate.cs @@ -0,0 +1,8 @@ +namespace Ocelot.ServiceDiscovery +{ + using System; + using Ocelot.Configuration; + using Providers; + + public delegate IServiceDiscoveryProvider ServiceDiscoveryFinderDelegate(IServiceProvider provider, ServiceProviderConfiguration config, string key); +} diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index 9d3aef25..a482d961 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -60,14 +60,16 @@ namespace Ocelot.ServiceDiscovery return new EurekaServiceDiscoveryProvider(key, _eurekaClient); } - // Todo - dont just hardcode this...only expect Consul at the momement so works. - var finderDelegate = _delegates.FirstOrDefault(); + foreach (var serviceDiscoveryFinderDelegate in _delegates) + { + var provider = serviceDiscoveryFinderDelegate?.Invoke(_provider, config, key); + if (provider != null) + { + return provider; + } + } - var provider = finderDelegate?.Invoke(_provider, config, key); - - return provider; + return null; } } - - public delegate IServiceDiscoveryProvider ServiceDiscoveryFinderDelegate(IServiceProvider provider, ServiceProviderConfiguration config, string key); } diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationPollerTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationPollerTests.cs index 18e8ddb6..4c6a6016 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationPollerTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationPollerTests.cs @@ -71,7 +71,7 @@ namespace Ocelot.UnitTests.Configuration } }; - this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 0)) + this.Given(x => WhenTheConfigIsChanged(newConfig, 0)) .Then(x => ThenTheSetterIsCalledAtLeast(newConfig, 1)) .BDDfy(); } @@ -96,13 +96,13 @@ namespace Ocelot.UnitTests.Configuration } }; - this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 10)) + this.Given(x => WhenTheConfigIsChanged(newConfig, 10)) .Then(x => ThenTheSetterIsCalled(newConfig, 1)) .BDDfy(); } [Fact] - public void should_do_nothing_if_call_to_consul_fails() + public void should_do_nothing_if_call_to_provider_fails() { var newConfig = new FileConfiguration { @@ -121,19 +121,19 @@ namespace Ocelot.UnitTests.Configuration } }; - this.Given(x => WhenConsulErrors()) + this.Given(x => WhenProviderErrors()) .Then(x => ThenTheSetterIsCalled(newConfig, 0)) .BDDfy(); } - private void WhenConsulErrors() + private void WhenProviderErrors() { _repo .Setup(x => x.Get()) .ReturnsAsync(new ErrorResponse(new AnyError())); } - private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig, int delay) + private void WhenTheConfigIsChanged(FileConfiguration newConfig, int delay) { _repo .Setup(x => x.Get()) diff --git a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs index fbc17868..9460300b 100644 --- a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs +++ b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs @@ -72,7 +72,6 @@ namespace Ocelot.UnitTests.Responder [InlineData(OcelotErrorCode.UnableToFindLoadBalancerError)] [InlineData(OcelotErrorCode.UnableToFindServiceDiscoveryProviderError)] [InlineData(OcelotErrorCode.UnableToFindQoSProviderError)] - [InlineData(OcelotErrorCode.UnableToSetConfigInConsulError)] [InlineData(OcelotErrorCode.UnknownError)] [InlineData(OcelotErrorCode.UnmappableRequestError)] [InlineData(OcelotErrorCode.UnsupportedAuthenticationProviderError)] @@ -126,7 +125,7 @@ namespace Ocelot.UnitTests.Responder // If this test fails then it's because the number of error codes has changed. // You should make the appropriate changes to the test cases here to ensure // they cover all the error codes, and then modify this assertion. - Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(35, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); + Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(34, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); } private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode) From 34afefced6ed4bd4aa599795df31282c3706e67a Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 12 Aug 2018 19:44:08 +0530 Subject: [PATCH 26/50] Feature/remove pivotal (#541) * #529 removed eureka client * #529 removed some unused code --- ....cs => IFileConfigurationPollerOptions.cs} | 0 .../DependencyInjection/OcelotBuilder.cs | 12 - .../Middleware/OcelotMiddlewareExtensions.cs | 12 - src/Ocelot/Ocelot.csproj | 1 - .../EurekaServiceDiscoveryProvider.cs | 34 --- .../Providers/FakeEurekaDiscoveryClient.cs | 27 -- .../ServiceDiscoveryProviderFactory.cs | 10 +- .../ServiceDiscoveryTests.cs | 282 ------------------ .../DependencyInjection/OcelotBuilderTests.cs | 34 --- .../OcelotPipelineExtensionsTests.cs | 14 - .../EurekaServiceDiscoveryProviderTests.cs | 118 -------- .../ServiceProviderFactoryTests.cs | 29 +- 12 files changed, 5 insertions(+), 568 deletions(-) rename src/Ocelot/Configuration/Repository/{IConsulPollerConfiguration.cs => IFileConfigurationPollerOptions.cs} (100%) delete mode 100644 src/Ocelot/ServiceDiscovery/Providers/EurekaServiceDiscoveryProvider.cs delete mode 100644 src/Ocelot/ServiceDiscovery/Providers/FakeEurekaDiscoveryClient.cs delete mode 100644 test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs delete mode 100644 test/Ocelot.UnitTests/ServiceDiscovery/EurekaServiceDiscoveryProviderTests.cs diff --git a/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs b/src/Ocelot/Configuration/Repository/IFileConfigurationPollerOptions.cs similarity index 100% rename from src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs rename to src/Ocelot/Configuration/Repository/IFileConfigurationPollerOptions.cs diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index d6d87c33..d155bc1e 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -42,8 +42,6 @@ namespace Ocelot.DependencyInjection using Ocelot.Infrastructure; using Ocelot.Middleware.Multiplexer; using ServiceDiscovery.Providers; - using Steeltoe.Common.Discovery; - using Pivotal.Discovery.Client; using Ocelot.Request.Creator; public class OcelotBuilder : IOcelotBuilder @@ -109,16 +107,6 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); - - if (UsingEurekaServiceDiscoveryProvider(configurationRoot)) - { - Services.AddDiscoveryClient(configurationRoot); - } - else - { - Services.TryAddSingleton(); - } - Services.TryAddSingleton(); // see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index f6bb2905..ef3cc67e 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -18,7 +18,6 @@ using Rafty.Concensus; using Rafty.Infrastructure; using Ocelot.Middleware.Pipeline; - using Pivotal.Discovery.Client; using Rafty.Concensus.Node; using Microsoft.Extensions.DependencyInjection; @@ -48,15 +47,9 @@ SetUpRafty(builder); } - if (UsingEurekaServiceDiscoveryProvider(configuration)) - { - builder.UseDiscoveryClient(); - } - ConfigureDiagnosticListener(builder); return CreateOcelotPipeline(builder, pipelineConfiguration); - } private static IApplicationBuilder CreateOcelotPipeline(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) @@ -84,11 +77,6 @@ return builder; } - private static bool UsingEurekaServiceDiscoveryProvider(IInternalConfiguration configuration) - { - return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "eureka"; - } - private static bool UsingRafty(IApplicationBuilder builder) { var node = builder.ApplicationServices.GetService(); diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index 38de9a00..7019986e 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -48,7 +48,6 @@ - diff --git a/src/Ocelot/ServiceDiscovery/Providers/EurekaServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/Providers/EurekaServiceDiscoveryProvider.cs deleted file mode 100644 index a9f12701..00000000 --- a/src/Ocelot/ServiceDiscovery/Providers/EurekaServiceDiscoveryProvider.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace Ocelot.ServiceDiscovery.Providers -{ - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; - using Steeltoe.Common.Discovery; - using Values; - - public class EurekaServiceDiscoveryProvider : IServiceDiscoveryProvider - { - private readonly IDiscoveryClient _client; - private readonly string _serviceName; - - public EurekaServiceDiscoveryProvider(string serviceName, IDiscoveryClient client) - { - _client = client; - _serviceName = serviceName; - } - - public Task> Get() - { - var services = new List(); - - var instances = _client.GetInstances(_serviceName); - - if (instances != null && instances.Any()) - { - services.AddRange(instances.Select(i => new Service(i.ServiceId, new ServiceHostAndPort(i.Host, i.Port), "", "", new List()))); - } - - return Task.FromResult(services); - } - } -} diff --git a/src/Ocelot/ServiceDiscovery/Providers/FakeEurekaDiscoveryClient.cs b/src/Ocelot/ServiceDiscovery/Providers/FakeEurekaDiscoveryClient.cs deleted file mode 100644 index 5ccc382d..00000000 --- a/src/Ocelot/ServiceDiscovery/Providers/FakeEurekaDiscoveryClient.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Ocelot.ServiceDiscovery.Providers -{ - using System.Collections.Generic; - using System.Threading.Tasks; - using Steeltoe.Common.Discovery; - - public class FakeEurekaDiscoveryClient : IDiscoveryClient - { - public IServiceInstance GetLocalServiceInstance() - { - throw new System.NotImplementedException(); - } - - public IList GetInstances(string serviceId) - { - throw new System.NotImplementedException(); - } - - public Task ShutdownAsync() - { - throw new System.NotImplementedException(); - } - - public string Description { get; } - public IList Services { get; } - } -} diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index a482d961..503814e9 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -9,19 +9,16 @@ namespace Ocelot.ServiceDiscovery using System; using System.Linq; using Microsoft.Extensions.DependencyInjection; - using Steeltoe.Common.Discovery; public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory { private readonly IOcelotLoggerFactory _factory; - private readonly IDiscoveryClient _eurekaClient; private readonly List _delegates; private readonly IServiceProvider _provider; - public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IDiscoveryClient eurekaClient, IServiceProvider provider) + public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IServiceProvider provider) { _factory = factory; - _eurekaClient = eurekaClient; _provider = provider; _delegates = provider .GetServices() @@ -55,11 +52,6 @@ namespace Ocelot.ServiceDiscovery return new ServiceFabricServiceDiscoveryProvider(sfConfig); } - if (config.Type?.ToLower() == "eureka") - { - return new EurekaServiceDiscoveryProvider(key, _eurekaClient); - } - foreach (var serviceDiscoveryFinderDelegate in _delegates) { var provider = serviceDiscoveryFinderDelegate?.Invoke(_provider, config, key); diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs deleted file mode 100644 index 8d6324d3..00000000 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ /dev/null @@ -1,282 +0,0 @@ -namespace Ocelot.AcceptanceTests -{ - using System; - using System.Collections.Generic; - using System.Net; - using Microsoft.AspNetCore.Http; - using Ocelot.Configuration.File; - using TestStack.BDDfy; - using Xunit; - using Newtonsoft.Json; - using Steeltoe.Common.Discovery; - - public class ServiceDiscoveryTests : IDisposable - { - private readonly Steps _steps; - private readonly List _eurekaInstances; - private readonly ServiceHandler _serviceHandler; - - public ServiceDiscoveryTests() - { - _serviceHandler = new ServiceHandler(); - _steps = new Steps(); - _eurekaInstances = new List(); - } - - [Fact] - public void should_use_eureka_service_discovery_and_make_request() - { - var eurekaPort = 8761; - var serviceName = "product"; - var downstreamServicePort = 50371; - var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}"; - var fakeEurekaServiceDiscoveryUrl = $"http://localhost:{eurekaPort}"; - - var instanceOne = new FakeEurekaService(serviceName, "localhost", downstreamServicePort, false, - new Uri($"http://localhost:{downstreamServicePort}"), new Dictionary()); - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - ServiceName = serviceName, - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }, - UseServiceDiscovery = true, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Type = "Eureka" - } - } - }; - - this.Given(x => x.GivenEurekaProductServiceOneIsRunning(downstreamServiceOneUrl)) - .And(x => x.GivenThereIsAFakeEurekaServiceDiscoveryProvider(fakeEurekaServiceDiscoveryUrl, serviceName)) - .And(x => x.GivenTheServicesAreRegisteredWithEureka(instanceOne)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(_ => _steps.ThenTheResponseBodyShouldBe(nameof(ServiceDiscoveryTests))) - .BDDfy(); - } - - private void GivenTheServicesAreRegisteredWithEureka(params IServiceInstance[] serviceInstances) - { - foreach (var instance in serviceInstances) - { - _eurekaInstances.Add(instance); - } - } - - private void GivenThereIsAFakeEurekaServiceDiscoveryProvider(string url, string serviceName) - { - _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => - { - if (context.Request.Path.Value == "/eureka/apps/") - { - var apps = new List(); - - foreach (var serviceInstance in _eurekaInstances) - { - var a = new Application - { - name = serviceName, - instance = new List - { - new Instance - { - instanceId = $"{serviceInstance.Host}:{serviceInstance}", - hostName = serviceInstance.Host, - app = serviceName, - ipAddr = "127.0.0.1", - status = "UP", - overriddenstatus = "UNKNOWN", - port = new Port {value = serviceInstance.Port, enabled = "true"}, - securePort = new SecurePort {value = serviceInstance.Port, enabled = "true"}, - countryId = 1, - dataCenterInfo = new DataCenterInfo {value = "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", name = "MyOwn"}, - leaseInfo = new LeaseInfo - { - renewalIntervalInSecs = 30, - durationInSecs = 90, - registrationTimestamp = 1457714988223, - lastRenewalTimestamp= 1457716158319, - evictionTimestamp = 0, - serviceUpTimestamp = 1457714988223 - }, - metadata = new Metadata - { - value = "java.util.Collections$EmptyMap" - }, - homePageUrl = $"{serviceInstance.Host}:{serviceInstance.Port}", - statusPageUrl = $"{serviceInstance.Host}:{serviceInstance.Port}", - healthCheckUrl = $"{serviceInstance.Host}:{serviceInstance.Port}", - vipAddress = serviceName, - isCoordinatingDiscoveryServer = "false", - lastUpdatedTimestamp = "1457714988223", - lastDirtyTimestamp = "1457714988172", - actionType = "ADDED" - } - } - }; - - apps.Add(a); - } - - var applications = new EurekaApplications - { - applications = new Applications - { - application = apps, - apps__hashcode = "UP_1_", - versions__delta = "1" - } - }; - - await context.Response.WriteJsonAsync(applications); - } - }); - } - - private void GivenEurekaProductServiceOneIsRunning(string url) - { - _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => - { - try - { - context.Response.StatusCode = 200; - await context.Response.WriteAsync(nameof(ServiceDiscoveryTests)); - } - catch (Exception exception) - { - await context.Response.WriteAsync(exception.StackTrace); - } - }); - } - - public void Dispose() - { - _serviceHandler?.Dispose(); - _steps.Dispose(); - } - } - - public class FakeEurekaService : IServiceInstance - { - public FakeEurekaService(string serviceId, string host, int port, bool isSecure, Uri uri, IDictionary metadata) - { - ServiceId = serviceId; - Host = host; - Port = port; - IsSecure = isSecure; - Uri = uri; - Metadata = metadata; - } - - public string ServiceId { get; } - public string Host { get; } - public int Port { get; } - public bool IsSecure { get; } - public Uri Uri { get; } - public IDictionary Metadata { get; } - } - - public class Port - { - [JsonProperty("$")] - public int value { get; set; } - - [JsonProperty("@enabled")] - public string enabled { get; set; } - } - - public class SecurePort - { - [JsonProperty("$")] - public int value { get; set; } - - [JsonProperty("@enabled")] - public string enabled { get; set; } - } - - public class DataCenterInfo - { - [JsonProperty("@class")] - public string value { get; set; } - - public string name { get; set; } - } - - public class LeaseInfo - { - public int renewalIntervalInSecs { get; set; } - - public int durationInSecs { get; set; } - - public long registrationTimestamp { get; set; } - - public long lastRenewalTimestamp { get; set; } - - public int evictionTimestamp { get; set; } - - public long serviceUpTimestamp { get; set; } - } - - public class Metadata - { - [JsonProperty("@class")] - public string value { get; set; } - } - - public class Instance - { - public string instanceId { get; set; } - public string hostName { get; set; } - public string app { get; set; } - public string ipAddr { get; set; } - public string status { get; set; } - public string overriddenstatus { get; set; } - public Port port { get; set; } - public SecurePort securePort { get; set; } - public int countryId { get; set; } - public DataCenterInfo dataCenterInfo { get; set; } - public LeaseInfo leaseInfo { get; set; } - public Metadata metadata { get; set; } - public string homePageUrl { get; set; } - public string statusPageUrl { get; set; } - public string healthCheckUrl { get; set; } - public string vipAddress { get; set; } - public string isCoordinatingDiscoveryServer { get; set; } - public string lastUpdatedTimestamp { get; set; } - public string lastDirtyTimestamp { get; set; } - public string actionType { get; set; } - } - - public class Application - { - public string name { get; set; } - public List instance { get; set; } - } - - public class Applications - { - public string versions__delta { get; set; } - public string apps__hashcode { get; set; } - public List application { get; set; } - } - - public class EurekaApplications - { - public Applications applications { get; set; } - } -} diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 419fd2e0..11afdcf7 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -159,15 +159,6 @@ namespace Ocelot.UnitTests.DependencyInjection _ocelotBuilder.AddTransientDefinedAggregator(); } - 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(); @@ -269,18 +260,6 @@ namespace Ocelot.UnitTests.DependencyInjection } } - private void AddGlobalDelegatingHandler() - where T : DelegatingHandler - { - _ocelotBuilder.AddDelegatingHandler(true); - } - - private void AddSpecificDelegatingHandler() - where T : DelegatingHandler - { - _ocelotBuilder.AddDelegatingHandler(); - } - private void ThenAnOcelotBuilderIsReturned() { _ocelotBuilder.ShouldBeOfType(); @@ -324,19 +303,6 @@ namespace Ocelot.UnitTests.DependencyInjection } } - private void WhenIAccessOcelotHttpTracingHandler() - { - try - { - var tracingHandler = _serviceProvider.GetService(); - tracingHandler.ShouldNotBeNull(); - } - catch (Exception e) - { - _ex = e; - } - } - private void WhenIValidateScopes() { try diff --git a/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs index 0de6cae2..e14dddb5 100644 --- a/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs +++ b/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs @@ -1,9 +1,7 @@ namespace Ocelot.UnitTests.Middleware { - using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; - using Ocelot.Cache; using Ocelot.DependencyInjection; using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamUrlCreator.Middleware; @@ -12,10 +10,7 @@ namespace Ocelot.UnitTests.Middleware using Ocelot.Middleware.Pipeline; using Ocelot.Request.Middleware; using Ocelot.WebSockets.Middleware; - using Pivotal.Discovery.Client; using Shouldly; - using Steeltoe.Common.Discovery; - using Steeltoe.Discovery.Eureka; using TestStack.BDDfy; using Xunit; @@ -74,15 +69,6 @@ namespace Ocelot.UnitTests.Middleware var root = test.Build(); var services = new ServiceCollection(); services.AddSingleton(root); - services.AddDiscoveryClient(new DiscoveryOptions - { - ClientType = DiscoveryClientType.EUREKA, - ClientOptions = new EurekaClientOptions() - { - ShouldFetchRegistry = false, - ShouldRegisterWithEureka = false - } - }); services.AddOcelot(); var provider = services.BuildServiceProvider(); _builder = new OcelotPipelineBuilder(provider); diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/EurekaServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/EurekaServiceDiscoveryProviderTests.cs deleted file mode 100644 index adfb6957..00000000 --- a/test/Ocelot.UnitTests/ServiceDiscovery/EurekaServiceDiscoveryProviderTests.cs +++ /dev/null @@ -1,118 +0,0 @@ -namespace Ocelot.UnitTests.ServiceDiscovery -{ - using System; - using System.Collections.Generic; - using System.Threading.Tasks; - using Moq; - using Ocelot.ServiceDiscovery.Providers; - using Pivotal.Discovery.Client; - using Shouldly; - using Steeltoe.Common.Discovery; - using TestStack.BDDfy; - using Values; - using Xunit; - - public class EurekaServiceDiscoveryProviderTests - { - private readonly EurekaServiceDiscoveryProvider _provider; - private readonly Mock _client; - private readonly string _serviceId; - private List _instances; - private List _result; - - public EurekaServiceDiscoveryProviderTests() - { - _serviceId = "Laura"; - _client = new Mock(); - _provider = new EurekaServiceDiscoveryProvider(_serviceId, _client.Object); - } - - [Fact] - public void should_return_empty_services() - { - this.When(_ => WhenIGet()) - .Then(_ => ThenTheCountIs(0)) - .BDDfy(); - } - - [Fact] - public void should_return_service_from_client() - { - var instances = new List - { - new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary()) - }; - - this.Given(_ => GivenThe(instances)) - .When(_ => WhenIGet()) - .Then(_ => ThenTheCountIs(1)) - .And(_ => ThenTheClientIsCalledCorrectly()) - .And(_ => ThenTheServiceIsMapped()) - .BDDfy(); - } - - [Fact] - public void should_return_services_from_client() - { - var instances = new List - { - new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary()), - new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary()) - }; - - this.Given(_ => GivenThe(instances)) - .When(_ => WhenIGet()) - .Then(_ => ThenTheCountIs(2)) - .And(_ => ThenTheClientIsCalledCorrectly()) - .BDDfy(); - } - - private void ThenTheServiceIsMapped() - { - _result[0].HostAndPort.DownstreamHost.ShouldBe("somehost"); - _result[0].HostAndPort.DownstreamPort.ShouldBe(801); - _result[0].Name.ShouldBe(_serviceId); - } - - private void ThenTheCountIs(int expected) - { - _result.Count.ShouldBe(expected); - } - - private void ThenTheClientIsCalledCorrectly() - { - _client.Verify(x => x.GetInstances(_serviceId), Times.Once); - } - - private async Task WhenIGet() - { - _result = await _provider.Get(); - } - - private void GivenThe(List instances) - { - _instances = instances; - _client.Setup(x => x.GetInstances(It.IsAny())).Returns(instances); - } - } - - public class EurekaService : IServiceInstance - { - public EurekaService(string serviceId, string host, int port, bool isSecure, Uri uri, IDictionary metadata) - { - ServiceId = serviceId; - Host = host; - Port = port; - IsSecure = isSecure; - Uri = uri; - Metadata = metadata; - } - - public string ServiceId { get; } - public string Host { get; } - public int Port { get; } - public bool IsSecure { get; } - public Uri Uri { get; } - public IDictionary Metadata { get; } - } -} diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index edf18cdd..6db38012 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -3,7 +3,6 @@ namespace Ocelot.UnitTests.ServiceDiscovery using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; - using Steeltoe.Common.Discovery; using Values; using System.Collections.Generic; using Moq; @@ -22,20 +21,18 @@ namespace Ocelot.UnitTests.ServiceDiscovery private IServiceDiscoveryProvider _result; private ServiceDiscoveryProviderFactory _factory; private DownstreamReRoute _reRoute; - private Mock _loggerFactory; - private Mock _discoveryClient; + private readonly Mock _loggerFactory; private Mock _logger; private IServiceProvider _provider; - private IServiceCollection _collection; + private readonly IServiceCollection _collection; public ServiceProviderFactoryTests() { _loggerFactory = new Mock(); _logger = new Mock(); - _discoveryClient = new Mock(); _collection = new ServiceCollection(); _provider = _collection.BuildServiceProvider(); - _factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, _discoveryClient.Object, _provider); + _factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, _provider); } [Fact] @@ -109,30 +106,12 @@ namespace Ocelot.UnitTests.ServiceDiscovery .BDDfy(); } - [Fact] - public void should_return_eureka_provider() - { - var reRoute = new DownstreamReRouteBuilder() - .WithServiceName("product") - .WithUseServiceDiscovery(true) - .Build(); - - var serviceConfig = new ServiceProviderConfigurationBuilder() - .WithType("Eureka") - .Build(); - - this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) - .When(x => x.WhenIGetTheServiceProvider()) - .Then(x => x.ThenTheServiceProviderIs()) - .BDDfy(); - } - private void GivenAFakeDelegate() { ServiceDiscoveryFinderDelegate fake = (provider, config, name) => new Fake(); _collection.AddSingleton(fake); _provider = _collection.BuildServiceProvider(); - _factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, _discoveryClient.Object, _provider); + _factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, _provider); } class Fake : IServiceDiscoveryProvider From 4e22b3cfc44aca66167426c6b62d3cda53622ace Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 12 Aug 2018 20:00:36 +0530 Subject: [PATCH 27/50] #529 +semver: breaking updated docs to show how to load pivotal package (#542) --- docs/features/servicediscovery.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index 3a88fb47..c24a2e39 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -117,7 +117,18 @@ This feature was requested as part of `Issue 262 `_ which is something to do with `Pivotal `_! Anyway enough of the background. -In order to get this working add the following to ocelot.json.. +The first thing you need to do is install the NuGet package that provides Eureka support in Ocelot. + +``Install-Package Ocelot.Provider.Eureka`` + +Then add the following to your ConfigureServices method. + +.. code-block:: csharp + + s.AddOcelot() + .AddEureka(); + +Then in order to get this working add the following to ocelot.json.. .. code-block:: json From cbd0af6d75a80e53dcf2b3364dfcef3600d7d3c9 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 12 Aug 2018 22:12:59 +0530 Subject: [PATCH 28/50] #529 removed raft (#543) --- .../FileConfigurationController.cs | 16 - .../IOcelotAdministrationBuilder.cs | 1 - .../OcelotAdministrationBuilder.cs | 29 +- .../Middleware/OcelotMiddlewareExtensions.cs | 34 -- src/Ocelot/Ocelot.csproj | 1 - src/Ocelot/Raft/BearerToken.cs | 17 - .../Raft/ExcludeFromCoverageAttribute.cs | 7 - src/Ocelot/Raft/FakeCommand.cs | 15 - src/Ocelot/Raft/FilePeer.cs | 8 - src/Ocelot/Raft/FilePeers.cs | 15 - src/Ocelot/Raft/FilePeersProvider.cs | 47 -- src/Ocelot/Raft/HttpPeer.cs | 131 ----- src/Ocelot/Raft/OcelotFiniteStateMachine.cs | 26 - src/Ocelot/Raft/RaftController.cs | 101 ---- src/Ocelot/Raft/SqlLiteLog.cs | 335 ------------ src/Ocelot/Raft/UpdateFileConfiguration.cs | 15 - .../Ocelot.AcceptanceTests.csproj | 3 +- .../Ocelot.IntegrationTests.csproj | 1 - test/Ocelot.IntegrationTests/RaftTests.cs | 515 ------------------ .../FileConfigurationControllerTests.cs | 58 -- .../DependencyInjection/OcelotBuilderTests.cs | 22 - test/Ocelot.UnitTests/Ocelot.UnitTests.csproj | 1 - .../Raft/OcelotFiniteStateMachineTests.cs | 45 -- 23 files changed, 6 insertions(+), 1437 deletions(-) delete mode 100644 src/Ocelot/Raft/BearerToken.cs delete mode 100644 src/Ocelot/Raft/ExcludeFromCoverageAttribute.cs delete mode 100644 src/Ocelot/Raft/FakeCommand.cs delete mode 100644 src/Ocelot/Raft/FilePeer.cs delete mode 100644 src/Ocelot/Raft/FilePeers.cs delete mode 100644 src/Ocelot/Raft/FilePeersProvider.cs delete mode 100644 src/Ocelot/Raft/HttpPeer.cs delete mode 100644 src/Ocelot/Raft/OcelotFiniteStateMachine.cs delete mode 100644 src/Ocelot/Raft/RaftController.cs delete mode 100644 src/Ocelot/Raft/SqlLiteLog.cs delete mode 100644 src/Ocelot/Raft/UpdateFileConfiguration.cs delete mode 100644 test/Ocelot.IntegrationTests/RaftTests.cs delete mode 100644 test/Ocelot.UnitTests/Raft/OcelotFiniteStateMachineTests.cs diff --git a/src/Ocelot/Configuration/FileConfigurationController.cs b/src/Ocelot/Configuration/FileConfigurationController.cs index dbc77b77..91777ef3 100644 --- a/src/Ocelot/Configuration/FileConfigurationController.cs +++ b/src/Ocelot/Configuration/FileConfigurationController.cs @@ -4,11 +4,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Ocelot.Configuration.File; using Ocelot.Configuration.Setter; -using Ocelot.Raft; namespace Ocelot.Configuration { - using Rafty.Concensus.Node; using Repository; [Authorize] @@ -44,20 +42,6 @@ namespace Ocelot.Configuration { try { - //todo - this code is a bit shit sort it out.. - var test = _provider.GetService(typeof(INode)); - if (test != null) - { - var node = (INode)test; - var result = await node.Accept(new UpdateFileConfiguration(fileConfiguration)); - if (result.GetType() == typeof(Rafty.Infrastructure.ErrorResponse)) - { - return new BadRequestObjectResult("There was a problem. This error message sucks raise an issue in GitHub."); - } - - return new OkObjectResult(result.Command.Configuration); - } - var response = await _setter.Set(fileConfiguration); if (response.IsError) diff --git a/src/Ocelot/DependencyInjection/IOcelotAdministrationBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotAdministrationBuilder.cs index 859f4d98..8db868f6 100644 --- a/src/Ocelot/DependencyInjection/IOcelotAdministrationBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotAdministrationBuilder.cs @@ -2,6 +2,5 @@ namespace Ocelot.DependencyInjection { public interface IOcelotAdministrationBuilder { - IOcelotAdministrationBuilder AddRafty(); } } diff --git a/src/Ocelot/DependencyInjection/OcelotAdministrationBuilder.cs b/src/Ocelot/DependencyInjection/OcelotAdministrationBuilder.cs index c96cdef3..3045d3d7 100644 --- a/src/Ocelot/DependencyInjection/OcelotAdministrationBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotAdministrationBuilder.cs @@ -1,36 +1,17 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Ocelot.Raft; -using Rafty.Concensus; -using Rafty.FiniteStateMachine; -using Rafty.Infrastructure; -using Rafty.Log; namespace Ocelot.DependencyInjection { - using Rafty.Concensus.Node; - public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder { - private readonly IServiceCollection _services; - private readonly IConfiguration _configurationRoot; - + private IServiceCollection Services { get; } + private IConfiguration ConfigurationRoot { get; } + public OcelotAdministrationBuilder(IServiceCollection services, IConfiguration configurationRoot) { - _configurationRoot = configurationRoot; - _services = services; - } - - public IOcelotAdministrationBuilder AddRafty() - { - var settings = new InMemorySettings(4000, 6000, 100, 10000); - _services.AddSingleton(); - _services.AddSingleton(); - _services.AddSingleton(settings); - _services.AddSingleton(); - _services.AddSingleton(); - _services.Configure(_configurationRoot); - return this; + ConfigurationRoot = configurationRoot; + Services = services; } } } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index ef3cc67e..6fe5a1df 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -15,10 +15,7 @@ using Ocelot.Configuration.Setter; using Ocelot.Responses; using Ocelot.Logging; - using Rafty.Concensus; - using Rafty.Infrastructure; using Ocelot.Middleware.Pipeline; - using Rafty.Concensus.Node; using Microsoft.Extensions.DependencyInjection; public static class OcelotMiddlewareExtensions @@ -42,11 +39,6 @@ CreateAdministrationArea(builder, configuration); - if (UsingRafty(builder)) - { - SetUpRafty(builder); - } - ConfigureDiagnosticListener(builder); return CreateOcelotPipeline(builder, pipelineConfiguration); @@ -77,26 +69,6 @@ return builder; } - private static bool UsingRafty(IApplicationBuilder builder) - { - var node = builder.ApplicationServices.GetService(); - if (node != null) - { - return true; - } - - return false; - } - - private static void SetUpRafty(IApplicationBuilder builder) - { - var applicationLifetime = builder.ApplicationServices.GetService(); - applicationLifetime.ApplicationStopping.Register(() => OnShutdown(builder)); - var node = builder.ApplicationServices.GetService(); - var nodeId = builder.ApplicationServices.GetService(); - node.Start(nodeId); - } - private static async Task CreateConfiguration(IApplicationBuilder builder) { // make configuration from file system? @@ -207,11 +179,5 @@ var diagnosticListener = builder.ApplicationServices.GetService(); diagnosticListener.SubscribeWithAdapter(listener); } - - private static void OnShutdown(IApplicationBuilder app) - { - var node = app.ApplicationServices.GetService(); - node.Stop(); - } } } diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index 7019986e..7a2cdf58 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -48,6 +48,5 @@ - diff --git a/src/Ocelot/Raft/BearerToken.cs b/src/Ocelot/Raft/BearerToken.cs deleted file mode 100644 index 983dada9..00000000 --- a/src/Ocelot/Raft/BearerToken.cs +++ /dev/null @@ -1,17 +0,0 @@ -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/Raft/ExcludeFromCoverageAttribute.cs b/src/Ocelot/Raft/ExcludeFromCoverageAttribute.cs deleted file mode 100644 index 9ea5544a..00000000 --- a/src/Ocelot/Raft/ExcludeFromCoverageAttribute.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; - -namespace Ocelot.Raft -{ - [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method|AttributeTargets.Property)] - public class ExcludeFromCoverageAttribute : Attribute{} -} \ No newline at end of file diff --git a/src/Ocelot/Raft/FakeCommand.cs b/src/Ocelot/Raft/FakeCommand.cs deleted file mode 100644 index 45dd1045..00000000 --- a/src/Ocelot/Raft/FakeCommand.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Rafty.FiniteStateMachine; - -namespace Ocelot.Raft -{ - [ExcludeFromCoverage] - public class FakeCommand : ICommand - { - public FakeCommand(string value) - { - this.Value = value; - } - - public string Value { get; private set; } - } -} diff --git a/src/Ocelot/Raft/FilePeer.cs b/src/Ocelot/Raft/FilePeer.cs deleted file mode 100644 index dc18840d..00000000 --- a/src/Ocelot/Raft/FilePeer.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ocelot.Raft -{ - [ExcludeFromCoverage] - public class FilePeer - { - public string HostAndPort { get; set; } - } -} diff --git a/src/Ocelot/Raft/FilePeers.cs b/src/Ocelot/Raft/FilePeers.cs deleted file mode 100644 index 7ad32cbe..00000000 --- a/src/Ocelot/Raft/FilePeers.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; - -namespace Ocelot.Raft -{ - [ExcludeFromCoverage] - public class FilePeers - { - public FilePeers() - { - Peers = new List(); - } - - public List Peers {get; set;} - } -} diff --git a/src/Ocelot/Raft/FilePeersProvider.cs b/src/Ocelot/Raft/FilePeersProvider.cs deleted file mode 100644 index 58edae9f..00000000 --- a/src/Ocelot/Raft/FilePeersProvider.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Collections.Generic; -using System.Net.Http; -using Microsoft.Extensions.Options; -using Ocelot.Configuration; -using Ocelot.Configuration.Repository; -using Ocelot.Middleware; -using Rafty.Concensus; -using Rafty.Infrastructure; - -namespace Ocelot.Raft -{ - using Rafty.Concensus.Peers; - - [ExcludeFromCoverage] - public class FilePeersProvider : IPeersProvider - { - private readonly IOptions _options; - private readonly List _peers; - private IBaseUrlFinder _finder; - private IInternalConfigurationRepository _repo; - private IIdentityServerConfiguration _identityServerConfig; - - public FilePeersProvider(IOptions options, IBaseUrlFinder finder, IInternalConfigurationRepository repo, IIdentityServerConfiguration identityServerConfig) - { - _identityServerConfig = identityServerConfig; - _repo = repo; - _finder = finder; - _options = options; - _peers = new List(); - - var config = _repo.Get(); - foreach (var item in _options.Value.Peers) - { - var httpClient = new HttpClient(); - - //todo what if this errors? - var httpPeer = new HttpPeer(item.HostAndPort, httpClient, _finder, config.Data, _identityServerConfig); - _peers.Add(httpPeer); - } - } - - public List Get() - { - return _peers; - } - } -} diff --git a/src/Ocelot/Raft/HttpPeer.cs b/src/Ocelot/Raft/HttpPeer.cs deleted file mode 100644 index 639f1ee8..00000000 --- a/src/Ocelot/Raft/HttpPeer.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Ocelot.Configuration; -using Ocelot.Middleware; -using Rafty.Concensus; -using Rafty.FiniteStateMachine; - -namespace Ocelot.Raft -{ - using Rafty.Concensus.Messages; - using Rafty.Concensus.Peers; - using Rafty.Infrastructure; - - [ExcludeFromCoverage] - public class HttpPeer : IPeer - { - private readonly string _hostAndPort; - private readonly HttpClient _httpClient; - private readonly JsonSerializerSettings _jsonSerializerSettings; - private readonly string _baseSchemeUrlAndPort; - private BearerToken _token; - private readonly IInternalConfiguration _config; - private readonly IIdentityServerConfiguration _identityServerConfiguration; - - public HttpPeer(string hostAndPort, HttpClient httpClient, IBaseUrlFinder finder, IInternalConfiguration config, IIdentityServerConfiguration identityServerConfiguration) - { - _identityServerConfiguration = identityServerConfiguration; - _config = config; - Id = hostAndPort; - _hostAndPort = hostAndPort; - _httpClient = httpClient; - _jsonSerializerSettings = new JsonSerializerSettings() { - TypeNameHandling = TypeNameHandling.All - }; - _baseSchemeUrlAndPort = finder.Find(); - } - - public string Id { get; } - - public async Task Request(RequestVote requestVote) - { - if(_token == null) - { - await SetToken(); - } - - var json = JsonConvert.SerializeObject(requestVote, _jsonSerializerSettings); - var content = new StringContent(json); - content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); - var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/requestvote", content); - if(response.IsSuccessStatusCode) - { - return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings); - } - - return new RequestVoteResponse(false, requestVote.Term); - } - - public async Task Request(AppendEntries appendEntries) - { - try - { - if(_token == null) - { - await SetToken(); - } - - var json = JsonConvert.SerializeObject(appendEntries, _jsonSerializerSettings); - var content = new StringContent(json); - content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); - var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/appendEntries", content); - if(response.IsSuccessStatusCode) - { - return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings); - } - - return new AppendEntriesResponse(appendEntries.Term, false); - } - catch(Exception ex) - { - Console.WriteLine(ex); - return new AppendEntriesResponse(appendEntries.Term, false); - } - } - - public async Task> Request(T command) - where T : ICommand - { - Console.WriteLine("SENDING REQUEST...."); - if(_token == null) - { - await SetToken(); - } - - var json = JsonConvert.SerializeObject(command, _jsonSerializerSettings); - var content = new StringContent(json); - content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); - var response = await _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content); - if(response.IsSuccessStatusCode) - { - Console.WriteLine("REQUEST OK...."); - var okResponse = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings); - return new OkResponse((T)okResponse.Command); - } - - Console.WriteLine("REQUEST NOT OK...."); - return new ErrorResponse(await response.Content.ReadAsStringAsync(), command); - } - - private async Task SetToken() - { - var tokenUrl = $"{_baseSchemeUrlAndPort}{_config.AdministrationPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", _identityServerConfiguration.ApiName), - new KeyValuePair("client_secret", _identityServerConfiguration.ApiSecret), - new KeyValuePair("scope", _identityServerConfiguration.ApiName), - new KeyValuePair("grant_type", "client_credentials") - }; - var content = new FormUrlEncodedContent(formData); - var response = await _httpClient.PostAsync(tokenUrl, content); - var responseContent = await response.Content.ReadAsStringAsync(); - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_token.TokenType, _token.AccessToken); - } - } -} diff --git a/src/Ocelot/Raft/OcelotFiniteStateMachine.cs b/src/Ocelot/Raft/OcelotFiniteStateMachine.cs deleted file mode 100644 index 618d7f5f..00000000 --- a/src/Ocelot/Raft/OcelotFiniteStateMachine.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.Setter; -using Rafty.FiniteStateMachine; -using Rafty.Log; - -namespace Ocelot.Raft -{ - [ExcludeFromCoverage] - public class OcelotFiniteStateMachine : IFiniteStateMachine - { - private readonly IFileConfigurationSetter _setter; - - public OcelotFiniteStateMachine(IFileConfigurationSetter setter) - { - _setter = setter; - } - - public async Task Handle(LogEntry log) - { - //todo - handle an error - //hack it to just cast as at the moment we know this is the only command :P - var hack = (UpdateFileConfiguration)log.CommandData; - await _setter.Set(hack.Configuration); - } - } -} diff --git a/src/Ocelot/Raft/RaftController.cs b/src/Ocelot/Raft/RaftController.cs deleted file mode 100644 index 660449c6..00000000 --- a/src/Ocelot/Raft/RaftController.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.Raft; -using Rafty.Concensus; -using Rafty.FiniteStateMachine; - -namespace Ocelot.Raft -{ - using Rafty.Concensus.Messages; - using Rafty.Concensus.Node; - - [ExcludeFromCoverage] - [Authorize] - [Route("raft")] - public class RaftController : Controller - { - private readonly INode _node; - private readonly IOcelotLogger _logger; - private readonly string _baseSchemeUrlAndPort; - private readonly JsonSerializerSettings _jsonSerialiserSettings; - - public RaftController(INode node, IOcelotLoggerFactory loggerFactory, IBaseUrlFinder finder) - { - _jsonSerialiserSettings = new JsonSerializerSettings { - TypeNameHandling = TypeNameHandling.All - }; - _baseSchemeUrlAndPort = finder.Find(); - _logger = loggerFactory.CreateLogger(); - _node = node; - } - - [Route("appendentries")] - public async Task AppendEntries() - { - using(var reader = new StreamReader(HttpContext.Request.Body)) - { - var json = await reader.ReadToEndAsync(); - - var appendEntries = JsonConvert.DeserializeObject(json, _jsonSerialiserSettings); - - _logger.LogDebug($"{_baseSchemeUrlAndPort}/appendentries called, my state is {_node.State.GetType().FullName}"); - - var appendEntriesResponse = await _node.Handle(appendEntries); - - return new OkObjectResult(appendEntriesResponse); - } - } - - [Route("requestvote")] - public async Task RequestVote() - { - using(var reader = new StreamReader(HttpContext.Request.Body)) - { - var json = await reader.ReadToEndAsync(); - - var requestVote = JsonConvert.DeserializeObject(json, _jsonSerialiserSettings); - - _logger.LogDebug($"{_baseSchemeUrlAndPort}/requestvote called, my state is {_node.State.GetType().FullName}"); - - var requestVoteResponse = await _node.Handle(requestVote); - - return new OkObjectResult(requestVoteResponse); - } - } - - [Route("command")] - public async Task Command() - { - try - { - using(var reader = new StreamReader(HttpContext.Request.Body)) - { - var json = await reader.ReadToEndAsync(); - - var command = JsonConvert.DeserializeObject(json, _jsonSerialiserSettings); - - _logger.LogDebug($"{_baseSchemeUrlAndPort}/command called, my state is {_node.State.GetType().FullName}"); - - var commandResponse = await _node.Accept(command); - - json = JsonConvert.SerializeObject(commandResponse, _jsonSerialiserSettings); - - return StatusCode(200, json); - } - } - catch(Exception e) - { - _logger.LogError($"THERE WAS A PROBLEM ON NODE {_node.State.CurrentState.Id}", e); - throw; - } - } - } -} diff --git a/src/Ocelot/Raft/SqlLiteLog.cs b/src/Ocelot/Raft/SqlLiteLog.cs deleted file mode 100644 index 99cd0308..00000000 --- a/src/Ocelot/Raft/SqlLiteLog.cs +++ /dev/null @@ -1,335 +0,0 @@ -namespace Ocelot.Raft -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Data.Sqlite; - using Microsoft.Extensions.Logging; - using Newtonsoft.Json; - using Rafty.Infrastructure; - using Rafty.Log; - - [ExcludeFromCoverage] - public class SqlLiteLog : ILog - { - private readonly string _path; - private readonly SemaphoreSlim _sempaphore = new SemaphoreSlim(1, 1); - private readonly ILogger _logger; - private readonly NodeId _nodeId; - - public SqlLiteLog(NodeId nodeId, ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - _nodeId = nodeId; - _path = $"{nodeId.Id.Replace("/", "").Replace(":", "")}.db"; - _sempaphore.Wait(); - - if (!File.Exists(_path)) - { - var fs = File.Create(_path); - - fs.Dispose(); - - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - - const string sql = @"create table logs ( - id integer primary key, - data text not null - )"; - - using (var command = new SqliteCommand(sql, connection)) - { - var result = command.ExecuteNonQuery(); - - _logger.LogInformation(result == 0 - ? $"id: {_nodeId.Id} create database, result: {result}" - : $"id: {_nodeId.Id} did not create database., result: {result}"); - } - } - } - - _sempaphore.Release(); - } - - public async Task LastLogIndex() - { - _sempaphore.Wait(); - var result = 1; - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - var sql = @"select id from logs order by id desc limit 1"; - using (var command = new SqliteCommand(sql, connection)) - { - var index = Convert.ToInt32(await command.ExecuteScalarAsync()); - if (index > result) - { - result = index; - } - } - } - - _sempaphore.Release(); - return result; - } - - public async Task LastLogTerm() - { - _sempaphore.Wait(); - long result = 0; - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - var sql = @"select data from logs order by id desc limit 1"; - using (var command = new SqliteCommand(sql, connection)) - { - var data = Convert.ToString(await command.ExecuteScalarAsync()); - var jsonSerializerSettings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }; - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - if (log != null && log.Term > result) - { - result = log.Term; - } - } - } - - _sempaphore.Release(); - return result; - } - - public async Task Count() - { - _sempaphore.Wait(); - var result = 0; - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - var sql = @"select count(id) from logs"; - using (var command = new SqliteCommand(sql, connection)) - { - var index = Convert.ToInt32(await command.ExecuteScalarAsync()); - if (index > result) - { - result = index; - } - } - } - - _sempaphore.Release(); - return result; - } - - public async Task Apply(LogEntry log) - { - _sempaphore.Wait(); - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - var jsonSerializerSettings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }; - var data = JsonConvert.SerializeObject(log, jsonSerializerSettings); - - //todo - sql injection dont copy this.. - var sql = $"insert into logs (data) values ('{data}')"; - _logger.LogInformation($"id: {_nodeId.Id}, sql: {sql}"); - using (var command = new SqliteCommand(sql, connection)) - { - var result = await command.ExecuteNonQueryAsync(); - _logger.LogInformation($"id: {_nodeId.Id}, insert log result: {result}"); - } - - sql = "select last_insert_rowid()"; - using (var command = new SqliteCommand(sql, connection)) - { - var result = await command.ExecuteScalarAsync(); - _logger.LogInformation($"id: {_nodeId.Id}, about to release semaphore"); - _sempaphore.Release(); - _logger.LogInformation($"id: {_nodeId.Id}, saved log to sqlite"); - return Convert.ToInt32(result); - } - } - } - - public async Task DeleteConflictsFromThisLog(int index, LogEntry logEntry) - { - _sempaphore.Wait(); - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - - //todo - sql injection dont copy this.. - var sql = $"select data from logs where id = {index};"; - _logger.LogInformation($"id: {_nodeId.Id} sql: {sql}"); - using (var command = new SqliteCommand(sql, connection)) - { - var data = Convert.ToString(await command.ExecuteScalarAsync()); - var jsonSerializerSettings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }; - - _logger.LogInformation($"id {_nodeId.Id} got log for index: {index}, data is {data} and new log term is {logEntry.Term}"); - - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - if (logEntry != null && log != null && logEntry.Term != log.Term) - { - //todo - sql injection dont copy this.. - var deleteSql = $"delete from logs where id >= {index};"; - _logger.LogInformation($"id: {_nodeId.Id} sql: {deleteSql}"); - using (var deleteCommand = new SqliteCommand(deleteSql, connection)) - { - var result = await deleteCommand.ExecuteNonQueryAsync(); - } - } - } - } - - _sempaphore.Release(); - } - - public async Task IsDuplicate(int index, LogEntry logEntry) - { - _sempaphore.Wait(); - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - - //todo - sql injection dont copy this.. - var sql = $"select data from logs where id = {index};"; - using (var command = new SqliteCommand(sql, connection)) - { - var data = Convert.ToString(await command.ExecuteScalarAsync()); - var jsonSerializerSettings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }; - - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - - if (logEntry != null && log != null && logEntry.Term == log.Term) - { - _sempaphore.Release(); - return true; - } - } - } - - _sempaphore.Release(); - return false; - } - - public async Task Get(int index) - { - _sempaphore.Wait(); - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - - //todo - sql injection dont copy this.. - var sql = $"select data from logs where id = {index}"; - using (var command = new SqliteCommand(sql, connection)) - { - var data = Convert.ToString(await command.ExecuteScalarAsync()); - var jsonSerializerSettings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }; - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - _sempaphore.Release(); - return log; - } - } - } - - public async Task> GetFrom(int index) - { - _sempaphore.Wait(); - var logsToReturn = new List<(int, LogEntry)>(); - - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - - //todo - sql injection dont copy this.. - var sql = $"select id, data from logs where id >= {index}"; - using (var command = new SqliteCommand(sql, connection)) - { - using (var reader = await command.ExecuteReaderAsync()) - { - while (reader.Read()) - { - var id = Convert.ToInt32(reader[0]); - var data = (string)reader[1]; - var jsonSerializerSettings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }; - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - logsToReturn.Add((id, log)); - } - } - } - - _sempaphore.Release(); - return logsToReturn; - } - } - - public async Task GetTermAtIndex(int index) - { - _sempaphore.Wait(); - long result = 0; - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - - //todo - sql injection dont copy this.. - var sql = $"select data from logs where id = {index}"; - using (var command = new SqliteCommand(sql, connection)) - { - var data = Convert.ToString(await command.ExecuteScalarAsync()); - var jsonSerializerSettings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }; - var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); - if (log != null && log.Term > result) - { - result = log.Term; - } - } - } - - _sempaphore.Release(); - return result; - } - - public async Task Remove(int indexOfCommand) - { - _sempaphore.Wait(); - using (var connection = new SqliteConnection($"Data Source={_path};")) - { - connection.Open(); - - //todo - sql injection dont copy this.. - var deleteSql = $"delete from logs where id >= {indexOfCommand};"; - _logger.LogInformation($"id: {_nodeId.Id} Remove {deleteSql}"); - using (var deleteCommand = new SqliteCommand(deleteSql, connection)) - { - var result = await deleteCommand.ExecuteNonQueryAsync(); - } - } - - _sempaphore.Release(); - } - } -} diff --git a/src/Ocelot/Raft/UpdateFileConfiguration.cs b/src/Ocelot/Raft/UpdateFileConfiguration.cs deleted file mode 100644 index dfe6a433..00000000 --- a/src/Ocelot/Raft/UpdateFileConfiguration.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Ocelot.Configuration.File; -using Rafty.FiniteStateMachine; - -namespace Ocelot.Raft -{ - public class UpdateFileConfiguration : ICommand - { - public UpdateFileConfiguration(FileConfiguration configuration) - { - Configuration = configuration; - } - - public FileConfiguration Configuration {get;private set;} - } -} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 778fe2ef..b1bbf06e 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -51,7 +51,6 @@ - - + diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj index 62e7371c..1d262f6f 100644 --- a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj @@ -42,7 +42,6 @@ - diff --git a/test/Ocelot.IntegrationTests/RaftTests.cs b/test/Ocelot.IntegrationTests/RaftTests.cs deleted file mode 100644 index df550a0d..00000000 --- a/test/Ocelot.IntegrationTests/RaftTests.cs +++ /dev/null @@ -1,515 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.Raft; -using Rafty.Concensus; -using Rafty.Infrastructure; -using Shouldly; -using Xunit; -using static Rafty.Infrastructure.Wait; -using Microsoft.Data.Sqlite; -using Microsoft.Extensions.Configuration; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; - -namespace Ocelot.IntegrationTests -{ - using System.Threading.Tasks; - using Xunit.Abstractions; - - public class RaftTests : IDisposable - { - private readonly List _builders; - private readonly List _webHostBuilders; - private readonly List _threads; - private FilePeers _peers; - private HttpClient _httpClient; - private readonly HttpClient _httpClientForAssertions; - private BearerToken _token; - private HttpResponseMessage _response; - private static readonly object _lock = new object(); - private ITestOutputHelper _output; - - public RaftTests(ITestOutputHelper output) - { - _output = output; - _httpClientForAssertions = new HttpClient(); - _webHostBuilders = new List(); - _builders = new List(); - _threads = new List(); - } - - [Fact(Skip = "Still not stable, more work required in rafty..")] - public async Task should_persist_command_to_five_servers() - { - var peers = new List - { - new FilePeer {HostAndPort = "http://localhost:5000"}, - - new FilePeer {HostAndPort = "http://localhost:5001"}, - - new FilePeer {HostAndPort = "http://localhost:5002"}, - - new FilePeer {HostAndPort = "http://localhost:5003"}, - - new FilePeer {HostAndPort = "http://localhost:5004"} - }; - - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - } - }; - - var updatedConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "127.0.0.1", - Port = 80, - } - }, - DownstreamScheme = "http", - DownstreamPathTemplate = "/geoffrey", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" - }, - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "123.123.123", - Port = 443, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/blooper/{productId}", - UpstreamHttpMethod = new List { "post" }, - UpstreamPathTemplate = "/test" - } - } - }; - - var command = new UpdateFileConfiguration(updatedConfiguration); - GivenThePeersAre(peers); - GivenThereIsAConfiguration(configuration); - GivenFiveServersAreRunning(); - await GivenIHaveAnOcelotToken("/administration"); - await WhenISendACommandIntoTheCluster(command); - Thread.Sleep(5000); - await ThenTheCommandIsReplicatedToAllStateMachines(command); - } - - [Fact(Skip = "Still not stable, more work required in rafty..")] - public async Task should_persist_command_to_five_servers_when_using_administration_api() - { - var peers = new List - { - new FilePeer {HostAndPort = "http://localhost:5005"}, - - new FilePeer {HostAndPort = "http://localhost:5006"}, - - new FilePeer {HostAndPort = "http://localhost:5007"}, - - new FilePeer {HostAndPort = "http://localhost:5008"}, - - new FilePeer {HostAndPort = "http://localhost:5009"} - }; - - var configuration = new FileConfiguration - { - }; - - var updatedConfiguration = new FileConfiguration - { - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "127.0.0.1", - Port = 80, - } - }, - DownstreamScheme = "http", - DownstreamPathTemplate = "/geoffrey", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" - }, - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "123.123.123", - Port = 443, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/blooper/{productId}", - UpstreamHttpMethod = new List { "post" }, - UpstreamPathTemplate = "/test" - } - } - }; - - var command = new UpdateFileConfiguration(updatedConfiguration); - GivenThePeersAre(peers); - GivenThereIsAConfiguration(configuration); - GivenFiveServersAreRunning(); - await GivenIHaveAnOcelotToken("/administration"); - GivenIHaveAddedATokenToMyRequest(); - await WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration); - await ThenTheCommandIsReplicatedToAllStateMachines(command); - } - - private void GivenThePeersAre(List peers) - { - FilePeers filePeers = new FilePeers(); - filePeers.Peers.AddRange(peers); - var json = JsonConvert.SerializeObject(filePeers); - File.WriteAllText("peers.json", json); - _httpClient = new HttpClient(); - var ocelotBaseUrl = peers[0].HostAndPort; - _httpClient.BaseAddress = new Uri(ocelotBaseUrl); - } - - private async Task WhenISendACommandIntoTheCluster(UpdateFileConfiguration command) - { - async Task SendCommand() - { - try - { - var p = _peers.Peers.First(); - var json = JsonConvert.SerializeObject(command, new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }); - var httpContent = new StringContent(json); - httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - using (var httpClient = new HttpClient()) - { - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - var response = await httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent); - response.EnsureSuccessStatusCode(); - var content = await response.Content.ReadAsStringAsync(); - - var errorResult = JsonConvert.DeserializeObject>(content); - - if (!string.IsNullOrEmpty(errorResult.Error)) - { - return false; - } - - var okResult = JsonConvert.DeserializeObject>(content); - - if (okResult.Command.Configuration.ReRoutes.Count == 2) - { - return true; - } - } - - return false; - } - catch (Exception e) - { - Console.WriteLine(e); - return false; - } - } - - var commandSent = await WaitFor(40000).Until(async () => - { - var result = await SendCommand(); - Thread.Sleep(1000); - return result; - }); - - commandSent.ShouldBeTrue(); - } - - private async Task ThenTheCommandIsReplicatedToAllStateMachines(UpdateFileConfiguration expecteds) - { - async Task CommandCalledOnAllStateMachines() - { - try - { - var passed = 0; - foreach (var peer in _peers.Peers) - { - var path = $"{peer.HostAndPort.Replace("/","").Replace(":","")}.db"; - using(var connection = new SqliteConnection($"Data Source={path};")) - { - connection.Open(); - var sql = @"select count(id) from logs"; - using(var command = new SqliteCommand(sql, connection)) - { - var index = Convert.ToInt32(command.ExecuteScalar()); - index.ShouldBe(1); - } - } - - _httpClientForAssertions.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - var result = await _httpClientForAssertions.GetAsync($"{peer.HostAndPort}/administration/configuration"); - var json = await result.Content.ReadAsStringAsync(); - var response = JsonConvert.DeserializeObject(json, new JsonSerializerSettings{TypeNameHandling = TypeNameHandling.All}); - response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.Configuration.GlobalConfiguration.RequestIdKey); - response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Host); - response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for (var i = 0; i < response.ReRoutes.Count; i++) - { - for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++) - { - var res = response.ReRoutes[i].DownstreamHostAndPorts[j]; - var expected = expecteds.Configuration.ReRoutes[i].DownstreamHostAndPorts[j]; - res.Host.ShouldBe(expected.Host); - res.Port.ShouldBe(expected.Port); - } - - response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.Configuration.ReRoutes[i].DownstreamPathTemplate); - response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.Configuration.ReRoutes[i].DownstreamScheme); - response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.Configuration.ReRoutes[i].UpstreamPathTemplate); - response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.Configuration.ReRoutes[i].UpstreamHttpMethod); - } - - passed++; - } - - return passed == 5; - } - catch(Exception e) - { - //_output.WriteLine($"{e.Message}, {e.StackTrace}"); - Console.WriteLine(e); - return false; - } - } - - var commandOnAllStateMachines = await WaitFor(40000).Until(async () => - { - var result = await CommandCalledOnAllStateMachines(); - Thread.Sleep(1000); - return result; - }); - - commandOnAllStateMachines.ShouldBeTrue(); - } - - private async Task WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) - { - async Task SendCommand() - { - var json = JsonConvert.SerializeObject(updatedConfiguration); - - var content = new StringContent(json); - - content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - - _response = await _httpClient.PostAsync(url, content); - - var responseContent = await _response.Content.ReadAsStringAsync(); - - if(responseContent == "There was a problem. This error message sucks raise an issue in GitHub.") - { - return false; - } - - if(string.IsNullOrEmpty(responseContent)) - { - return false; - } - - return _response.IsSuccessStatusCode; - } - - var commandSent = await WaitFor(40000).Until(async () => - { - var result = await SendCommand(); - Thread.Sleep(1000); - return result; - }); - - commandSent.ShouldBeTrue(); - } - - private void GivenIHaveAddedATokenToMyRequest() - { - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - } - - private async Task GivenIHaveAnOcelotToken(string adminPath) - { - async Task AddToken() - { - try - { - var tokenUrl = $"{adminPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "admin"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "admin"), - new KeyValuePair("grant_type", "client_credentials") - }; - var content = new FormUrlEncodedContent(formData); - - var response = await _httpClient.PostAsync(tokenUrl, content); - var responseContent = await response.Content.ReadAsStringAsync(); - if(!response.IsSuccessStatusCode) - { - return false; - } - - _token = JsonConvert.DeserializeObject(responseContent); - var configPath = $"{adminPath}/.well-known/openid-configuration"; - response = await _httpClient.GetAsync(configPath); - return response.IsSuccessStatusCode; - } - catch(Exception) - { - return false; - } - } - - var addToken = await WaitFor(40000).Until(async () => - { - var result = await AddToken(); - Thread.Sleep(1000); - return result; - }); - - addToken.ShouldBeTrue(); - } - - private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - var text = File.ReadAllText(configurationPath); - - configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - text = File.ReadAllText(configurationPath); - } - - private void GivenAServerIsRunning(string url) - { - lock(_lock) - { - IWebHostBuilder webHostBuilder = new WebHostBuilder(); - webHostBuilder.UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", false, false); - config.AddJsonFile("peers.json", optional: true, reloadOnChange: false); - #pragma warning disable CS0618 - config.AddOcelotBaseUrl(url); - #pragma warning restore CS0618 - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - x.AddSingleton(new NodeId(url)); - x - .AddOcelot() - .AddAdministration("/administration", "secret") - .AddRafty(); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - var builder = webHostBuilder.Build(); - builder.Start(); - - _webHostBuilders.Add(webHostBuilder); - _builders.Add(builder); - } - } - - private void GivenFiveServersAreRunning() - { - var bytes = File.ReadAllText("peers.json"); - _peers = JsonConvert.DeserializeObject(bytes); - - foreach (var peer in _peers.Peers) - { - File.Delete(peer.HostAndPort.Replace("/", "").Replace(":", "")); - File.Delete($"{peer.HostAndPort.Replace("/", "").Replace(":", "")}.db"); - var thread = new Thread(() => GivenAServerIsRunning(peer.HostAndPort)); - thread.Start(); - _threads.Add(thread); - } - } - - public void Dispose() - { - foreach (var builder in _builders) - { - builder?.Dispose(); - } - - foreach (var peer in _peers.Peers) - { - try - { - File.Delete(peer.HostAndPort.Replace("/", "").Replace(":", "")); - File.Delete($"{peer.HostAndPort.Replace("/", "").Replace(":", "")}.db"); - } - catch (Exception e) - { - Console.WriteLine(e); - } - } - } - } -} diff --git a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs index 8432d584..9a42dba9 100644 --- a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs @@ -8,14 +8,11 @@ using Ocelot.Responses; using TestStack.BDDfy; using Xunit; using Shouldly; -using Ocelot.Raft; -using Rafty.Concensus; using Ocelot.Configuration; namespace Ocelot.UnitTests.Controllers { using Ocelot.Configuration.Repository; - using Rafty.Concensus.Node; public class FileConfigurationControllerTests { @@ -25,7 +22,6 @@ namespace Ocelot.UnitTests.Controllers private IActionResult _result; private FileConfiguration _fileConfiguration; private readonly Mock _provider; - private Mock _node; public FileConfigurationControllerTests() { @@ -70,33 +66,6 @@ namespace Ocelot.UnitTests.Controllers .BDDfy(); } - [Fact] - public void should_post_file_configuration_using_raft_node() - { - var expected = new FileConfiguration(); - - this.Given(x => GivenTheFileConfiguration(expected)) - .And(x => GivenARaftNodeIsRegistered()) - .And(x => GivenTheNodeReturnsOK()) - .And(x => GivenTheConfigSetterReturns(new OkResponse())) - .When(x => WhenIPostTheFileConfiguration()) - .Then(x => x.ThenTheNodeIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_return_error_when_cannot_set_config_using_raft_node() - { - var expected = new FileConfiguration(); - - this.Given(x => GivenTheFileConfiguration(expected)) - .And(x => GivenARaftNodeIsRegistered()) - .And(x => GivenTheNodeReturnsError()) - .When(x => WhenIPostTheFileConfiguration()) - .Then(x => ThenTheResponseIs()) - .BDDfy(); - } - [Fact] public void should_return_error_when_cannot_set_config() { @@ -110,33 +79,6 @@ namespace Ocelot.UnitTests.Controllers .BDDfy(); } - private void ThenTheNodeIsCalledCorrectly() - { - _node.Verify(x => x.Accept(It.IsAny()), Times.Once); - } - - private void GivenARaftNodeIsRegistered() - { - _node = new Mock(); - _provider - .Setup(x => x.GetService(typeof(INode))) - .Returns(_node.Object); - } - - private void GivenTheNodeReturnsOK() - { - _node - .Setup(x => x.Accept(It.IsAny())) - .ReturnsAsync(new Rafty.Infrastructure.OkResponse(new UpdateFileConfiguration(new FileConfiguration()))); - } - - private void GivenTheNodeReturnsError() - { - _node - .Setup(x => x.Accept(It.IsAny())) - .ReturnsAsync(new Rafty.Infrastructure.ErrorResponse("error", new UpdateFileConfiguration(new FileConfiguration()))); - } - private void GivenTheConfigSetterReturns(Response response) { _setter diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 11afdcf7..7abe40e6 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -75,16 +75,6 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } - [Fact] - public void should_set_up_rafty() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpRafty()) - .Then(x => ThenAnExceptionIsntThrown()) - .Then(x => ThenTheCorrectAdminPathIsRegitered()) - .BDDfy(); - } - [Fact] public void should_set_up_administration_with_identity_server_options() { @@ -248,18 +238,6 @@ namespace Ocelot.UnitTests.DependencyInjection first.ShouldBe(second); } - private void WhenISetUpRafty() - { - try - { - _ocelotBuilder.AddAdministration("/administration", "secret").AddRafty(); - } - catch (Exception e) - { - _ex = e; - } - } - private void ThenAnOcelotBuilderIsReturned() { _ocelotBuilder.ShouldBeOfType(); diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index cb7110b4..7a08589a 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -57,7 +57,6 @@ - diff --git a/test/Ocelot.UnitTests/Raft/OcelotFiniteStateMachineTests.cs b/test/Ocelot.UnitTests/Raft/OcelotFiniteStateMachineTests.cs deleted file mode 100644 index 669b6bf0..00000000 --- a/test/Ocelot.UnitTests/Raft/OcelotFiniteStateMachineTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Moq; -using Ocelot.Configuration.Setter; -using Ocelot.Raft; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Raft -{ - public class OcelotFiniteStateMachineTests - { - private UpdateFileConfiguration _command; - private OcelotFiniteStateMachine _fsm; - private Mock _setter; - - public OcelotFiniteStateMachineTests() - { - _setter = new Mock(); - _fsm = new OcelotFiniteStateMachine(_setter.Object); - } - - [Fact] - public void should_handle_update_file_configuration_command() - { - this.Given(x => GivenACommand(new UpdateFileConfiguration(new Ocelot.Configuration.File.FileConfiguration()))) - .When(x => WhenTheCommandIsHandled()) - .Then(x => ThenTheStateIsUpdated()) - .BDDfy(); - } - - private void GivenACommand(UpdateFileConfiguration command) - { - _command = command; - } - - private void WhenTheCommandIsHandled() - { - _fsm.Handle(new Rafty.Log.LogEntry(_command, _command.GetType(), 0)); - } - - private void ThenTheStateIsUpdated() - { - _setter.Verify(x => x.Set(_command.Configuration), Times.Once); - } - } -} \ No newline at end of file From 5916cbfd942c2014928265ba429566e11d8a768e Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 12 Aug 2018 22:51:11 +0530 Subject: [PATCH 29/50] Feature/remove rafty (#544) * #529 removed raft * #529 Rafty needs properties exposed --- .../IOcelotAdministrationBuilder.cs | 5 +++++ .../DependencyInjection/OcelotAdministrationBuilder.cs | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Ocelot/DependencyInjection/IOcelotAdministrationBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotAdministrationBuilder.cs index 8db868f6..bcb01189 100644 --- a/src/Ocelot/DependencyInjection/IOcelotAdministrationBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotAdministrationBuilder.cs @@ -1,6 +1,11 @@ namespace Ocelot.DependencyInjection { + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + public interface IOcelotAdministrationBuilder { + IServiceCollection Services { get; } + IConfiguration ConfigurationRoot { get; } } } diff --git a/src/Ocelot/DependencyInjection/OcelotAdministrationBuilder.cs b/src/Ocelot/DependencyInjection/OcelotAdministrationBuilder.cs index 3045d3d7..6e59284f 100644 --- a/src/Ocelot/DependencyInjection/OcelotAdministrationBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotAdministrationBuilder.cs @@ -1,12 +1,12 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - namespace Ocelot.DependencyInjection { + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder { - private IServiceCollection Services { get; } - private IConfiguration ConfigurationRoot { get; } + public IServiceCollection Services { get; } + public IConfiguration ConfigurationRoot { get; } public OcelotAdministrationBuilder(IServiceCollection services, IConfiguration configurationRoot) { From edbe33415dbd8d6505bff4bed0b801c607d79592 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Mon, 13 Aug 2018 00:58:58 +0530 Subject: [PATCH 30/50] #529 updated docs (#545) --- docs/features/raft.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/features/raft.rst b/docs/features/raft.rst index b193b407..047bd347 100644 --- a/docs/features/raft.rst +++ b/docs/features/raft.rst @@ -5,7 +5,11 @@ Ocelot has recenely integrated `Rafty `_ Raft is a distributed concensus algorythm that allows a cluster of servers (Ocelots) to maintain local state without having a centralised database for storing state (e.g. SQL Server). -In order to enable Rafty in Ocelot you must make the following changes to your Startup.cs. +To get Raft support you must first install the Ocelot Rafty package. + +``Install-Package Ocelot.Provider.Rafty`` + +Then you must make the following changes to your Startup.cs / Program.cs. .. code-block:: csharp From 0786614e50d9713eb3703ce9c4f8313983d71f53 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Tue, 14 Aug 2018 08:19:00 +0100 Subject: [PATCH 31/50] removed idserver4 (#547) --- src/Ocelot/Authorisation/ScopesAuthoriser.cs | 94 +- .../DependencyInjection/IOcelotBuilder.cs | 5 - .../DependencyInjection/OcelotBuilder.cs | 115 --- .../Middleware/OcelotMiddlewareExtensions.cs | 21 - src/Ocelot/Ocelot.csproj | 2 - .../Ocelot.AcceptanceTests.csproj | 2 + .../AdministrationTests.cs | 837 ------------------ test/Ocelot.IntegrationTests/BearerToken.cs | 16 - .../Ocelot.IntegrationTests.csproj | 1 - .../ThreadSafeHeadersTests.cs | 3 +- test/Ocelot.ManualTest/Program.cs | 15 +- .../AuthenticationMiddlewareTests.cs | 186 ++-- .../DependencyInjection/OcelotBuilderTests.cs | 40 - 13 files changed, 150 insertions(+), 1187 deletions(-) delete mode 100644 test/Ocelot.IntegrationTests/AdministrationTests.cs delete mode 100644 test/Ocelot.IntegrationTests/BearerToken.cs diff --git a/src/Ocelot/Authorisation/ScopesAuthoriser.cs b/src/Ocelot/Authorisation/ScopesAuthoriser.cs index 4b999c10..6d7a8d57 100644 --- a/src/Ocelot/Authorisation/ScopesAuthoriser.cs +++ b/src/Ocelot/Authorisation/ScopesAuthoriser.cs @@ -1,47 +1,47 @@ -using IdentityModel; -using Ocelot.Responses; -using System.Collections.Generic; -using System.Security.Claims; -using System.Linq; - -namespace Ocelot.Authorisation -{ - using Infrastructure.Claims.Parser; - - public class ScopesAuthoriser : IScopesAuthoriser - { - private readonly IClaimsParser _claimsParser; - - public ScopesAuthoriser(IClaimsParser claimsParser) - { - _claimsParser = claimsParser; - } - - public Response Authorise(ClaimsPrincipal claimsPrincipal, List routeAllowedScopes) - { - if (routeAllowedScopes == null || routeAllowedScopes.Count == 0) - { - return new OkResponse(true); - } - - var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, JwtClaimTypes.Scope); - - if (values.IsError) - { - return new ErrorResponse(values.Errors); - } - - var userScopes = values.Data; - - var matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList(); - - if (matchesScopes.Count == 0) - { - return new ErrorResponse( - new ScopeNotAuthorisedError($"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'")); - } - - return new OkResponse(true); - } - } -} +using Ocelot.Responses; +using System.Collections.Generic; +using System.Security.Claims; +using System.Linq; + +namespace Ocelot.Authorisation +{ + using Infrastructure.Claims.Parser; + + public class ScopesAuthoriser : IScopesAuthoriser + { + private readonly IClaimsParser _claimsParser; + private readonly string _scope = "scope"; + + public ScopesAuthoriser(IClaimsParser claimsParser) + { + _claimsParser = claimsParser; + } + + public Response Authorise(ClaimsPrincipal claimsPrincipal, List routeAllowedScopes) + { + if (routeAllowedScopes == null || routeAllowedScopes.Count == 0) + { + return new OkResponse(true); + } + + var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, _scope); + + if (values.IsError) + { + return new ErrorResponse(values.Errors); + } + + var userScopes = values.Data; + + var matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList(); + + if (matchesScopes.Count == 0) + { + return new ErrorResponse( + new ScopeNotAuthorisedError($"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'")); + } + + return new OkResponse(true); + } + } +} diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index 2a5abd05..5e22f8f6 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -1,6 +1,5 @@ using System; using System.Net.Http; -using IdentityServer4.AccessTokenValidation; using Ocelot.Middleware.Multiplexer; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; @@ -13,10 +12,6 @@ namespace Ocelot.DependencyInjection IConfiguration Configuration { get; } - IOcelotAdministrationBuilder AddAdministration(string path, string secret); - - IOcelotAdministrationBuilder AddAdministration(string path, Action configOptions); - IOcelotBuilder AddDelegatingHandler(bool global = false) where T : DelegatingHandler; diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index d155bc1e..80c8ae29 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -1,6 +1,5 @@ namespace Ocelot.DependencyInjection { - using IdentityServer4.Models; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -31,10 +30,8 @@ namespace Ocelot.DependencyInjection using Ocelot.ServiceDiscovery; using System; using System.Collections.Generic; - using System.IdentityModel.Tokens.Jwt; using System.Reflection; using System.Security.Cryptography.X509Certificates; - using IdentityServer4.AccessTokenValidation; using Microsoft.AspNetCore.Builder; using Ocelot.Configuration; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -141,35 +138,6 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); } - public IOcelotAdministrationBuilder AddAdministration(string path, string secret) - { - var administrationPath = new AdministrationPath(path); - - //add identity server for admin area - var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(secret); - - if (identityServerConfiguration != null) - { - AddIdentityServer(identityServerConfiguration, administrationPath); - } - - Services.AddSingleton(administrationPath); - return new OcelotAdministrationBuilder(Services, Configuration); - } - - public IOcelotAdministrationBuilder AddAdministration(string path, Action configureOptions) - { - var administrationPath = new AdministrationPath(path); - - if (configureOptions != null) - { - AddIdentityServer(configureOptions); - } - - Services.AddSingleton(administrationPath); - return new OcelotAdministrationBuilder(Services, Configuration); - } - public IOcelotBuilder AddSingletonDefinedAggregator() where T : class, IDefinedAggregator { @@ -202,88 +170,5 @@ namespace Ocelot.DependencyInjection return this; } - - private void AddIdentityServer(Action configOptions) - { - Services - .AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) - .AddIdentityServerAuthentication(configOptions); - } - - private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath) - { - Services.TryAddSingleton(identityServerConfiguration); - var identityServerBuilder = Services - .AddIdentityServer(o => { - o.IssuerUri = "Ocelot"; - }) - .AddInMemoryApiResources(Resources(identityServerConfiguration)) - .AddInMemoryClients(Client(identityServerConfiguration)); - - var urlFinder = new BaseUrlFinder(Configuration); - var baseSchemeUrlAndPort = urlFinder.Find(); - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - - Services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) - .AddIdentityServerAuthentication(o => - { - o.Authority = baseSchemeUrlAndPort + adminPath.Path; - o.ApiName = identityServerConfiguration.ApiName; - o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = identityServerConfiguration.ApiSecret; - }); - - //todo - refactor naming.. - if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword)) - { - identityServerBuilder.AddDeveloperSigningCredential(); - } - else - { - //todo - refactor so calls method? - var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword); - identityServerBuilder.AddSigningCredential(cert); - } - } - - private List Resources(IIdentityServerConfiguration identityServerConfiguration) - { - return new List - { - new ApiResource(identityServerConfiguration.ApiName, identityServerConfiguration.ApiName) - { - ApiSecrets = new List - { - new Secret - { - Value = identityServerConfiguration.ApiSecret.Sha256() - } - } - }, - }; - } - - private List Client(IIdentityServerConfiguration identityServerConfiguration) - { - return new List - { - new Client - { - ClientId = identityServerConfiguration.ApiName, - AllowedGrantTypes = GrantTypes.ClientCredentials, - ClientSecrets = new List {new Secret(identityServerConfiguration.ApiSecret.Sha256())}, - AllowedScopes = { identityServerConfiguration.ApiName } - } - }; - } - - private static bool UsingEurekaServiceDiscoveryProvider(IConfiguration configurationRoot) - { - var type = configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Type", - string.Empty); - - return type.ToLower() == "eureka"; - } } } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 6fe5a1df..14877d4f 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -37,8 +37,6 @@ { var configuration = await CreateConfiguration(builder); - CreateAdministrationArea(builder, configuration); - ConfigureDiagnosticListener(builder); return CreateOcelotPipeline(builder, pipelineConfiguration); @@ -153,25 +151,6 @@ throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}"); } - private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration) - { - if (!string.IsNullOrEmpty(configuration.AdministrationPath)) - { - builder.Map(configuration.AdministrationPath, app => - { - //todo - hack so we know that we are using internal identity server - var identityServerConfiguration = builder.ApplicationServices.GetService(); - if (identityServerConfiguration != null) - { - app.UseIdentityServer(); - } - - app.UseAuthentication(); - app.UseMvc(); - }); - } - } - private static void ConfigureDiagnosticListener(IApplicationBuilder builder) { var env = builder.ApplicationServices.GetService(); diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index 7a2cdf58..910c4ce5 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -27,7 +27,6 @@ - @@ -47,6 +46,5 @@ - diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index b1bbf06e..15d5e2b3 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -52,5 +52,7 @@ + + diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs deleted file mode 100644 index f3835b0c..00000000 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ /dev/null @@ -1,837 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Security.Claims; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; -using IdentityServer4.Test; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Ocelot.Cache; -using Ocelot.Configuration.File; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -[assembly: CollectionBehavior(DisableTestParallelization = true)] -namespace Ocelot.IntegrationTests -{ - public class AdministrationTests : IDisposable - { - private HttpClient _httpClient; - private readonly HttpClient _httpClientTwo; - private HttpResponseMessage _response; - private IWebHost _builder; - private IWebHostBuilder _webHostBuilder; - private string _ocelotBaseUrl; - private BearerToken _token; - private IWebHostBuilder _webHostBuilderTwo; - private IWebHost _builderTwo; - private IWebHost _identityServerBuilder; - private IWebHost _fooServiceBuilder; - private IWebHost _barServiceBuilder; - - public AdministrationTests() - { - _httpClient = new HttpClient(); - _httpClientTwo = new HttpClient(); - _ocelotBaseUrl = "http://localhost:5000"; - _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); - } - - [Fact] - public void should_return_response_401_with_call_re_routes_controller() - { - var configuration = new FileConfiguration(); - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_call_re_routes_controller() - { - var configuration = new FileConfiguration(); - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_call_re_routes_controller_using_base_url_added_in_file_config() - { - _httpClient = new HttpClient(); - _ocelotBaseUrl = "http://localhost:5011"; - _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); - - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - BaseUrl = _ocelotBaseUrl - } - }; - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunningWithNoWebHostBuilder(_ocelotBaseUrl)) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_be_able_to_use_token_from_ocelot_a_on_ocelot_b() - { - var configuration = new FileConfiguration(); - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenIdentityServerSigningEnvironmentalVariablesAreSet()) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenAnotherOcelotIsRunning("http://localhost:5007")) - .When(x => WhenIGetUrlOnTheSecondOcelot("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_return_file_configuration() - { - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - RequestIdKey = "RequestId", - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Host = "127.0.0.1", - } - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10, - Region = "Geoff" - } - }, - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/test", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10, - Region = "Dave" - } - } - } - }; - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => ThenTheResponseShouldBe(configuration)) - .BDDfy(); - } - - [Fact] - public void should_get_file_configuration_edit_and_post_updated_version() - { - var initialConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" - }, - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/test" - } - } - }; - - var updatedConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "http", - DownstreamPathTemplate = "/geoffrey", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" - }, - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "123.123.123", - Port = 443, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/blooper/{productId}", - UpstreamHttpMethod = new List { "post" }, - UpstreamPathTemplate = "/test" - } - } - }; - - this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration)) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => ThenTheResponseShouldBe(updatedConfiguration)) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .And(x => ThenTheResponseShouldBe(updatedConfiguration)) - .And(_ => ThenTheConfigurationIsSavedCorrectly(updatedConfiguration)) - .BDDfy(); - } - - private void ThenTheConfigurationIsSavedCorrectly(FileConfiguration expected) - { - var ocelotJsonPath = $"{AppContext.BaseDirectory}ocelot.json"; - var resultText = File.ReadAllText(ocelotJsonPath); - var expectedText = JsonConvert.SerializeObject(expected, Formatting.Indented); - resultText.ShouldBe(expectedText); - - var environmentSpecificPath = $"{AppContext.BaseDirectory}/ocelot.Production.json"; - resultText = File.ReadAllText(environmentSpecificPath); - expectedText = JsonConvert.SerializeObject(expected, Formatting.Indented); - resultText.ShouldBe(expectedText); - } - - [Fact] - public void should_get_file_configuration_edit_and_post_updated_version_redirecting_reroute() - { - var fooPort = 47689; - var barPort = 47690; - - var initialConfiguration = new FileConfiguration - { - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = fooPort, - } - }, - DownstreamScheme = "http", - DownstreamPathTemplate = "/foo", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/foo" - } - } - }; - - var updatedConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = barPort, - } - }, - DownstreamScheme = "http", - DownstreamPathTemplate = "/bar", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/foo" - } - } - }; - - this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) - .And(x => GivenThereIsAFooServiceRunningOn($"http://localhost:{fooPort}")) - .And(x => GivenThereIsABarServiceRunningOn($"http://localhost:{barPort}")) - .And(x => GivenOcelotIsRunning()) - .And(x => WhenIGetUrlOnTheApiGateway("/foo")) - .Then(x => ThenTheResponseBodyShouldBe("foo")) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration)) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => ThenTheResponseShouldBe(updatedConfiguration)) - .And(x => WhenIGetUrlOnTheApiGateway("/foo")) - .Then(x => ThenTheResponseBodyShouldBe("bar")) - .When(x => WhenIPostOnTheApiGateway("/administration/configuration", initialConfiguration)) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => ThenTheResponseShouldBe(initialConfiguration)) - .And(x => WhenIGetUrlOnTheApiGateway("/foo")) - .Then(x => ThenTheResponseBodyShouldBe("foo")) - .BDDfy(); - } - - [Fact] - public void should_clear_region() - { - var initialConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10 - } - }, - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/test", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10 - } - } - } - }; - - var regionToClear = "gettest"; - - this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIDeleteOnTheApiGateway($"/administration/outputcache/{regionToClear}")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NoContent)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_call_re_routes_controller_when_using_own_identity_server_to_secure_admin_area() - { - var configuration = new FileConfiguration(); - - var identityServerRootUrl = "http://localhost:5123"; - - Action options = o => { - o.Authority = identityServerRootUrl; - o.ApiName = "api"; - o.RequireHttpsMetadata = false; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = "secret"; - }; - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenThereIsAnIdentityServerOn(identityServerRootUrl, "api")) - .And(x => GivenOcelotIsRunningWithIdentityServerSettings(options)) - .And(x => GivenIHaveAToken(identityServerRootUrl)) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - private void GivenIHaveAToken(string url) - { - var formData = new List> - { - new KeyValuePair("client_id", "api"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "api"), - new KeyValuePair("username", "test"), - new KeyValuePair("password", "test"), - new KeyValuePair("grant_type", "password") - }; - var content = new FormUrlEncodedContent(formData); - - using (var httpClient = new HttpClient()) - { - var response = httpClient.PostAsync($"{url}/connect/token", content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - } - } - - private void GivenThereIsAnIdentityServerOn(string url, string apiName) - { - _identityServerBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureServices(services => - { - services.AddLogging(); - services.AddIdentityServer() - .AddDeveloperSigningCredential() - .AddInMemoryApiResources(new List - { - new ApiResource - { - Name = apiName, - Description = apiName, - Enabled = true, - DisplayName = apiName, - Scopes = new List() - { - new Scope(apiName) - } - } - }) - .AddInMemoryClients(new List - { - new Client - { - ClientId = apiName, - AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret("secret".Sha256())}, - AllowedScopes = new List { apiName }, - AccessTokenType = AccessTokenType.Jwt, - Enabled = true - } - }) - .AddTestUsers(new List - { - new TestUser - { - Username = "test", - Password = "test", - SubjectId = "1231231" - } - }); - }) - .Configure(app => - { - app.UseIdentityServer(); - }) - .Build(); - - _identityServerBuilder.Start(); - - using (var httpClient = new HttpClient()) - { - var response = httpClient.GetAsync($"{url}/.well-known/openid-configuration").Result; - response.EnsureSuccessStatusCode(); - } - } - - private void GivenAnotherOcelotIsRunning(string baseUrl) - { - _httpClientTwo.BaseAddress = new Uri(baseUrl); - - _webHostBuilderTwo = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", false, false); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - x.AddOcelot() - .AddAdministration("/administration", "secret"); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _builderTwo = _webHostBuilderTwo.Build(); - - _builderTwo.Start(); - } - - private void GivenIdentityServerSigningEnvironmentalVariablesAreSet() - { - Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "idsrv3test.pfx"); - Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "idsrv3test"); - } - - private void WhenIGetUrlOnTheSecondOcelot(string url) - { - _httpClientTwo.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - _response = _httpClientTwo.GetAsync(url).Result; - } - - private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) - { - var json = JsonConvert.SerializeObject(updatedConfiguration); - var content = new StringContent(json); - content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - _response = _httpClient.PostAsync(url, content).Result; - } - - private void ThenTheResponseShouldBe(List expected) - { - var content = _response.Content.ReadAsStringAsync().Result; - var result = JsonConvert.DeserializeObject(content); - result.Value.ShouldBe(expected); - } - - private void ThenTheResponseBodyShouldBe(string expected) - { - var content = _response.Content.ReadAsStringAsync().Result; - content.ShouldBe(expected); - } - - private void ThenTheResponseShouldBe(FileConfiguration expecteds) - { - var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); - - response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); - response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); - response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for (var i = 0; i < response.ReRoutes.Count; i++) - { - for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++) - { - var result = response.ReRoutes[i].DownstreamHostAndPorts[j]; - var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; - result.Host.ShouldBe(expected.Host); - result.Port.ShouldBe(expected.Port); - } - - response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); - response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); - response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].UpstreamPathTemplate); - response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.ReRoutes[i].UpstreamHttpMethod); - } - } - - private void GivenIHaveAddedATokenToMyRequest() - { - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - } - - private void GivenIHaveAnOcelotToken(string adminPath) - { - var tokenUrl = $"{adminPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "admin"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "admin"), - new KeyValuePair("grant_type", "client_credentials") - }; - var content = new FormUrlEncodedContent(formData); - - var response = _httpClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - var configPath = $"{adminPath}/.well-known/openid-configuration"; - response = _httpClient.GetAsync(configPath).Result; - response.EnsureSuccessStatusCode(); - } - - private void GivenOcelotIsRunningWithIdentityServerSettings(Action configOptions) - { - _webHostBuilder = new WebHostBuilder() - .UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", false, false); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => { - x.AddSingleton(_webHostBuilder); - x.AddOcelot() - .AddAdministration("/administration", configOptions); - }) - .Configure(app => { - app.UseOcelot().Wait(); - }); - - _builder = _webHostBuilder.Build(); - - _builder.Start(); - } - - private void GivenOcelotIsRunning() - { - _webHostBuilder = new WebHostBuilder() - .UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", false, false); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - x.AddOcelot() - .AddAdministration("/administration", "secret"); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _builder = _webHostBuilder.Build(); - - _builder.Start(); - } - - private void GivenOcelotIsRunningWithNoWebHostBuilder(string baseUrl) - { - _webHostBuilder = new WebHostBuilder() - .UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); - config.AddJsonFile("ocelot.json", false, false); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => { - x.AddSingleton(_webHostBuilder); - x.AddOcelot() - .AddAdministration("/administration", "secret"); - }) - .Configure(app => { - app.UseOcelot().Wait(); - }); - - _builder = _webHostBuilder.Build(); - - _builder.Start(); - } - - private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - var text = File.ReadAllText(configurationPath); - - configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - text = File.ReadAllText(configurationPath); - } - - private void WhenIGetUrlOnTheApiGateway(string url) - { - _response = _httpClient.GetAsync(url).Result; - } - - private void WhenIDeleteOnTheApiGateway(string url) - { - _response = _httpClient.DeleteAsync(url).Result; - } - - private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) - { - _response.StatusCode.ShouldBe(expectedHttpStatusCode); - } - - public void Dispose() - { - Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", ""); - Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", ""); - _builder?.Dispose(); - _httpClient?.Dispose(); - _identityServerBuilder?.Dispose(); - } - - private void GivenThereIsAFooServiceRunningOn(string baseUrl) - { - _fooServiceBuilder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.UsePathBase("/foo"); - app.Run(async context => - { - context.Response.StatusCode = 200; - await context.Response.WriteAsync("foo"); - }); - }) - .Build(); - - _fooServiceBuilder.Start(); - } - - private void GivenThereIsABarServiceRunningOn(string baseUrl) - { - _barServiceBuilder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.UsePathBase("/bar"); - app.Run(async context => - { - context.Response.StatusCode = 200; - await context.Response.WriteAsync("bar"); - }); - }) - .Build(); - - _barServiceBuilder.Start(); - } - } -} diff --git a/test/Ocelot.IntegrationTests/BearerToken.cs b/test/Ocelot.IntegrationTests/BearerToken.cs deleted file mode 100644 index 02180844..00000000 --- a/test/Ocelot.IntegrationTests/BearerToken.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Newtonsoft.Json; - -namespace Ocelot.IntegrationTests -{ - 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/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj index 1d262f6f..716744f1 100644 --- a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj @@ -39,7 +39,6 @@ - diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index a1a29f52..1e9c0dd8 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -111,8 +111,7 @@ namespace Ocelot.IntegrationTests }) .ConfigureServices(x => { - x.AddOcelot() - .AddAdministration("/administration", "secret"); + x.AddOcelot(); }) .Configure(app => { diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index 865c11e0..2a43e886 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -8,7 +8,6 @@ using Ocelot.DependencyInjection; using Ocelot.Middleware; using System; - using IdentityServer4.AccessTokenValidation; using System.Net.Http; using System.Threading.Tasks; using System.Threading; @@ -38,17 +37,17 @@ }); s.AddOcelot() - .AddDelegatingHandler(true) + .AddDelegatingHandler(true); // .AddCacheManager(x => // { // x.WithDictionaryHandle(); // }) - /*.AddOpenTracing(option => - { - option.CollectorUrl = "http://localhost:9618"; - option.Service = "Ocelot.ManualTest"; - })*/ - .AddAdministration("/administration", "secret"); + // .AddOpenTracing(option => + // { + // option.CollectorUrl = "http://localhost:9618"; + // option.Service = "Ocelot.ManualTest"; + // }) + // .AddAdministration("/administration", "secret"); }) .ConfigureLogging((hostingContext, logging) => { diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs index 1be11401..d9ad9768 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs @@ -1,94 +1,94 @@ -using Xunit; - -[assembly: CollectionBehavior(DisableTestParallelization = true)] - -namespace Ocelot.UnitTests.Authentication -{ - using System.Collections.Generic; - using System.IO; - using System.Text; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Authentication.Middleware; - using Ocelot.Configuration.Builder; - using Ocelot.Logging; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - using Ocelot.Configuration; +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] + +namespace Ocelot.UnitTests.Authentication +{ + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Authentication.Middleware; + using Ocelot.Configuration.Builder; + using Ocelot.Logging; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + using Ocelot.Configuration; using Ocelot.Middleware; - - public class AuthenticationMiddlewareTests - { - private AuthenticationMiddleware _middleware; - private readonly Mock _factory; - private Mock _logger; - private OcelotRequestDelegate _next; - private readonly DownstreamContext _downstreamContext; - - public AuthenticationMiddlewareTests() - { - _factory = new Mock(); - _logger = new Mock(); - _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - } - - [Fact] - public void should_call_next_middleware_if_route_is_not_authenticated() - { - this.Given(x => GivenTheDownStreamRouteIs( - new DownstreamReRouteBuilder().WithUpstreamHttpMethod(new List { "Get" }).Build())) - .And(x => GivenTheTestServerPipelineIsConfigured()) - .When(x => WhenICallTheMiddleware()) - .Then(x => ThenTheUserIsAuthenticated()) - .BDDfy(); - } - - private void WhenICallTheMiddleware() - { - _next = (context) => { - byte[] byteArray = Encoding.ASCII.GetBytes("The user is authenticated"); - var stream = new MemoryStream(byteArray); - context.HttpContext.Response.Body = stream; - return Task.CompletedTask; - }; - _middleware = new AuthenticationMiddleware(_next, _factory.Object); - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenTheTestServerPipelineIsConfigured() - { - _next = (context) => { - byte[] byteArray = Encoding.ASCII.GetBytes("The user is authenticated"); - var stream = new MemoryStream(byteArray); - context.HttpContext.Response.Body = stream; - return Task.CompletedTask; - }; - } - - private void ThenTheUserIsAuthenticated() - { - var content = _downstreamContext.HttpContext.Response.Body.AsString(); - content.ShouldBe("The user is authenticated"); - } - - private void GivenTheDownStreamRouteIs(DownstreamReRoute downstreamRoute) - { - _downstreamContext.DownstreamReRoute = downstreamRoute; - } - } - - public static class StreamExtensions - { - public static string AsString(this Stream stream) - { - using(var reader = new StreamReader(stream)) - { - string text = reader.ReadToEnd(); - return text; - } - } - } -} + + public class AuthenticationMiddlewareTests + { + private AuthenticationMiddleware _middleware; + private readonly Mock _factory; + private Mock _logger; + private OcelotRequestDelegate _next; + private readonly DownstreamContext _downstreamContext; + + public AuthenticationMiddlewareTests() + { + _factory = new Mock(); + _logger = new Mock(); + _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + } + + [Fact] + public void should_call_next_middleware_if_route_is_not_authenticated() + { + this.Given(x => GivenTheDownStreamRouteIs( + new DownstreamReRouteBuilder().WithUpstreamHttpMethod(new List { "Get" }).Build())) + .And(x => GivenTheTestServerPipelineIsConfigured()) + .When(x => WhenICallTheMiddleware()) + .Then(x => ThenTheUserIsAuthenticated()) + .BDDfy(); + } + + private void WhenICallTheMiddleware() + { + _next = (context) => { + byte[] byteArray = Encoding.ASCII.GetBytes("The user is authenticated"); + var stream = new MemoryStream(byteArray); + context.HttpContext.Response.Body = stream; + return Task.CompletedTask; + }; + _middleware = new AuthenticationMiddleware(_next, _factory.Object); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + + private void GivenTheTestServerPipelineIsConfigured() + { + _next = (context) => { + byte[] byteArray = Encoding.ASCII.GetBytes("The user is authenticated"); + var stream = new MemoryStream(byteArray); + context.HttpContext.Response.Body = stream; + return Task.CompletedTask; + }; + } + + private void ThenTheUserIsAuthenticated() + { + var content = _downstreamContext.HttpContext.Response.Body.AsString(); + content.ShouldBe("The user is authenticated"); + } + + private void GivenTheDownStreamRouteIs(DownstreamReRoute downstreamRoute) + { + _downstreamContext.DownstreamReRoute = downstreamRoute; + } + } + + public static class StreamExtensions + { + public static string AsString(this Stream stream) + { + using(var reader = new StreamReader(stream)) + { + string text = reader.ReadToEnd(); + return text; + } + } + } +} diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 7abe40e6..6459daf9 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -13,7 +13,6 @@ namespace Ocelot.UnitTests.DependencyInjection using Ocelot.Requester; using Ocelot.UnitTests.Requester; using Shouldly; - using IdentityServer4.AccessTokenValidation; using TestStack.BDDfy; using Xunit; using static Ocelot.UnitTests.Middleware.UserDefinedResponseAggregatorTests; @@ -75,28 +74,6 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } - [Fact] - public void should_set_up_administration_with_identity_server_options() - { - Action options = o => {}; - - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpAdministration(options)) - .Then(x => ThenAnExceptionIsntThrown()) - .Then(x => ThenTheCorrectAdminPathIsRegitered()) - .BDDfy(); - } - - [Fact] - public void should_set_up_administration() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpAdministration()) - .Then(x => ThenAnExceptionIsntThrown()) - .Then(x => ThenTheCorrectAdminPathIsRegitered()) - .BDDfy(); - } - [Fact] public void should_use_logger_factory() { @@ -167,16 +144,6 @@ namespace Ocelot.UnitTests.DependencyInjection 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 { @@ -189,13 +156,6 @@ namespace Ocelot.UnitTests.DependencyInjection _ocelotBuilder.AddDelegatingHandler(); } - private void ThenTheCorrectAdminPathIsRegitered() - { - _serviceProvider = _services.BuildServiceProvider(); - var path = _serviceProvider.GetService(); - path.Path.ShouldBe("/administration"); - } - private void ThenTheProviderIsRegisteredAndReturnsHandlers() { _serviceProvider = _services.BuildServiceProvider(); From 373a3927f2fbb57d689359be1228af9a752fed86 Mon Sep 17 00:00:00 2001 From: sa Date: Tue, 14 Aug 2018 22:51:10 +0530 Subject: [PATCH 32/50] Small spelling correction (#550) --- docs/features/raft.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/raft.rst b/docs/features/raft.rst index 047bd347..e3793b1f 100644 --- a/docs/features/raft.rst +++ b/docs/features/raft.rst @@ -1,7 +1,7 @@ Raft (EXPERIMENTAL DO NOT USE IN PRODUCTION) ============================================ -Ocelot has recenely integrated `Rafty `_ which is an implementation of Raft that I have also been working on over the last year. This project is very experimental so please do not use this feature of Ocelot in production until I think it's OK. +Ocelot has recently integrated `Rafty `_ which is an implementation of Raft that I have also been working on over the last year. This project is very experimental so please do not use this feature of Ocelot in production until I think it's OK. Raft is a distributed concensus algorythm that allows a cluster of servers (Ocelots) to maintain local state without having a centralised database for storing state (e.g. SQL Server). From 18ded25b0eba4dabce88ebc397482f63bdad9855 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Tue, 14 Aug 2018 18:39:18 +0100 Subject: [PATCH 33/50] Feature/remove id server (#551) * removed idserver4 * some files I missed removing --- .../IdentityServerConfigurationCreator.cs | 23 -------------- .../File/FileIdentityServerConfig.cs | 10 ------- .../IIdentityServerConfiguration.cs | 14 --------- .../IdentityServerConfiguration.cs | 30 ------------------- ...IdentityServerConfigurationCreatorTests.cs | 16 ---------- 5 files changed, 93 deletions(-) delete mode 100644 src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs delete mode 100644 src/Ocelot/Configuration/File/FileIdentityServerConfig.cs delete mode 100644 src/Ocelot/Configuration/IIdentityServerConfiguration.cs delete mode 100644 src/Ocelot/Configuration/IdentityServerConfiguration.cs delete mode 100644 test/Ocelot.UnitTests/Configuration/IdentityServerConfigurationCreatorTests.cs diff --git a/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs deleted file mode 100644 index 8569001e..00000000 --- a/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Ocelot.Configuration.Creator -{ - public static class IdentityServerConfigurationCreator - { - public static IdentityServerConfiguration GetIdentityServerConfiguration(string secret) - { - var credentialsSigningCertificateLocation = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE"); - var credentialsSigningCertificatePassword = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD"); - - return new IdentityServerConfiguration( - "admin", - false, - secret, - new List { "admin", "openid", "offline_access" }, - credentialsSigningCertificateLocation, - credentialsSigningCertificatePassword - ); - } - } -} diff --git a/src/Ocelot/Configuration/File/FileIdentityServerConfig.cs b/src/Ocelot/Configuration/File/FileIdentityServerConfig.cs deleted file mode 100644 index a07edab1..00000000 --- a/src/Ocelot/Configuration/File/FileIdentityServerConfig.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ocelot.Configuration.File -{ - public class FileIdentityServerConfig - { - public string ProviderRootUrl { get; set; } - public string ApiName { get; set; } - public bool RequireHttps { get; set; } - public string ApiSecret { get; set; } - } -} \ No newline at end of file diff --git a/src/Ocelot/Configuration/IIdentityServerConfiguration.cs b/src/Ocelot/Configuration/IIdentityServerConfiguration.cs deleted file mode 100644 index 0eb70347..00000000 --- a/src/Ocelot/Configuration/IIdentityServerConfiguration.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ocelot.Configuration -{ - using System.Collections.Generic; - - public interface IIdentityServerConfiguration - { - string ApiName { get; } - string ApiSecret { get; } - bool RequireHttps { get; } - List AllowedScopes { get; } - string CredentialsSigningCertificateLocation { get; } - string CredentialsSigningCertificatePassword { get; } - } -} diff --git a/src/Ocelot/Configuration/IdentityServerConfiguration.cs b/src/Ocelot/Configuration/IdentityServerConfiguration.cs deleted file mode 100644 index b8b00ea2..00000000 --- a/src/Ocelot/Configuration/IdentityServerConfiguration.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Ocelot.Configuration -{ - using System.Collections.Generic; - - public class IdentityServerConfiguration : IIdentityServerConfiguration - { - public IdentityServerConfiguration( - string apiName, - bool requireHttps, - string apiSecret, - List allowedScopes, - string credentialsSigningCertificateLocation, - string credentialsSigningCertificatePassword) - { - ApiName = apiName; - RequireHttps = requireHttps; - ApiSecret = apiSecret; - AllowedScopes = allowedScopes; - CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation; - CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword; - } - - public string ApiName { get; } - public bool RequireHttps { get; } - public List AllowedScopes { get; } - public string ApiSecret { get; } - public string CredentialsSigningCertificateLocation { get; } - public string CredentialsSigningCertificatePassword { get; } - } -} diff --git a/test/Ocelot.UnitTests/Configuration/IdentityServerConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/IdentityServerConfigurationCreatorTests.cs deleted file mode 100644 index 5f8d3210..00000000 --- a/test/Ocelot.UnitTests/Configuration/IdentityServerConfigurationCreatorTests.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Ocelot.Configuration.Creator; -using Shouldly; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class IdentityServerConfigurationCreatorTests - { - [Fact] - public void happy_path_only_exists_for_test_coverage_even_uncle_bob_probably_wouldnt_test_this() - { - var result = IdentityServerConfigurationCreator.GetIdentityServerConfiguration("secret"); - result.ApiName.ShouldBe("admin"); - } - } -} \ No newline at end of file From e909cf9ce7e6b761680e4d3a54f52e114b470f00 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Tue, 14 Aug 2018 20:50:51 +0100 Subject: [PATCH 34/50] updated docs to advise you need Ocelot.Administration when using admin API (#552) --- docs/features/administration.rst | 272 ++++++++++++++++--------------- 1 file changed, 139 insertions(+), 133 deletions(-) diff --git a/docs/features/administration.rst b/docs/features/administration.rst index 33a1e1cb..29dc142b 100644 --- a/docs/features/administration.rst +++ b/docs/features/administration.rst @@ -1,133 +1,139 @@ -Administration -============== - -Ocelot supports changing configuration during runtime via an authenticated HTTP API. This can be authenticated in two ways either using Ocelot's -internal IdentityServer (for authenticating requests to the administration API only) or hooking the administration API authentication into your own -IdentityServer. - -Providing your own IdentityServer -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -All you need to do to hook into your own IdentityServer is add the following to your ConfigureServices method. - -.. code-block:: csharp - - public virtual void ConfigureServices(IServiceCollection services) - { - Action options = o => { - // o.Authority = ; - // o.ApiName = ; - // etc.... - }; - - services - .AddOcelot() - .AddAdministration("/administration", options); - } - -You now need to get a token from your IdentityServer and use in subsequent requests to Ocelot's administration API. - -This feature was implemented for `issue 228 `_. It is useful because the IdentityServer authentication -middleware needs the URL of the IdentityServer. If you are using the internal IdentityServer it might not alaways be possible to have the Ocelot URL. - -Internal IdentityServer -^^^^^^^^^^^^^^^^^^^^^^^ - -The API is authenticated using bearer tokens that you request from Ocelot iteself. This is provided by the amazing -`Identity Server `_ project that I have been using for a few years now. Check them out. - -In order to enable the administration section you need to do a few things. First of all add this to your -initial Startup.cs. - -The path can be anything you want and it is obviously reccomended don't use -a url you would like to route through with Ocelot as this will not work. The administration uses the -MapWhen functionality of asp.net core and all requests to {root}/administration will be sent there not -to the Ocelot middleware. - -The secret is the client secret that Ocelot's internal IdentityServer will use to authenticate requests to the administration API. This can be whatever you want it to be! - -.. code-block:: csharp - - public virtual void ConfigureServices(IServiceCollection services) - { - services - .AddOcelot() - .AddAdministration("/administration", "secret"); - } - -In order for the administration API to work, Ocelot / IdentityServer must be able to call itself for validation. This means that you need to add the base url of Ocelot -to global configuration if it is not default (http://localhost:5000). This can be done as follows.. - -If you want to run on a different host and port locally.. - -.. code-block:: json - - "GlobalConfiguration": { - "BaseUrl": "http://localhost:55580" - } - -or if Ocelot is exposed via dns - -.. code-block:: json - - "GlobalConfiguration": { - "BaseUrl": "http://mydns.com" - } - -Now if you went with the configuration options above and want to access the API you can use the postman scripts -called ocelot.postman_collection.json in the solution to change the Ocelot configuration. Obviously these -will need to be changed if you are running Ocelot on a different url to http://localhost:5000. - - -The scripts show you how to request a bearer token from ocelot and then use it to GET the existing configuration and POST -a configuration. - -If you are running multiple Ocelot instances in a cluster then you need to use a certificate to sign the bearer tokens used to access the administration API. - -In order to do this you need to add two more environmental variables for each Ocelot in the cluster. - -``OCELOT_CERTIFICATE`` - The path to a certificate that can be used to sign the tokens. The certificate needs to be of the type X509 and obviously Ocelot needs to be able to access it. -``OCELOT_CERTIFICATE_PASSWORD`` - The password for the certificate. - -Normally Ocelot just uses temporary signing credentials but if you set these environmental variables then it will use the certificate. If all the other Ocelot instances in the cluster have the same certificate then you are good! - - -Administration API -^^^^^^^^^^^^^^^^^^ - -**POST {adminPath}/connect/token** - -This gets a token for use with the admin area using the client credentials we talk about setting above. Under the hood this calls into an IdentityServer hosted within Ocelot. - -The body of the request is form-data as follows - -``client_id`` set as admin - -``client_secret`` set as whatever you used when setting up the administration services. - -``scope`` set as admin - -``grant_type`` set as client_credentials - -**GET {adminPath}/configuration** - - -This gets the current Ocelot configuration. It is exactly the same JSON we use to set Ocelot up with in the first place. - -**POST {adminPath}/configuration** - -This overrwrites the existing configuration (should probably be a put!). I reccomend getting your config from the GET endpoint, making any changes and posting it back...simples. - -The body of the request is JSON and it is the same format as the FileConfiguration.cs that we use to set up -Ocelot on a file system. - -Please note that if you want to use this API then the process running Ocelot must have permission to write to the disk -where your ocelot.json or ocelot.{environment}.json is located. This is because Ocelot will overwrite them on save. - -**DELETE {adminPath}/outputcache/{region}** - -This clears a region of the cache. If you are using a backplane it will clear all instances of the cache! Giving your the ability to run a cluster of Ocelots and cache over all of them in memory and clear them all at the same time / just use a distributed cache. - -The region is whatever you set against the Region field in the FileCacheOptions section of the Ocelot configuration. +Administration +============== + +Ocelot supports changing configuration during runtime via an authenticated HTTP API. This can be authenticated in two ways either using Ocelot's +internal IdentityServer (for authenticating requests to the administration API only) or hooking the administration API authentication into your own +IdentityServer. + +The first thing you need to do if you want to use the administration API is bring in the relavent NuGet package.. + +``Install-Package Ocelot.Administration`` + +This will bring down everything needed by the admin API. + +Providing your own IdentityServer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +All you need to do to hook into your own IdentityServer is add the following to your ConfigureServices method. + +.. code-block:: csharp + + public virtual void ConfigureServices(IServiceCollection services) + { + Action options = o => { + // o.Authority = ; + // o.ApiName = ; + // etc.... + }; + + services + .AddOcelot() + .AddAdministration("/administration", options); + } + +You now need to get a token from your IdentityServer and use in subsequent requests to Ocelot's administration API. + +This feature was implemented for `issue 228 `_. It is useful because the IdentityServer authentication +middleware needs the URL of the IdentityServer. If you are using the internal IdentityServer it might not alaways be possible to have the Ocelot URL. + +Internal IdentityServer +^^^^^^^^^^^^^^^^^^^^^^^ + +The API is authenticated using bearer tokens that you request from Ocelot iteself. This is provided by the amazing +`Identity Server `_ project that I have been using for a few years now. Check them out. + +In order to enable the administration section you need to do a few things. First of all add this to your +initial Startup.cs. + +The path can be anything you want and it is obviously reccomended don't use +a url you would like to route through with Ocelot as this will not work. The administration uses the +MapWhen functionality of asp.net core and all requests to {root}/administration will be sent there not +to the Ocelot middleware. + +The secret is the client secret that Ocelot's internal IdentityServer will use to authenticate requests to the administration API. This can be whatever you want it to be! + +.. code-block:: csharp + + public virtual void ConfigureServices(IServiceCollection services) + { + services + .AddOcelot() + .AddAdministration("/administration", "secret"); + } + +In order for the administration API to work, Ocelot / IdentityServer must be able to call itself for validation. This means that you need to add the base url of Ocelot +to global configuration if it is not default (http://localhost:5000). This can be done as follows.. + +If you want to run on a different host and port locally.. + +.. code-block:: json + + "GlobalConfiguration": { + "BaseUrl": "http://localhost:55580" + } + +or if Ocelot is exposed via dns + +.. code-block:: json + + "GlobalConfiguration": { + "BaseUrl": "http://mydns.com" + } + +Now if you went with the configuration options above and want to access the API you can use the postman scripts +called ocelot.postman_collection.json in the solution to change the Ocelot configuration. Obviously these +will need to be changed if you are running Ocelot on a different url to http://localhost:5000. + + +The scripts show you how to request a bearer token from ocelot and then use it to GET the existing configuration and POST +a configuration. + +If you are running multiple Ocelot instances in a cluster then you need to use a certificate to sign the bearer tokens used to access the administration API. + +In order to do this you need to add two more environmental variables for each Ocelot in the cluster. + +``OCELOT_CERTIFICATE`` + The path to a certificate that can be used to sign the tokens. The certificate needs to be of the type X509 and obviously Ocelot needs to be able to access it. +``OCELOT_CERTIFICATE_PASSWORD`` + The password for the certificate. + +Normally Ocelot just uses temporary signing credentials but if you set these environmental variables then it will use the certificate. If all the other Ocelot instances in the cluster have the same certificate then you are good! + + +Administration API +^^^^^^^^^^^^^^^^^^ + +**POST {adminPath}/connect/token** + +This gets a token for use with the admin area using the client credentials we talk about setting above. Under the hood this calls into an IdentityServer hosted within Ocelot. + +The body of the request is form-data as follows + +``client_id`` set as admin + +``client_secret`` set as whatever you used when setting up the administration services. + +``scope`` set as admin + +``grant_type`` set as client_credentials + +**GET {adminPath}/configuration** + + +This gets the current Ocelot configuration. It is exactly the same JSON we use to set Ocelot up with in the first place. + +**POST {adminPath}/configuration** + +This overrwrites the existing configuration (should probably be a put!). I reccomend getting your config from the GET endpoint, making any changes and posting it back...simples. + +The body of the request is JSON and it is the same format as the FileConfiguration.cs that we use to set up +Ocelot on a file system. + +Please note that if you want to use this API then the process running Ocelot must have permission to write to the disk +where your ocelot.json or ocelot.{environment}.json is located. This is because Ocelot will overwrite them on save. + +**DELETE {adminPath}/outputcache/{region}** + +This clears a region of the cache. If you are using a backplane it will clear all instances of the cache! Giving your the ability to run a cluster of Ocelots and cache over all of them in memory and clear them all at the same time / just use a distributed cache. + +The region is whatever you set against the Region field in the FileCacheOptions section of the Ocelot configuration. From 98ba0271bebf7aae4e14b2dee0ccfdcfc0bfceb5 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 19 Aug 2018 10:14:15 +0100 Subject: [PATCH 35/50] Feature/move polly (#561) * added delegate to select last handler * #529 implemented a way we can inject the last delegating handler * wip - moving code * #529 removed loads of qos code and moved it into Ocelot.Provider.Polly --- src/Ocelot/Configuration/QoSOptions.cs | 65 ++--- .../DependencyInjection/OcelotBuilder.cs | 8 +- src/Ocelot/Ocelot.csproj | 1 - .../DelegatingHandlerHandlerFactory.cs | 49 ++-- src/Ocelot/Requester/HttpClientBuilder.cs | 2 +- .../Requester/HttpClientHttpRequester.cs | 14 - .../PollyCircuitBreakingDelegatingHandler.cs | 45 --- src/Ocelot/Requester/QoS/CircuitBreaker.cs | 17 -- src/Ocelot/Requester/QoS/IQoSProvider.cs | 7 - .../Requester/QoS/IQoSProviderFactory.cs | 10 - src/Ocelot/Requester/QoS/IQosFactory.cs | 11 + src/Ocelot/Requester/QoS/IQosProviderHouse.cs | 10 - src/Ocelot/Requester/QoS/NoQoSProvider.cs | 7 - src/Ocelot/Requester/QoS/PollyQoSProvider.cs | 51 ---- .../Requester/QoS/QoSProviderFactory.cs | 25 -- src/Ocelot/Requester/QoS/QosFactory.cs | 33 +++ src/Ocelot/Requester/QoS/QosProviderHouse.cs | 53 ---- .../QoS/UnableToFindQoSProviderError.cs | 10 +- .../Requester/QosDelegatingHandlerDelegate.cs | 8 + src/Ocelot/Requester/RequestTimedOutError.cs | 13 - test/Ocelot.AcceptanceTests/QoSTests.cs | 272 ------------------ test/Ocelot.AcceptanceTests/RoutingTests.cs | 7 +- .../Ocelot.ManualTest.csproj | 3 +- ...atingHandlerHandlerProviderFactoryTests.cs | 63 ++-- .../Requester/HttpClientHttpRequesterTest.cs | 2 +- .../Requester/QoSProviderFactoryTests.cs | 48 ++-- .../Requester/QosProviderHouseTests.cs | 48 ++-- 27 files changed, 213 insertions(+), 669 deletions(-) delete mode 100644 src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs delete mode 100644 src/Ocelot/Requester/QoS/CircuitBreaker.cs delete mode 100644 src/Ocelot/Requester/QoS/IQoSProvider.cs delete mode 100644 src/Ocelot/Requester/QoS/IQoSProviderFactory.cs create mode 100644 src/Ocelot/Requester/QoS/IQosFactory.cs delete mode 100644 src/Ocelot/Requester/QoS/IQosProviderHouse.cs delete mode 100644 src/Ocelot/Requester/QoS/NoQoSProvider.cs delete mode 100644 src/Ocelot/Requester/QoS/PollyQoSProvider.cs delete mode 100644 src/Ocelot/Requester/QoS/QoSProviderFactory.cs create mode 100644 src/Ocelot/Requester/QoS/QosFactory.cs delete mode 100644 src/Ocelot/Requester/QoS/QosProviderHouse.cs create mode 100644 src/Ocelot/Requester/QosDelegatingHandlerDelegate.cs delete mode 100644 src/Ocelot/Requester/RequestTimedOutError.cs delete mode 100644 test/Ocelot.AcceptanceTests/QoSTests.cs diff --git a/src/Ocelot/Configuration/QoSOptions.cs b/src/Ocelot/Configuration/QoSOptions.cs index 3b7733a3..b0e7df7a 100644 --- a/src/Ocelot/Configuration/QoSOptions.cs +++ b/src/Ocelot/Configuration/QoSOptions.cs @@ -1,32 +1,33 @@ -using Polly.Timeout; - -namespace Ocelot.Configuration -{ - public class QoSOptions - { - public QoSOptions( - int exceptionsAllowedBeforeBreaking, - int durationofBreak, - int timeoutValue, - string key, - TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) - { - ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; - DurationOfBreak = durationofBreak; - TimeoutValue = timeoutValue; - TimeoutStrategy = timeoutStrategy; - Key = key; - } - - public int ExceptionsAllowedBeforeBreaking { get; } - - public int DurationOfBreak { get; } - - public int TimeoutValue { get; } - - public TimeoutStrategy TimeoutStrategy { get; } - - public bool UseQos => ExceptionsAllowedBeforeBreaking > 0 && TimeoutValue > 0; - public string Key { get; } - } -} +namespace Ocelot.Configuration +{ + public class QoSOptions + { + public QoSOptions( + int exceptionsAllowedBeforeBreaking, + int durationofBreak, + int timeoutValue, + string key, + //todo - this is never set in Ocelot so always Pessimistic...I guess it doesn't + //matter to much. + string timeoutStrategy = "Pessimistic") + { + ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; + DurationOfBreak = durationofBreak; + TimeoutValue = timeoutValue; + TimeoutStrategy = timeoutStrategy; + Key = key; + } + + public int ExceptionsAllowedBeforeBreaking { get; } + + public int DurationOfBreak { get; } + + public int TimeoutValue { get; } + + public string TimeoutStrategy { get; } + + public bool UseQos => ExceptionsAllowedBeforeBreaking > 0 && TimeoutValue > 0; + + public string Key { get; } + } +} diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 80c8ae29..c9b49039 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -28,17 +28,12 @@ namespace Ocelot.DependencyInjection using Ocelot.Requester.QoS; using Ocelot.Responder; using Ocelot.ServiceDiscovery; - using System; - using System.Collections.Generic; using System.Reflection; - using System.Security.Cryptography.X509Certificates; - using Microsoft.AspNetCore.Builder; using Ocelot.Configuration; using Microsoft.Extensions.DependencyInjection.Extensions; using System.Net.Http; using Ocelot.Infrastructure; using Ocelot.Middleware.Multiplexer; - using ServiceDiscovery.Providers; using Ocelot.Request.Creator; public class OcelotBuilder : IOcelotBuilder @@ -75,8 +70,6 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); @@ -136,6 +129,7 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); + Services.TryAddSingleton(); } public IOcelotBuilder AddSingletonDefinedAggregator() diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index 910c4ce5..b25af9f7 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -45,6 +45,5 @@ all - diff --git a/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs index a4263ef1..89de2840 100644 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs +++ b/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs @@ -1,31 +1,28 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Configuration; -using Ocelot.Logging; -using Ocelot.Requester.QoS; -using Ocelot.Responses; - namespace Ocelot.Requester { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.Configuration; + using Ocelot.Responses; + using QoS; + public class DelegatingHandlerHandlerFactory : IDelegatingHandlerHandlerFactory { - private readonly ITracingHandlerFactory _factory; - private readonly IOcelotLoggerFactory _loggerFactory; - private readonly IQosProviderHouse _qosProviderHouse; + private readonly ITracingHandlerFactory _tracingFactory; + private readonly IQoSFactory _qoSFactory; private readonly IServiceProvider _serviceProvider; - public DelegatingHandlerHandlerFactory(IOcelotLoggerFactory loggerFactory, - ITracingHandlerFactory factory, - IQosProviderHouse qosProviderHouse, + public DelegatingHandlerHandlerFactory( + ITracingHandlerFactory tracingFactory, + IQoSFactory qoSFactory, IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; - _factory = factory; - _loggerFactory = loggerFactory; - _qosProviderHouse = qosProviderHouse; + _tracingFactory = tracingFactory; + _qoSFactory = qoSFactory; } public Response>> Get(DownstreamReRoute request) @@ -64,19 +61,21 @@ namespace Ocelot.Requester if (request.HttpHandlerOptions.UseTracing) { - handlers.Add(() => (DelegatingHandler)_factory.Get()); + handlers.Add(() => (DelegatingHandler)_tracingFactory.Get()); } if (request.QosOptions.UseQos) { - var qosProvider = _qosProviderHouse.Get(request); + var handler = _qoSFactory.Get(request); - if (qosProvider.IsError) + if (handler != null && !handler.IsError) { - return new ErrorResponse>>(qosProvider.Errors); + handlers.Add(() => handler.Data); + } + else + { + return new ErrorResponse>>(handler?.Errors); } - - handlers.Add(() => new PollyCircuitBreakingDelegatingHandler(qosProvider.Data, _loggerFactory)); } return new OkResponse>>(handlers); diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index 748c60c4..ec44e375 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -132,7 +132,7 @@ namespace Ocelot.Requester { var cacheKey = $"{request.DownstreamRequest.Method}:{request.DownstreamRequest.OriginalString}"; - this._logger.LogDebug($"Cache key for request is {cacheKey}"); + _logger.LogDebug($"Cache key for request is {cacheKey}"); return cacheKey; } diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index c7914c94..ea557f66 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -4,8 +4,6 @@ using System.Threading.Tasks; using Ocelot.Logging; using Ocelot.Middleware; using Ocelot.Responses; -using Polly.CircuitBreaker; -using Polly.Timeout; namespace Ocelot.Requester { @@ -35,18 +33,6 @@ namespace Ocelot.Requester var response = await httpClient.SendAsync(context.DownstreamRequest.ToHttpRequestMessage()); return new OkResponse(response); } - catch (TimeoutRejectedException exception) - { - return new ErrorResponse(new RequestTimedOutError(exception)); - } - catch (TaskCanceledException exception) - { - return new ErrorResponse(new RequestTimedOutError(exception)); - } - catch (BrokenCircuitException exception) - { - return new ErrorResponse(new RequestTimedOutError(exception)); - } catch (Exception exception) { return new ErrorResponse(new UnableToCompleteRequestError(exception)); diff --git a/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs b/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs deleted file mode 100644 index 45b0ac12..00000000 --- a/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Ocelot.Logging; -using Ocelot.Requester.QoS; -using Polly; -using Polly.CircuitBreaker; -using Polly.Timeout; - -namespace Ocelot.Requester -{ - public class PollyCircuitBreakingDelegatingHandler : DelegatingHandler - { - private readonly IQoSProvider _qoSProvider; - private readonly IOcelotLogger _logger; - - public PollyCircuitBreakingDelegatingHandler( - IQoSProvider qoSProvider, - IOcelotLoggerFactory loggerFactory) - { - _qoSProvider = qoSProvider; - _logger = loggerFactory.CreateLogger(); - } - - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - try - { - return await Policy - .WrapAsync(_qoSProvider.CircuitBreaker.CircuitBreakerPolicy, _qoSProvider.CircuitBreaker.TimeoutPolicy) - .ExecuteAsync(() => base.SendAsync(request,cancellationToken)); - } - catch (BrokenCircuitException ex) - { - _logger.LogError($"Reached to allowed number of exceptions. Circuit is open",ex); - throw; - } - catch (HttpRequestException ex) - { - _logger.LogError($"Error in CircuitBreakingDelegatingHandler.SendAync", ex); - throw; - } - } - } -} diff --git a/src/Ocelot/Requester/QoS/CircuitBreaker.cs b/src/Ocelot/Requester/QoS/CircuitBreaker.cs deleted file mode 100644 index 91b8e430..00000000 --- a/src/Ocelot/Requester/QoS/CircuitBreaker.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Polly.CircuitBreaker; -using Polly.Timeout; - -namespace Ocelot.Requester.QoS -{ - public class CircuitBreaker - { - public CircuitBreaker(CircuitBreakerPolicy circuitBreakerPolicy, TimeoutPolicy timeoutPolicy) - { - CircuitBreakerPolicy = circuitBreakerPolicy; - TimeoutPolicy = timeoutPolicy; - } - - public CircuitBreakerPolicy CircuitBreakerPolicy { get; private set; } - public TimeoutPolicy TimeoutPolicy { get; private set; } - } -} \ No newline at end of file diff --git a/src/Ocelot/Requester/QoS/IQoSProvider.cs b/src/Ocelot/Requester/QoS/IQoSProvider.cs deleted file mode 100644 index 7382cb06..00000000 --- a/src/Ocelot/Requester/QoS/IQoSProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ocelot.Requester.QoS -{ - public interface IQoSProvider - { - CircuitBreaker CircuitBreaker { get; } - } -} diff --git a/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs b/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs deleted file mode 100644 index 2152fb41..00000000 --- a/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Ocelot.Configuration; -using Ocelot.LoadBalancer.LoadBalancers; - -namespace Ocelot.Requester.QoS -{ - public interface IQoSProviderFactory - { - IQoSProvider Get(DownstreamReRoute reRoute); - } -} diff --git a/src/Ocelot/Requester/QoS/IQosFactory.cs b/src/Ocelot/Requester/QoS/IQosFactory.cs new file mode 100644 index 00000000..fb795504 --- /dev/null +++ b/src/Ocelot/Requester/QoS/IQosFactory.cs @@ -0,0 +1,11 @@ +namespace Ocelot.Requester.QoS +{ + using System.Net.Http; + using Configuration; + using Responses; + + public interface IQoSFactory + { + Response Get(DownstreamReRoute request); + } +} diff --git a/src/Ocelot/Requester/QoS/IQosProviderHouse.cs b/src/Ocelot/Requester/QoS/IQosProviderHouse.cs deleted file mode 100644 index 29f1324a..00000000 --- a/src/Ocelot/Requester/QoS/IQosProviderHouse.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Ocelot.Configuration; -using Ocelot.Responses; - -namespace Ocelot.Requester.QoS -{ - public interface IQosProviderHouse - { - Response Get(DownstreamReRoute reRoute); - } -} \ No newline at end of file diff --git a/src/Ocelot/Requester/QoS/NoQoSProvider.cs b/src/Ocelot/Requester/QoS/NoQoSProvider.cs deleted file mode 100644 index 45ed2285..00000000 --- a/src/Ocelot/Requester/QoS/NoQoSProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ocelot.Requester.QoS -{ - public class NoQoSProvider : IQoSProvider - { - public CircuitBreaker CircuitBreaker { get; } - } -} \ No newline at end of file diff --git a/src/Ocelot/Requester/QoS/PollyQoSProvider.cs b/src/Ocelot/Requester/QoS/PollyQoSProvider.cs deleted file mode 100644 index afbf9c3a..00000000 --- a/src/Ocelot/Requester/QoS/PollyQoSProvider.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Net.Http; -using Ocelot.Configuration; -using Ocelot.Logging; -using Polly; -using Polly.CircuitBreaker; -using Polly.Timeout; - -namespace Ocelot.Requester.QoS -{ - public class PollyQoSProvider : IQoSProvider - { - private readonly CircuitBreakerPolicy _circuitBreakerPolicy; - private readonly TimeoutPolicy _timeoutPolicy; - private readonly IOcelotLogger _logger; - private readonly CircuitBreaker _circuitBreaker; - - public PollyQoSProvider(DownstreamReRoute reRoute, IOcelotLoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - - _timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(reRoute.QosOptions.TimeoutValue), reRoute.QosOptions.TimeoutStrategy); - - _circuitBreakerPolicy = Policy - .Handle() - .Or() - .Or() - .CircuitBreakerAsync( - exceptionsAllowedBeforeBreaking: reRoute.QosOptions.ExceptionsAllowedBeforeBreaking, - durationOfBreak: TimeSpan.FromMilliseconds(reRoute.QosOptions.DurationOfBreak), - onBreak: (ex, breakDelay) => - { - _logger.LogError( - ".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!", ex); - }, - onReset: () => - { - _logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again."); - }, - onHalfOpen: () => - { - _logger.LogDebug(".Breaker logging: Half-open; next call is a trial."); - } - ); - - _circuitBreaker = new CircuitBreaker(_circuitBreakerPolicy, _timeoutPolicy); - } - - public CircuitBreaker CircuitBreaker => _circuitBreaker; - } -} diff --git a/src/Ocelot/Requester/QoS/QoSProviderFactory.cs b/src/Ocelot/Requester/QoS/QoSProviderFactory.cs deleted file mode 100644 index 45fa78b3..00000000 --- a/src/Ocelot/Requester/QoS/QoSProviderFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Ocelot.Configuration; -using Ocelot.Logging; - -namespace Ocelot.Requester.QoS -{ - public class QoSProviderFactory : IQoSProviderFactory - { - private readonly IOcelotLoggerFactory _loggerFactory; - - public QoSProviderFactory(IOcelotLoggerFactory loggerFactory) - { - _loggerFactory = loggerFactory; - } - - public IQoSProvider Get(DownstreamReRoute reRoute) - { - if (reRoute.QosOptions.UseQos) - { - return new PollyQoSProvider(reRoute, _loggerFactory); - } - - return new NoQoSProvider(); - } - } -} diff --git a/src/Ocelot/Requester/QoS/QosFactory.cs b/src/Ocelot/Requester/QoS/QosFactory.cs new file mode 100644 index 00000000..37211f4b --- /dev/null +++ b/src/Ocelot/Requester/QoS/QosFactory.cs @@ -0,0 +1,33 @@ +namespace Ocelot.Requester.QoS +{ + using System; + using System.Net.Http; + using Configuration; + using Logging; + using Microsoft.Extensions.DependencyInjection; + using Responses; + + public class QoSFactory : IQoSFactory + { + private readonly IServiceProvider _serviceProvider; + private readonly IOcelotLoggerFactory _ocelotLoggerFactory; + + public QoSFactory(IServiceProvider serviceProvider, IOcelotLoggerFactory ocelotLoggerFactory) + { + _serviceProvider = serviceProvider; + _ocelotLoggerFactory = ocelotLoggerFactory; + } + + public Response Get(DownstreamReRoute request) + { + var handler = _serviceProvider.GetService(); + + if (handler != null) + { + return new OkResponse(handler(request, _ocelotLoggerFactory)); + } + + return new ErrorResponse(new UnableToFindQoSProviderError($"could not find qosProvider for {request.DownstreamScheme}{request.DownstreamAddresses}{request.DownstreamPathTemplate}")); + } + } +} diff --git a/src/Ocelot/Requester/QoS/QosProviderHouse.cs b/src/Ocelot/Requester/QoS/QosProviderHouse.cs deleted file mode 100644 index f0e44b39..00000000 --- a/src/Ocelot/Requester/QoS/QosProviderHouse.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using Ocelot.Configuration; -using Ocelot.Responses; - -namespace Ocelot.Requester.QoS -{ - public class QosProviderHouse : IQosProviderHouse - { - private readonly ConcurrentDictionary _qoSProviders; - private readonly IQoSProviderFactory _qoSProviderFactory; - - public QosProviderHouse(IQoSProviderFactory qoSProviderFactory) - { - _qoSProviderFactory = qoSProviderFactory; - _qoSProviders = new ConcurrentDictionary(); - } - - public Response Get(DownstreamReRoute reRoute) - { - try - { - if (_qoSProviders.TryGetValue(reRoute.QosOptions.Key, out var qosProvider)) - { - if (reRoute.QosOptions.UseQos && qosProvider.CircuitBreaker == null) - { - qosProvider = _qoSProviderFactory.Get(reRoute); - Add(reRoute.QosOptions.Key, qosProvider); - } - - return new OkResponse(_qoSProviders[reRoute.QosOptions.Key]); - } - - qosProvider = _qoSProviderFactory.Get(reRoute); - Add(reRoute.QosOptions.Key, qosProvider); - return new OkResponse(qosProvider); - } - catch (Exception ex) - { - return new ErrorResponse(new List() - { - new UnableToFindQoSProviderError($"unabe to find qos provider for {reRoute.QosOptions.Key}, exception was {ex}") - }); - } - } - - private void Add(string key, IQoSProvider qosProvider) - { - _qoSProviders.AddOrUpdate(key, qosProvider, (x, y) => qosProvider); - } - } -} diff --git a/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs b/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs index d54db554..e3c02b27 100644 --- a/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs +++ b/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs @@ -1,11 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Ocelot.Errors; - -namespace Ocelot.Requester.QoS +namespace Ocelot.Requester.QoS { + using Ocelot.Errors; + public class UnableToFindQoSProviderError : Error { public UnableToFindQoSProviderError(string message) diff --git a/src/Ocelot/Requester/QosDelegatingHandlerDelegate.cs b/src/Ocelot/Requester/QosDelegatingHandlerDelegate.cs new file mode 100644 index 00000000..a0c66955 --- /dev/null +++ b/src/Ocelot/Requester/QosDelegatingHandlerDelegate.cs @@ -0,0 +1,8 @@ +namespace Ocelot.Requester +{ + using System.Net.Http; + using Configuration; + using Logging; + + public delegate DelegatingHandler QosDelegatingHandlerDelegate(DownstreamReRoute reRoute, IOcelotLoggerFactory logger); +} diff --git a/src/Ocelot/Requester/RequestTimedOutError.cs b/src/Ocelot/Requester/RequestTimedOutError.cs deleted file mode 100644 index f99308b3..00000000 --- a/src/Ocelot/Requester/RequestTimedOutError.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using Ocelot.Errors; - -namespace Ocelot.Requester -{ - public class RequestTimedOutError : Error - { - public RequestTimedOutError(Exception exception) - : base($"Timeout making http request, exception: {exception}", OcelotErrorCode.RequestTimedOutError) - { - } - } -} diff --git a/test/Ocelot.AcceptanceTests/QoSTests.cs b/test/Ocelot.AcceptanceTests/QoSTests.cs deleted file mode 100644 index e62de7b3..00000000 --- a/test/Ocelot.AcceptanceTests/QoSTests.cs +++ /dev/null @@ -1,272 +0,0 @@ -namespace Ocelot.AcceptanceTests -{ - using System; - using System.Collections.Generic; - using System.Net; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - using Ocelot.Configuration.File; - using TestStack.BDDfy; - using Xunit; - - public class QoSTests : IDisposable - { - private readonly Steps _steps; - private int _requestCount; - private readonly ServiceHandler _serviceHandler; - - public QoSTests() - { - _serviceHandler = new ServiceHandler(); - _steps = new Steps(); - } - - [Fact] - public void should_not_timeout() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51569, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Post" }, - QoSOptions = new FileQoSOptions - { - TimeoutValue = 1000, - } - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51569", 200, string.Empty, 10)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenThePostHasContent("postContent")) - .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_timeout() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51579, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Post" }, - QoSOptions = new FileQoSOptions - { - TimeoutValue = 10, - } - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51579", 201, string.Empty, 1000)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.GivenThePostHasContent("postContent")) - .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) - .BDDfy(); - } - - [Fact] - public void should_open_circuit_breaker_then_close() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51892, - } - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - QoSOptions = new FileQoSOptions - { - ExceptionsAllowedBeforeBreaking = 1, - TimeoutValue = 500, - DurationOfBreak = 1000 - }, - } - } - }; - - this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51892", "Hello from Laura")) - .Given(x => _steps.GivenThereIsAConfiguration(configuration)) - .Given(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) - .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) - .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) - .Given(x => x.GivenIWaitMilliseconds(3000)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void open_circuit_should_not_effect_different_reRoute() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51872, - } - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - QoSOptions = new FileQoSOptions - { - ExceptionsAllowedBeforeBreaking = 1, - TimeoutValue = 500, - DurationOfBreak = 1000 - } - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51880, - } - }, - UpstreamPathTemplate = "/working", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51872", "Hello from Laura")) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", 200, "Hello from Tom", 0)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) - .And(x => _steps.WhenIGetUrlOnTheApiGateway("/working")) - .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) - .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) - .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) - .And(x => x.GivenIWaitMilliseconds(3000)) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - private void GivenIWaitMilliseconds(int ms) - { - Thread.Sleep(ms); - } - - private void GivenThereIsAPossiblyBrokenServiceRunningOn(string url, string responseBody) - { - _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => - { - //circuit starts closed - if (_requestCount == 0) - { - _requestCount++; - context.Response.StatusCode = 200; - await context.Response.WriteAsync(responseBody); - return; - } - - //request one times out and polly throws exception, circuit opens - if (_requestCount == 1) - { - _requestCount++; - await Task.Delay(1000); - context.Response.StatusCode = 200; - return; - } - - //after break closes we return 200 OK - if (_requestCount == 2) - { - context.Response.StatusCode = 200; - await context.Response.WriteAsync(responseBody); - } - }); - } - - private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody, int timeout) - { - _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => - { - Thread.Sleep(timeout); - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - } - - public void Dispose() - { - _serviceHandler?.Dispose(); - _steps.Dispose(); - } - } -} diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 340db66f..685c1e00 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -831,7 +831,7 @@ namespace Ocelot.AcceptanceTests } [Fact] - public void should_fix_145() + public void should_not_set_trailing_slash_on_url_template() { var configuration = new FileConfiguration { @@ -851,11 +851,6 @@ namespace Ocelot.AcceptanceTests }, UpstreamPathTemplate = "/platform/{url}", UpstreamHttpMethod = new List { "Get" }, - QoSOptions = new FileQoSOptions { - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak = 10, - TimeoutValue = 5000 - } } } }; diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj index 5c3c5cfe..2f81d2b9 100644 --- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj +++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj @@ -36,9 +36,8 @@ - all - \ No newline at end of file + diff --git a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs index 2882ed8b..679b051c 100644 --- a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs @@ -16,23 +16,28 @@ using Xunit; namespace Ocelot.UnitTests.Requester { + using Responder; + public class DelegatingHandlerHandlerProviderFactoryTests { private DelegatingHandlerHandlerFactory _factory; private readonly Mock _loggerFactory; private DownstreamReRoute _request; private Response>> _result; - private readonly Mock _qosProviderHouse; + private readonly Mock _qosFactory; private readonly Mock _tracingFactory; private IServiceProvider _serviceProvider; private readonly IServiceCollection _services; + private readonly QosDelegatingHandlerDelegate _qosDelegate; public DelegatingHandlerHandlerProviderFactoryTests() { + _qosDelegate = (a, b) => new FakeQoSHandler(); _tracingFactory = new Mock(); - _qosProviderHouse = new Mock(); + _qosFactory = new Mock(); _loggerFactory = new Mock(); _services = new ServiceCollection(); + _services.AddSingleton(_qosDelegate); } [Fact] @@ -56,8 +61,8 @@ namespace Ocelot.UnitTests.Requester .Build(); this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheQosFactoryReturns(new FakeQoSHandler())) .And(x => GivenTheTracingFactoryReturns()) - .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) .When(x => WhenIGet()) @@ -67,7 +72,7 @@ namespace Ocelot.UnitTests.Requester .And(x => ThenHandlerAtPositionIs(2)) .And(x => ThenHandlerAtPositionIs(3)) .And(x => ThenHandlerAtPositionIs(4)) - .And(x => ThenHandlerAtPositionIs(5)) + .And(x => ThenHandlerAtPositionIs(5)) .BDDfy(); } @@ -93,8 +98,8 @@ namespace Ocelot.UnitTests.Requester .Build(); this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheQosFactoryReturns(new FakeQoSHandler())) .And(x => GivenTheTracingFactoryReturns()) - .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) .When(x => WhenIGet()) @@ -104,7 +109,7 @@ namespace Ocelot.UnitTests.Requester .And(x => ThenHandlerAtPositionIs(2)) //second from config .And(x => ThenHandlerAtPositionIs(3)) //third from config (global) .And(x => ThenHandlerAtPositionIs(4)) - .And(x => ThenHandlerAtPositionIs(5)) + .And(x => ThenHandlerAtPositionIs(5)) .BDDfy(); } @@ -129,8 +134,8 @@ namespace Ocelot.UnitTests.Requester .Build(); this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheQosFactoryReturns(new FakeQoSHandler())) .And(x => GivenTheTracingFactoryReturns()) - .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) .When(x => WhenIGet()) @@ -140,7 +145,7 @@ namespace Ocelot.UnitTests.Requester .And(x => ThenHandlerAtPositionIs(2)) .And(x => ThenHandlerAtPositionIs(3)) .And(x => ThenHandlerAtPositionIs(4)) - .And(x => ThenHandlerAtPositionIs(5)) + .And(x => ThenHandlerAtPositionIs(5)) .BDDfy(); } @@ -164,8 +169,8 @@ namespace Ocelot.UnitTests.Requester .Build(); this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheQosFactoryReturns(new FakeQoSHandler())) .And(x => GivenTheTracingFactoryReturns()) - .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) .When(x => WhenIGet()) @@ -174,7 +179,7 @@ namespace Ocelot.UnitTests.Requester .And(x => ThenHandlerAtPositionIs(1)) .And(x => ThenHandlerAtPositionIs(2)) .And(x => ThenHandlerAtPositionIs(3)) - .And(x => ThenHandlerAtPositionIs(4)) + .And(x => ThenHandlerAtPositionIs(4)) .BDDfy(); } @@ -194,8 +199,8 @@ namespace Ocelot.UnitTests.Requester .Build(); this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheQosFactoryReturns(new FakeQoSHandler())) .And(x => GivenTheTracingFactoryReturns()) - .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) .When(x => WhenIGet()) @@ -203,7 +208,7 @@ namespace Ocelot.UnitTests.Requester .And(x => ThenHandlerAtPositionIs(0)) .And(x => ThenHandlerAtPositionIs(1)) .And(x => ThenHandlerAtPositionIs(2)) - .And(x => ThenHandlerAtPositionIs(3)) + .And(x => ThenHandlerAtPositionIs(3)) .BDDfy(); } @@ -225,7 +230,6 @@ namespace Ocelot.UnitTests.Requester .Build(); this.Given(x => GivenTheFollowingRequest(reRoute)) - .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(2)) @@ -247,12 +251,12 @@ namespace Ocelot.UnitTests.Requester .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false, true)).WithLoadBalancerKey("").Build(); this.Given(x => GivenTheFollowingRequest(reRoute)) - .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) + .And(x => GivenTheQosFactoryReturns(new FakeQoSHandler())) .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(3)) .And(x => ThenTheDelegatesAreAddedCorrectly()) - .And(x => ThenItIsPolly(2)) + .And(x => ThenItIsQosHandler(2)) .BDDfy(); } @@ -287,11 +291,11 @@ namespace Ocelot.UnitTests.Requester .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false, true)).WithLoadBalancerKey("").Build(); this.Given(x => GivenTheFollowingRequest(reRoute)) - .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) + .And(x => GivenTheQosFactoryReturns(new FakeQoSHandler())) .And(x => GivenTheServiceProviderReturnsNothing()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(1)) - .And(x => ThenItIsPolly(0)) + .And(x => ThenItIsQosHandler(0)) .BDDfy(); } @@ -309,7 +313,7 @@ namespace Ocelot.UnitTests.Requester .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false, true)).WithLoadBalancerKey("").Build(); this.Given(x => GivenTheFollowingRequest(reRoute)) - .And(x => GivenTheQosProviderHouseReturns(new ErrorResponse(It.IsAny()))) + .And(x => GivenTheQosFactoryReturnsError()) .And(x => GivenTheServiceProviderReturnsNothing()) .When(x => WhenIGet()) .Then(x => ThenAnErrorIsReturned()) @@ -378,18 +382,25 @@ namespace Ocelot.UnitTests.Requester handlerTwo.Order.ShouldBe(2); } - private void GivenTheQosProviderHouseReturns(Response qosProvider) + private void GivenTheQosFactoryReturns(DelegatingHandler handler) { - _qosProviderHouse + _qosFactory .Setup(x => x.Get(It.IsAny())) - .Returns(qosProvider); + .Returns(new OkResponse(handler)); } - private void ThenItIsPolly(int i) + private void GivenTheQosFactoryReturnsError() + { + _qosFactory + .Setup(x => x.Get(It.IsAny())) + .Returns(new ErrorResponse(new AnyError())); + } + + private void ThenItIsQosHandler(int i) { var delegates = _result.Data; var del = delegates[i].Invoke(); - del.ShouldBeOfType(); + del.ShouldBeOfType(); } private void ThenThereIsDelegatesInProvider(int count) @@ -406,7 +417,7 @@ namespace Ocelot.UnitTests.Requester private void WhenIGet() { _serviceProvider = _services.BuildServiceProvider(); - _factory = new DelegatingHandlerHandlerFactory(_loggerFactory.Object, _tracingFactory.Object, _qosProviderHouse.Object, _serviceProvider); + _factory = new DelegatingHandlerHandlerFactory(_tracingFactory.Object, _qosFactory.Object, _serviceProvider); _result = _factory.Get(_request); } @@ -420,4 +431,8 @@ namespace Ocelot.UnitTests.Requester internal class FakeTracingHandler : DelegatingHandler, ITracingHandler { } + + internal class FakeQoSHandler : DelegatingHandler + { + } } diff --git a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs index ae9a25af..6b961a16 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs @@ -144,7 +144,7 @@ namespace Ocelot.UnitTests.Requester private void ThenTheErrorIsTimeout() { - _response.Errors[0].ShouldBeOfType(); + _response.Errors[0].ShouldBeOfType(); } private void GivenTheHouseReturnsOkHandler() diff --git a/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs b/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs index f4292656..4310788d 100644 --- a/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs @@ -1,15 +1,17 @@ -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Logging; -using Ocelot.Requester.QoS; -using Shouldly; -using System.Collections.Generic; -using TestStack.BDDfy; -using Xunit; - +/* namespace Ocelot.UnitTests.Requester { + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.Logging; + using Ocelot.Requester.QoS; + using Shouldly; + using System.Collections.Generic; + using TestStack.BDDfy; + using Xunit; + public class QoSProviderFactoryTests { private readonly IQoSProviderFactory _factory; @@ -22,10 +24,9 @@ namespace Ocelot.UnitTests.Requester { _logger = new Mock(); _loggerFactory = new Mock(); - _loggerFactory - .Setup(x => x.CreateLogger()) - .Returns(_logger.Object); - _factory = new QoSProviderFactory(_loggerFactory.Object); + var services = new ServiceCollection(); + var provider = services.BuildServiceProvider(); + _factory = new QoSProviderFactory(_loggerFactory.Object, provider); } [Fact] @@ -46,7 +47,7 @@ namespace Ocelot.UnitTests.Requester } [Fact] - public void should_return_polly_qos_provider() + public void should_return_delegate_provider() { var qosOptions = new QoSOptionsBuilder() .WithTimeoutValue(100) @@ -55,13 +56,13 @@ namespace Ocelot.UnitTests.Requester .Build(); var reRoute = new DownstreamReRouteBuilder() - .WithUpstreamHttpMethod(new List { "get" }) - .WithQosOptions(qosOptions) - .Build(); + .WithUpstreamHttpMethod(new List { "get" }) + .WithQosOptions(qosOptions) + .Build(); this.Given(x => x.GivenAReRoute(reRoute)) .When(x => x.WhenIGetTheQoSProvider()) - .Then(x => x.ThenTheQoSProviderIsReturned()) + .Then(x => x.ThenTheQoSProviderIsReturned()) .BDDfy(); } @@ -80,4 +81,13 @@ namespace Ocelot.UnitTests.Requester _result.ShouldBeOfType(); } } + + internal class FakeProvider : IQoSProvider + { + public T CircuitBreaker() + { + throw new System.NotImplementedException(); + } + } } +*/ diff --git a/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs b/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs index a2db0198..f5c3682b 100644 --- a/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs +++ b/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs @@ -1,4 +1,5 @@ -using Moq; +/* +using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Requester.QoS; @@ -25,13 +26,13 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_store_qos_provider_on_first_request() - { - var qosOptions = new QoSOptionsBuilder() - .WithKey("test") - .Build(); + { + var qosOptions = new QoSOptionsBuilder() + .WithKey("test") + .Build(); - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) + var reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) .Build(); this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) @@ -47,7 +48,7 @@ namespace Ocelot.UnitTests.Requester .Build(); var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) + .WithQosOptions(qosOptions) .Build(); this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) @@ -68,8 +69,8 @@ namespace Ocelot.UnitTests.Requester .Build(); var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .Build(); + .WithQosOptions(qosOptions) + .Build(); var reRouteTwo = new DownstreamReRouteBuilder() .WithQosOptions(qosOptionsTwo) @@ -88,10 +89,10 @@ namespace Ocelot.UnitTests.Requester public void should_return_error_if_no_qos_provider_with_key() { var qosOptions = new QoSOptionsBuilder() - .Build(); + .Build(); - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) + var reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) .Build(); this.When(x => x.WhenWeGetTheQoSProvider(reRoute)) @@ -109,16 +110,16 @@ namespace Ocelot.UnitTests.Requester .WithExceptionsAllowedBeforeBreaking(1) .Build(); - var dontUseQoSOptions = new QoSOptionsBuilder() + var dontUseQoSOptions = new QoSOptionsBuilder() .WithKey("test") - .Build(); - + .Build(); + var reRoute = new DownstreamReRouteBuilder() .WithQosOptions(dontUseQoSOptions) .Build(); - var reRouteTwo = new DownstreamReRouteBuilder() - .WithQosOptions(useQoSOptions) + var reRouteTwo = new DownstreamReRouteBuilder() + .WithQosOptions(useQoSOptions) .Build(); this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) @@ -176,12 +177,19 @@ namespace Ocelot.UnitTests.Requester class FakeQoSProvider : IQoSProvider { - public CircuitBreaker CircuitBreaker { get; } + T IQoSProvider.CircuitBreaker() + { + throw new System.NotImplementedException(); + } } class FakePollyQoSProvider : IQoSProvider { - public CircuitBreaker CircuitBreaker { get; } + T IQoSProvider.CircuitBreaker() + { + throw new System.NotImplementedException(); + } } } } +*/ From 6d8b18c01d5e53efdd7e9e6c44ec39d01b8a4a34 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 19 Aug 2018 12:57:43 +0100 Subject: [PATCH 36/50] Feature/inject error mapper (#562) * added delegate to select last handler * #529 implemented a way we can inject the last delegating handler * wip - moving code * #529 removed loads of qos code and moved it into Ocelot.Provider.Polly * #529 can now inject http client expcetions to ocelot errors mappers and updated docs --- docs/features/qualityofservice.rst | 17 +- src/Ocelot/Configuration/QoSOptions.cs | 2 - .../DependencyInjection/OcelotBuilder.cs | 1 + .../Requester/HttpClientHttpRequester.cs | 10 +- .../Requester/HttpExeptionToErrorMapper.cs | 29 +++ .../Requester/IExceptionToErrorMapper.cs | 10 + .../Requester/ReRouteDelegatingHandler.cs | 10 - ...bleToFindDelegatingHandlerProviderError.cs | 12 -- .../Requester/HttpClientHttpRequesterTest.cs | 8 +- .../HttpExeptionToErrorMapperTests.cs | 52 +++++ .../Requester/QoSFactoryTests.cs | 55 +++++ .../Requester/QoSProviderFactoryTests.cs | 93 --------- .../Requester/QosProviderHouseTests.cs | 195 ------------------ 13 files changed, 177 insertions(+), 317 deletions(-) create mode 100644 src/Ocelot/Requester/HttpExeptionToErrorMapper.cs create mode 100644 src/Ocelot/Requester/IExceptionToErrorMapper.cs delete mode 100644 src/Ocelot/Requester/ReRouteDelegatingHandler.cs delete mode 100644 src/Ocelot/Requester/UnableToFindDelegatingHandlerProviderError.cs create mode 100644 test/Ocelot.UnitTests/Requester/HttpExeptionToErrorMapperTests.cs create mode 100644 test/Ocelot.UnitTests/Requester/QoSFactoryTests.cs delete mode 100644 test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs delete mode 100644 test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs diff --git a/docs/features/qualityofservice.rst b/docs/features/qualityofservice.rst index 17bf373d..b72f99d0 100644 --- a/docs/features/qualityofservice.rst +++ b/docs/features/qualityofservice.rst @@ -5,7 +5,22 @@ Ocelot supports one QoS capability at the current time. You can set on a per ReR want to use a circuit breaker when making requests to a downstream service. This uses the an awesome .NET library called Polly check them out `here `_. -Add the following section to a ReRoute configuration. +The first thing you need to do if you want to use the administration API is bring in the relavent NuGet package.. + +``Install-Package Ocelot.Provider.Polly`` + +Then in your ConfigureServices method + +.. code-block:: csharp + + public virtual void ConfigureServices(IServiceCollection services) + { + services + .AddOcelot() + .AddPolly(); + } + +Then add the following section to a ReRoute configuration. .. code-block:: json diff --git a/src/Ocelot/Configuration/QoSOptions.cs b/src/Ocelot/Configuration/QoSOptions.cs index b0e7df7a..3eb907eb 100644 --- a/src/Ocelot/Configuration/QoSOptions.cs +++ b/src/Ocelot/Configuration/QoSOptions.cs @@ -7,8 +7,6 @@ int durationofBreak, int timeoutValue, string key, - //todo - this is never set in Ocelot so always Pessimistic...I guess it doesn't - //matter to much. string timeoutStrategy = "Pessimistic") { ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index c9b49039..62adf1fb 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -130,6 +130,7 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); + Services.TryAddSingleton(); } public IOcelotBuilder AddSingletonDefinedAggregator() diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index ea557f66..0705e7d2 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -12,14 +12,17 @@ namespace Ocelot.Requester private readonly IHttpClientCache _cacheHandlers; private readonly IOcelotLogger _logger; private readonly IDelegatingHandlerHandlerFactory _factory; + private readonly IExceptionToErrorMapper _mapper; public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, IHttpClientCache cacheHandlers, - IDelegatingHandlerHandlerFactory house) + IDelegatingHandlerHandlerFactory factory, + IExceptionToErrorMapper mapper) { _logger = loggerFactory.CreateLogger(); _cacheHandlers = cacheHandlers; - _factory = house; + _factory = factory; + _mapper = mapper; } public async Task> GetResponse(DownstreamContext context) @@ -35,7 +38,8 @@ namespace Ocelot.Requester } catch (Exception exception) { - return new ErrorResponse(new UnableToCompleteRequestError(exception)); + var error = _mapper.Map(exception); + return new ErrorResponse(error); } finally { diff --git a/src/Ocelot/Requester/HttpExeptionToErrorMapper.cs b/src/Ocelot/Requester/HttpExeptionToErrorMapper.cs new file mode 100644 index 00000000..27e9c4ba --- /dev/null +++ b/src/Ocelot/Requester/HttpExeptionToErrorMapper.cs @@ -0,0 +1,29 @@ +namespace Ocelot.Requester +{ + using System; + using System.Collections.Generic; + using Errors; + using Microsoft.Extensions.DependencyInjection; + + public class HttpExeptionToErrorMapper : IExceptionToErrorMapper + { + private readonly Dictionary> _mappers; + + public HttpExeptionToErrorMapper(IServiceProvider serviceProvider) + { + _mappers = serviceProvider.GetService>>(); + } + + public Error Map(Exception exception) + { + var type = exception.GetType(); + + if (_mappers != null && _mappers.ContainsKey(type)) + { + return _mappers[type](exception); + } + + return new UnableToCompleteRequestError(exception); + } + } +} diff --git a/src/Ocelot/Requester/IExceptionToErrorMapper.cs b/src/Ocelot/Requester/IExceptionToErrorMapper.cs new file mode 100644 index 00000000..9d765fa7 --- /dev/null +++ b/src/Ocelot/Requester/IExceptionToErrorMapper.cs @@ -0,0 +1,10 @@ +using System; +using Ocelot.Errors; + +namespace Ocelot.Requester +{ + public interface IExceptionToErrorMapper + { + Error Map(Exception exception); + } +} diff --git a/src/Ocelot/Requester/ReRouteDelegatingHandler.cs b/src/Ocelot/Requester/ReRouteDelegatingHandler.cs deleted file mode 100644 index 0a5c5472..00000000 --- a/src/Ocelot/Requester/ReRouteDelegatingHandler.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Net.Http; - -namespace Ocelot.Requester -{ - public class ReRouteDelegatingHandler - where T : DelegatingHandler - { - public T DelegatingHandler { get; private set; } - } -} diff --git a/src/Ocelot/Requester/UnableToFindDelegatingHandlerProviderError.cs b/src/Ocelot/Requester/UnableToFindDelegatingHandlerProviderError.cs deleted file mode 100644 index fd8aeb9e..00000000 --- a/src/Ocelot/Requester/UnableToFindDelegatingHandlerProviderError.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.Requester -{ - public class UnableToFindDelegatingHandlerProviderError : Error - { - public UnableToFindDelegatingHandlerProviderError(string message) - : base(message, OcelotErrorCode.UnableToFindDelegatingHandlerProviderError) - { - } - } -} diff --git a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs index 6b961a16..a6701035 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs @@ -27,6 +27,7 @@ namespace Ocelot.UnitTests.Requester private DownstreamContext _request; private Mock _loggerFactory; private Mock _logger; + private Mock _mapper; public HttpClientHttpRequesterTest() { @@ -38,10 +39,12 @@ namespace Ocelot.UnitTests.Requester .Setup(x => x.CreateLogger()) .Returns(_logger.Object); _cacheHandlers = new Mock(); + _mapper = new Mock(); _httpClientRequester = new HttpClientHttpRequester( _loggerFactory.Object, _cacheHandlers.Object, - _factory.Object); + _factory.Object, + _mapper.Object); } [Fact] @@ -144,6 +147,7 @@ namespace Ocelot.UnitTests.Requester private void ThenTheErrorIsTimeout() { + _mapper.Verify(x => x.Map(It.IsAny()), Times.Once); _response.Errors[0].ShouldBeOfType(); } @@ -165,6 +169,8 @@ namespace Ocelot.UnitTests.Requester }; _factory.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse>>(handlers)); + + _mapper.Setup(x => x.Map(It.IsAny())).Returns(new UnableToCompleteRequestError(new Exception())); } class OkDelegatingHandler : DelegatingHandler diff --git a/test/Ocelot.UnitTests/Requester/HttpExeptionToErrorMapperTests.cs b/test/Ocelot.UnitTests/Requester/HttpExeptionToErrorMapperTests.cs new file mode 100644 index 00000000..04fb3a5e --- /dev/null +++ b/test/Ocelot.UnitTests/Requester/HttpExeptionToErrorMapperTests.cs @@ -0,0 +1,52 @@ +namespace Ocelot.UnitTests.Requester +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.Errors; + using Ocelot.Requester; + using Responder; + using Shouldly; + using Xunit; + + public class HttpExeptionToErrorMapperTests + { + private HttpExeptionToErrorMapper _mapper; + private readonly ServiceCollection _services; + + public HttpExeptionToErrorMapperTests() + { + _services = new ServiceCollection(); + var provider = _services.BuildServiceProvider(); + _mapper = new HttpExeptionToErrorMapper(provider); + } + + [Fact] + public void should_return_default_error_because_mappers_are_null() + { + var error = _mapper.Map(new Exception()); + + error.ShouldBeOfType(); + } + + [Fact] + public void should_return_error_from_mapper() + { + var errorMapping = new Dictionary> + { + {typeof(TaskCanceledException), e => new AnyError()}, + }; + + _services.AddSingleton(errorMapping); + + var provider = _services.BuildServiceProvider(); + + _mapper = new HttpExeptionToErrorMapper(provider); + + var error = _mapper.Map(new TaskCanceledException()); + + error.ShouldBeOfType(); + } + } +} diff --git a/test/Ocelot.UnitTests/Requester/QoSFactoryTests.cs b/test/Ocelot.UnitTests/Requester/QoSFactoryTests.cs new file mode 100644 index 00000000..4caedc53 --- /dev/null +++ b/test/Ocelot.UnitTests/Requester/QoSFactoryTests.cs @@ -0,0 +1,55 @@ +namespace Ocelot.UnitTests.Requester +{ + using System.Net.Http; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.Logging; + using Ocelot.Requester; + using Ocelot.Requester.QoS; + using Shouldly; + using Xunit; + + public class QoSFactoryTests + { + private QoSFactory _factory; + private ServiceCollection _services; + private readonly Mock _loggerFactory; + + public QoSFactoryTests() + { + _services = new ServiceCollection(); + _loggerFactory = new Mock(); + var provider = _services.BuildServiceProvider(); + _factory = new QoSFactory(provider, _loggerFactory.Object); + } + + [Fact] + public void should_return_error() + { + var downstreamReRoute = new DownstreamReRouteBuilder().Build(); + var handler = _factory.Get(downstreamReRoute); + handler.IsError.ShouldBeTrue(); + handler.Errors[0].ShouldBeOfType(); + } + + [Fact] + public void should_return_handler() + { + _services = new ServiceCollection(); + DelegatingHandler QosDelegatingHandlerDelegate(DownstreamReRoute a, IOcelotLoggerFactory b) => new FakeDelegatingHandler(); + _services.AddSingleton(QosDelegatingHandlerDelegate); + var provider = _services.BuildServiceProvider(); + _factory = new QoSFactory(provider, _loggerFactory.Object); + var downstreamReRoute = new DownstreamReRouteBuilder().Build(); + var handler = _factory.Get(downstreamReRoute); + handler.IsError.ShouldBeFalse(); + handler.Data.ShouldBeOfType(); + } + + class FakeDelegatingHandler : DelegatingHandler + { + } + } +} diff --git a/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs b/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs deleted file mode 100644 index 4310788d..00000000 --- a/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs +++ /dev/null @@ -1,93 +0,0 @@ -/* -namespace Ocelot.UnitTests.Requester -{ - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.Logging; - using Ocelot.Requester.QoS; - using Shouldly; - using System.Collections.Generic; - using TestStack.BDDfy; - using Xunit; - - public class QoSProviderFactoryTests - { - private readonly IQoSProviderFactory _factory; - private DownstreamReRoute _reRoute; - private IQoSProvider _result; - private Mock _loggerFactory; - private Mock _logger; - - public QoSProviderFactoryTests() - { - _logger = new Mock(); - _loggerFactory = new Mock(); - var services = new ServiceCollection(); - var provider = services.BuildServiceProvider(); - _factory = new QoSProviderFactory(_loggerFactory.Object, provider); - } - - [Fact] - public void should_return_no_qos_provider() - { - var qosOptions = new QoSOptionsBuilder() - .Build(); - - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .WithUpstreamHttpMethod(new List { "get" }) - .Build(); - - this.Given(x => x.GivenAReRoute(reRoute)) - .When(x => x.WhenIGetTheQoSProvider()) - .Then(x => x.ThenTheQoSProviderIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_return_delegate_provider() - { - var qosOptions = new QoSOptionsBuilder() - .WithTimeoutValue(100) - .WithDurationOfBreak(100) - .WithExceptionsAllowedBeforeBreaking(100) - .Build(); - - var reRoute = new DownstreamReRouteBuilder() - .WithUpstreamHttpMethod(new List { "get" }) - .WithQosOptions(qosOptions) - .Build(); - - this.Given(x => x.GivenAReRoute(reRoute)) - .When(x => x.WhenIGetTheQoSProvider()) - .Then(x => x.ThenTheQoSProviderIsReturned()) - .BDDfy(); - } - - private void GivenAReRoute(DownstreamReRoute reRoute) - { - _reRoute = reRoute; - } - - private void WhenIGetTheQoSProvider() - { - _result = _factory.Get(_reRoute); - } - - private void ThenTheQoSProviderIsReturned() - { - _result.ShouldBeOfType(); - } - } - - internal class FakeProvider : IQoSProvider - { - public T CircuitBreaker() - { - throw new System.NotImplementedException(); - } - } -} -*/ diff --git a/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs b/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs deleted file mode 100644 index f5c3682b..00000000 --- a/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs +++ /dev/null @@ -1,195 +0,0 @@ -/* -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Requester.QoS; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Requester -{ - public class QosProviderHouseTests - { - private IQoSProvider _qoSProvider; - private readonly QosProviderHouse _qosProviderHouse; - private Response _getResult; - private DownstreamReRoute _reRoute; - private readonly Mock _factory; - - public QosProviderHouseTests() - { - _factory = new Mock(); - _qosProviderHouse = new QosProviderHouse(_factory.Object); - } - - [Fact] - public void should_store_qos_provider_on_first_request() - { - var qosOptions = new QoSOptionsBuilder() - .WithKey("test") - .Build(); - - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .Build(); - - this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) - .Then(x => x.ThenItIsAdded()) - .BDDfy(); - } - - [Fact] - public void should_not_store_qos_provider_on_first_request() - { - var qosOptions = new QoSOptionsBuilder() - .WithKey("test") - .Build(); - - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .Build(); - - this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) - .When(x => x.WhenWeGetTheQoSProvider(reRoute)) - .Then(x => x.ThenItIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_store_qos_providers_by_key() - { - var qosOptions = new QoSOptionsBuilder() - .WithKey("test") - .Build(); - - var qosOptionsTwo = new QoSOptionsBuilder() - .WithKey("testTwo") - .Build(); - - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .Build(); - - var reRouteTwo = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptionsTwo) - .Build(); - - this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) - .And(x => x.GivenThereIsAQoSProvider(reRouteTwo, new FakePollyQoSProvider())) - .When(x => x.WhenWeGetTheQoSProvider(reRoute)) - .Then(x => x.ThenTheQoSProviderIs()) - .When(x => x.WhenWeGetTheQoSProvider(reRouteTwo)) - .Then(x => x.ThenTheQoSProviderIs()) - .BDDfy(); - } - - [Fact] - public void should_return_error_if_no_qos_provider_with_key() - { - var qosOptions = new QoSOptionsBuilder() - .Build(); - - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .Build(); - - this.When(x => x.WhenWeGetTheQoSProvider(reRoute)) - .Then(x => x.ThenAnErrorIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_get_new_qos_provider_if_reroute_qos_provider_has_changed() - { - var useQoSOptions = new QoSOptionsBuilder() - .WithTimeoutValue(1) - .WithKey("test") - .WithDurationOfBreak(1) - .WithExceptionsAllowedBeforeBreaking(1) - .Build(); - - var dontUseQoSOptions = new QoSOptionsBuilder() - .WithKey("test") - .Build(); - - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(dontUseQoSOptions) - .Build(); - - var reRouteTwo = new DownstreamReRouteBuilder() - .WithQosOptions(useQoSOptions) - .Build(); - - this.Given(x => x.GivenThereIsAQoSProvider(reRoute, new FakeQoSProvider())) - .When(x => x.WhenWeGetTheQoSProvider(reRoute)) - .Then(x => x.ThenTheQoSProviderIs()) - .When(x => x.WhenIGetTheReRouteWithTheSameKeyButDifferentQosProvider(reRouteTwo)) - .Then(x => x.ThenTheQoSProviderIs()) - .BDDfy(); - } - - private void WhenIGetTheReRouteWithTheSameKeyButDifferentQosProvider(DownstreamReRoute reRoute) - { - _reRoute = reRoute; - _factory.Setup(x => x.Get(_reRoute)).Returns(new FakePollyQoSProvider()); - _getResult = _qosProviderHouse.Get(_reRoute); - } - - private void ThenAnErrorIsReturned() - { - _getResult.IsError.ShouldBeTrue(); - _getResult.Errors[0].ShouldBeOfType(); - } - - private void ThenTheQoSProviderIs() - { - _getResult.Data.ShouldBeOfType(); - } - - private void ThenItIsAdded() - { - _getResult.IsError.ShouldBe(false); - _getResult.ShouldBeOfType>(); - _factory.Verify(x => x.Get(_reRoute), Times.Once); - _getResult.Data.ShouldBe(_qoSProvider); - } - - private void GivenThereIsAQoSProvider(DownstreamReRoute reRoute, IQoSProvider qoSProvider) - { - _reRoute = reRoute; - _qoSProvider = qoSProvider; - _factory.Setup(x => x.Get(_reRoute)).Returns(_qoSProvider); - _getResult = _qosProviderHouse.Get(reRoute); - } - - private void WhenWeGetTheQoSProvider(DownstreamReRoute reRoute) - { - _getResult = _qosProviderHouse.Get(reRoute); - } - - private void ThenItIsReturned() - { - _getResult.Data.ShouldBe(_qoSProvider); - _factory.Verify(x => x.Get(_reRoute), Times.Once); - } - - class FakeQoSProvider : IQoSProvider - { - T IQoSProvider.CircuitBreaker() - { - throw new System.NotImplementedException(); - } - } - - class FakePollyQoSProvider : IQoSProvider - { - T IQoSProvider.CircuitBreaker() - { - throw new System.NotImplementedException(); - } - } - } -} -*/ From 7e01caf5508b1cb95cc13ecb57d5c31a725a80b3 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 19 Aug 2018 13:57:57 +0100 Subject: [PATCH 37/50] #529 removed some deps ocelot doesnt need (#563) --- src/Ocelot/Ocelot.csproj | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index b25af9f7..81891d2f 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -4,7 +4,7 @@ 2.0.0 2.0.0 true - This project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. We have been unable to find this in my current workplace without having to write our own Javascript middlewares to handle the IdentityServer reference tokens. We would rather use the IdentityServer code that already exists to do this. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features. + Ocelot is an API Gateway. The project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. reference tokens. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features. Ocelot 0.0.0-dev Ocelot @@ -30,20 +30,11 @@ - - - - NU1701 - - - - all - From 00a600064deea0877058d04e6189d7e0278c99a5 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Mon, 20 Aug 2018 22:28:58 +0100 Subject: [PATCH 38/50] Feature/issue with path and query string #458 (#565) * #548 added failing test * #548 fixed failing tests for issue where using /{everything} didnt build path correctly --- .../UrlPathPlaceholderNameAndValueFinder.cs | 300 +++---- src/Ocelot/Responder/HttpContextResponder.cs | 5 +- test/Ocelot.AcceptanceTests/RoutingTests.cs | 35 + .../RoutingWithQueryStringTests.cs | 40 +- ...lPathPlaceholderNameAndValueFinderTests.cs | 745 +++++++++--------- 5 files changed, 616 insertions(+), 509 deletions(-) diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs index 6f4fec00..bb59fac4 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs @@ -1,150 +1,150 @@ -using System.Collections.Generic; -using Ocelot.Responses; - -namespace Ocelot.DownstreamRouteFinder.UrlMatcher -{ - public class UrlPathPlaceholderNameAndValueFinder : IPlaceholderNameAndValueFinder - { - public Response> Find(string path, string query, string pathTemplate) - { - var placeHolderNameAndValues = new List(); - - path = $"{path}{query}"; - - int counterForPath = 0; - - var delimiter = '/'; - var nextDelimiter = '/'; - - for (int counterForTemplate = 0; counterForTemplate < pathTemplate.Length; counterForTemplate++) - { - if ((path.Length > counterForPath) && CharactersDontMatch(pathTemplate[counterForTemplate], path[counterForPath]) && ContinueScanningUrl(counterForPath,path.Length)) - { - if (IsPlaceholder(pathTemplate[counterForTemplate])) - { - //should_find_multiple_query_string make test pass - if (PassedQueryString(pathTemplate, counterForTemplate)) - { - delimiter = '&'; - nextDelimiter = '&'; - } - - //should_find_multiple_query_string_and_path makes test pass - if (NotPassedQueryString(pathTemplate, counterForTemplate) && NoMoreForwardSlash(pathTemplate, counterForTemplate)) - { - delimiter = '?'; - nextDelimiter = '?'; - } - - var placeholderName = GetPlaceholderName(pathTemplate, counterForTemplate); - - var placeholderValue = GetPlaceholderValue(pathTemplate, query, placeholderName, path, counterForPath, delimiter); - - placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); - - counterForTemplate = GetNextCounterPosition(pathTemplate, counterForTemplate, '}'); - - counterForPath = GetNextCounterPosition(path, counterForPath, nextDelimiter); - - continue; - } - - return new OkResponse>(placeHolderNameAndValues); - } - else if(IsCatchAll(path, counterForPath, pathTemplate)) - { - var endOfPlaceholder = GetNextCounterPosition(pathTemplate, counterForTemplate, '}'); - - var placeholderName = GetPlaceholderName(pathTemplate, 1); - - if(NothingAfterFirstForwardSlash(path)) - { - placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, "")); - } - else - { - var placeholderValue = GetPlaceholderValue(pathTemplate, query, placeholderName, path, counterForPath + 1, '/'); - placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); - } - - counterForTemplate = endOfPlaceholder; - } - - counterForPath++; - } - - return new OkResponse>(placeHolderNameAndValues); - } - - private static bool NoMoreForwardSlash(string pathTemplate, int counterForTemplate) - { - return !pathTemplate.Substring(counterForTemplate).Contains("/"); - } - - private static bool NotPassedQueryString(string pathTemplate, int counterForTemplate) - { - return !pathTemplate.Substring(0, counterForTemplate).Contains("?"); - } - - private static bool PassedQueryString(string pathTemplate, int counterForTemplate) - { - return pathTemplate.Substring(0, counterForTemplate).Contains("?"); - } - - private bool IsCatchAll(string path, int counterForPath, string pathTemplate) - { - return string.IsNullOrEmpty(path) || (path.Length > counterForPath && path[counterForPath] == '/') && pathTemplate.Length > 1 - && pathTemplate.Substring(0, 2) == "/{" - && pathTemplate.IndexOf('}') == pathTemplate.Length - 1; - } - - private bool NothingAfterFirstForwardSlash(string path) - { - return path.Length == 1 || path.Length == 0; - } - - private string GetPlaceholderValue(string urlPathTemplate, string query, string variableName, string urlPath, int counterForUrl, char delimiter) - { - var positionOfNextSlash = urlPath.IndexOf(delimiter, counterForUrl); - - if (positionOfNextSlash == -1 || (urlPathTemplate.Trim(delimiter).EndsWith(variableName) && string.IsNullOrEmpty(query))) - { - positionOfNextSlash = urlPath.Length; - } - - var variableValue = urlPath.Substring(counterForUrl, positionOfNextSlash - counterForUrl); - - return variableValue; - } - - private string GetPlaceholderName(string urlPathTemplate, int counterForTemplate) - { - var postitionOfPlaceHolderClosingBracket = urlPathTemplate.IndexOf('}', counterForTemplate) + 1; - - var variableName = urlPathTemplate.Substring(counterForTemplate, postitionOfPlaceHolderClosingBracket - counterForTemplate); - - return variableName; - } - - private int GetNextCounterPosition(string urlTemplate, int counterForTemplate, char delimiter) - { - var closingPlaceHolderPositionOnTemplate = urlTemplate.IndexOf(delimiter, counterForTemplate); - return closingPlaceHolderPositionOnTemplate + 1; - } - - private bool CharactersDontMatch(char characterOne, char characterTwo) - { - return char.ToLower(characterOne) != char.ToLower(characterTwo); - } - - private bool ContinueScanningUrl(int counterForUrl, int urlLength) - { - return counterForUrl < urlLength; - } - - private bool IsPlaceholder(char character) - { - return character == '{'; - } - } -} +using System.Collections.Generic; +using Ocelot.Responses; + +namespace Ocelot.DownstreamRouteFinder.UrlMatcher +{ + public class UrlPathPlaceholderNameAndValueFinder : IPlaceholderNameAndValueFinder + { + public Response> Find(string path, string query, string pathTemplate) + { + var placeHolderNameAndValues = new List(); + + path = $"{path}{query}"; + + int counterForPath = 0; + + var delimiter = '/'; + var nextDelimiter = '/'; + + for (int counterForTemplate = 0; counterForTemplate < pathTemplate.Length; counterForTemplate++) + { + if ((path.Length > counterForPath) && CharactersDontMatch(pathTemplate[counterForTemplate], path[counterForPath]) && ContinueScanningUrl(counterForPath,path.Length)) + { + if (IsPlaceholder(pathTemplate[counterForTemplate])) + { + //should_find_multiple_query_string make test pass + if (PassedQueryString(pathTemplate, counterForTemplate)) + { + delimiter = '&'; + nextDelimiter = '&'; + } + + //should_find_multiple_query_string_and_path makes test pass + if (NotPassedQueryString(pathTemplate, counterForTemplate) && NoMoreForwardSlash(pathTemplate, counterForTemplate)) + { + delimiter = '?'; + nextDelimiter = '?'; + } + + var placeholderName = GetPlaceholderName(pathTemplate, counterForTemplate); + + var placeholderValue = GetPlaceholderValue(pathTemplate, query, placeholderName, path, counterForPath, delimiter); + + placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); + + counterForTemplate = GetNextCounterPosition(pathTemplate, counterForTemplate, '}'); + + counterForPath = GetNextCounterPosition(path, counterForPath, nextDelimiter); + + continue; + } + + return new OkResponse>(placeHolderNameAndValues); + } + else if(IsCatchAll(path, counterForPath, pathTemplate)) + { + var endOfPlaceholder = GetNextCounterPosition(pathTemplate, counterForTemplate, '}'); + + var placeholderName = GetPlaceholderName(pathTemplate, 1); + + if(NothingAfterFirstForwardSlash(path)) + { + placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, "")); + } + else + { + var placeholderValue = GetPlaceholderValue(pathTemplate, query, placeholderName, path, counterForPath + 1, '?'); + placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); + } + + counterForTemplate = endOfPlaceholder; + } + + counterForPath++; + } + + return new OkResponse>(placeHolderNameAndValues); + } + + private static bool NoMoreForwardSlash(string pathTemplate, int counterForTemplate) + { + return !pathTemplate.Substring(counterForTemplate).Contains("/"); + } + + private static bool NotPassedQueryString(string pathTemplate, int counterForTemplate) + { + return !pathTemplate.Substring(0, counterForTemplate).Contains("?"); + } + + private static bool PassedQueryString(string pathTemplate, int counterForTemplate) + { + return pathTemplate.Substring(0, counterForTemplate).Contains("?"); + } + + private bool IsCatchAll(string path, int counterForPath, string pathTemplate) + { + return string.IsNullOrEmpty(path) || (path.Length > counterForPath && path[counterForPath] == '/') && pathTemplate.Length > 1 + && pathTemplate.Substring(0, 2) == "/{" + && pathTemplate.IndexOf('}') == pathTemplate.Length - 1; + } + + private bool NothingAfterFirstForwardSlash(string path) + { + return path.Length == 1 || path.Length == 0; + } + + private string GetPlaceholderValue(string urlPathTemplate, string query, string variableName, string urlPath, int counterForUrl, char delimiter) + { + var positionOfNextSlash = urlPath.IndexOf(delimiter, counterForUrl); + + if (positionOfNextSlash == -1 || (urlPathTemplate.Trim(delimiter).EndsWith(variableName) && string.IsNullOrEmpty(query))) + { + positionOfNextSlash = urlPath.Length; + } + + var variableValue = urlPath.Substring(counterForUrl, positionOfNextSlash - counterForUrl); + + return variableValue; + } + + private string GetPlaceholderName(string urlPathTemplate, int counterForTemplate) + { + var postitionOfPlaceHolderClosingBracket = urlPathTemplate.IndexOf('}', counterForTemplate) + 1; + + var variableName = urlPathTemplate.Substring(counterForTemplate, postitionOfPlaceHolderClosingBracket - counterForTemplate); + + return variableName; + } + + private int GetNextCounterPosition(string urlTemplate, int counterForTemplate, char delimiter) + { + var closingPlaceHolderPositionOnTemplate = urlTemplate.IndexOf(delimiter, counterForTemplate); + return closingPlaceHolderPositionOnTemplate + 1; + } + + private bool CharactersDontMatch(char characterOne, char characterTwo) + { + return char.ToLower(characterOne) != char.ToLower(characterTwo); + } + + private bool ContinueScanningUrl(int counterForUrl, int urlLength) + { + return counterForUrl < urlLength; + } + + private bool IsPlaceholder(char character) + { + return character == '{'; + } + } +} diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index 6511590c..b5f4e69e 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -38,7 +38,10 @@ namespace Ocelot.Responder var content = await response.Content.ReadAsStreamAsync(); - AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ content.Length.ToString() }) ); + if(response.Content.Headers.ContentLength != null) + { + AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ response.Content.Headers.ContentLength.ToString() }) ); + } context.Response.OnStarting(state => { diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 685c1e00..19b47575 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -915,6 +915,41 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_match_multiple_paths_with_catch_all() + { + var port = 61999; + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/{everything}", + DownstreamScheme = "http", + UpstreamPathTemplate = "/{everything}", + UpstreamHttpMethod = new List { "Get" }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + } + }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/test/toot", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/test/toot")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + [Fact] public void should_fix_issue_271() { diff --git a/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs b/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs index 46919200..ccb9fa4a 100644 --- a/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs @@ -56,6 +56,44 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_return_response_200_with_odata_query_string() + { + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + var port = 57359; + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/{everything}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + } + }, + UpstreamPathTemplate = "/{everything}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", $"/odata/customers", "?$filter=Name%20eq%20'Sam'", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/odata/customers?$filter=Name eq 'Sam' ")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + [Fact] public void should_return_response_200_with_query_string_upstream_template() { @@ -206,7 +244,7 @@ namespace Ocelot.AcceptanceTests { _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => { - if (context.Request.PathBase.Value != basePath || context.Request.QueryString.Value != queryString) + if ((context.Request.PathBase.Value != basePath) || context.Request.QueryString.Value != queryString) { context.Response.StatusCode = 500; await context.Response.WriteAsync("downstream path didnt match base path"); diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs index 7ce2bb1d..46d6e503 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs @@ -1,357 +1,388 @@ -using System.Collections.Generic; -using System.Linq; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher -{ - public class UrlPathPlaceholderNameAndValueFinderTests - { - private readonly IPlaceholderNameAndValueFinder _finder; - private string _downstreamUrlPath; - private string _downstreamPathTemplate; - private Response> _result; - private string _query; - - public UrlPathPlaceholderNameAndValueFinderTests() - { - _finder = new UrlPathPlaceholderNameAndValueFinder(); - } - - [Fact] - public void can_match_down_stream_url() - { - this.Given(x => x.GivenIHaveAUpstreamPath("")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_nothing_then_placeholder_no_value_is_blank() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{url}", "") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_nothing_then_placeholder_value_is_test() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{url}", "test") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/test")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_forward_slash_then_placeholder_no_value_is_blank() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{url}", "") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_forward_slash() - { - var expectedTemplates = new List - { - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_forward_slash_then_placeholder_then_another_value() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{url}", "1") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/1/products")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}/products")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void should_not_find_anything() - { - this.Given(x => x.GivenIHaveAUpstreamPath("/products")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) - .BDDfy(); - } - - [Fact] - public void should_find_query_string() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/products")) - .And(x => x.GivenIHaveAQuery("?productId=1")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products?productId={productId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void should_find_query_string_dont_include_hardcoded() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/products")) - .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products?productId={productId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void should_find_multiple_query_string() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/products")) - .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products?productId={productId}&categoryId={categoryId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void should_find_multiple_query_string_and_path() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2"), - new PlaceholderNameAndValue("{account}", "3") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/products/3")) - .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/{account}?productId={productId}&categoryId={categoryId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void should_find_multiple_query_string_and_path_that_ends_with_slash() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2"), - new PlaceholderNameAndValue("{account}", "3") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("/products/3/")) - .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/{account}/?productId={productId}&categoryId={categoryId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_no_slash() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_one_slash() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_one_place_holder() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_two_place_holders() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/2")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/{categoryId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_two_place_holders_seperated_by_something() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_three_place_holders_seperated_by_something() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2"), - new PlaceholderNameAndValue("{variantId}", "123") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/123")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}/variant/{variantId}")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_three_place_holders() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{productId}", "1"), - new PlaceholderNameAndValue("{categoryId}", "2") - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}/variant/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_place_holder_to_final_url_path() - { - var expectedTemplates = new List - { - new PlaceholderNameAndValue("{finalUrlPath}", "product/products/categories/"), - }; - - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/categories/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/{finalUrlPath}/")) - .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) - .BDDfy(); - } - - private void ThenTheTemplatesVariablesAre(List expectedResults) - { - foreach (var expectedResult in expectedResults) - { - var result = _result.Data.First(t => t.Name == expectedResult.Name); - result.Value.ShouldBe(expectedResult.Value); - } - } - - private void GivenIHaveAUpstreamPath(string downstreamPath) - { - _downstreamUrlPath = downstreamPath; - } - - private void GivenIHaveAnUpstreamUrlTemplate(string downstreamUrlTemplate) - { - _downstreamPathTemplate = downstreamUrlTemplate; - } - - private void WhenIFindTheUrlVariableNamesAndValues() - { - _result = _finder.Find(_downstreamUrlPath, _query, _downstreamPathTemplate); - } - - private void GivenIHaveAQuery(string query) - { - _query = query; - } - } -} +using System.Collections.Generic; +using System.Linq; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher +{ + public class UrlPathPlaceholderNameAndValueFinderTests + { + private readonly IPlaceholderNameAndValueFinder _finder; + private string _downstreamUrlPath; + private string _downstreamPathTemplate; + private Response> _result; + private string _query; + + public UrlPathPlaceholderNameAndValueFinderTests() + { + _finder = new UrlPathPlaceholderNameAndValueFinder(); + } + + [Fact] + public void can_match_down_stream_url() + { + this.Given(x => x.GivenIHaveAUpstreamPath("")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_nothing_then_placeholder_no_value_is_blank() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{url}", "") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_nothing_then_placeholder_value_is_test() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{url}", "test") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/test")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_match_everything_in_path_with_query() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{everything}", "test/toot") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/test/toot")) + .And(x => GivenIHaveAQuery("?$filter=Name%20eq%20'Sam'")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{everything}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_match_everything_in_path() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{everything}", "test/toot") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/test/toot")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{everything}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_forward_slash_then_placeholder_no_value_is_blank() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{url}", "") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_forward_slash() + { + var expectedTemplates = new List + { + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_forward_slash_then_placeholder_then_another_value() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{url}", "1") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/1/products")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}/products")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_not_find_anything() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/products")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .BDDfy(); + } + + [Fact] + public void should_find_query_string() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/products")) + .And(x => x.GivenIHaveAQuery("?productId=1")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products?productId={productId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_find_query_string_dont_include_hardcoded() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/products")) + .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products?productId={productId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_find_multiple_query_string() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/products")) + .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products?productId={productId}&categoryId={categoryId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_find_multiple_query_string_and_path() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2"), + new PlaceholderNameAndValue("{account}", "3") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/products/3")) + .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/{account}?productId={productId}&categoryId={categoryId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void should_find_multiple_query_string_and_path_that_ends_with_slash() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2"), + new PlaceholderNameAndValue("{account}", "3") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/products/3/")) + .And(x => x.GivenIHaveAQuery("?productId=1&categoryId=2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/{account}/?productId={productId}&categoryId={categoryId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_no_slash() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_one_slash() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_one_place_holder() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_two_place_holders() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/2")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/{categoryId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_two_place_holders_seperated_by_something() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_three_place_holders_seperated_by_something() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2"), + new PlaceholderNameAndValue("{variantId}", "123") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/123")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}/variant/{variantId}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_three_place_holders() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/{productId}/categories/{categoryId}/variant/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_place_holder_to_final_url_path() + { + var expectedTemplates = new List + { + new PlaceholderNameAndValue("{finalUrlPath}", "product/products/categories/"), + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/categories/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("api/{finalUrlPath}/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + private void ThenTheTemplatesVariablesAre(List expectedResults) + { + foreach (var expectedResult in expectedResults) + { + var result = _result.Data.First(t => t.Name == expectedResult.Name); + result.Value.ShouldBe(expectedResult.Value); + } + } + + private void GivenIHaveAUpstreamPath(string downstreamPath) + { + _downstreamUrlPath = downstreamPath; + } + + private void GivenIHaveAnUpstreamUrlTemplate(string downstreamUrlTemplate) + { + _downstreamPathTemplate = downstreamUrlTemplate; + } + + private void WhenIFindTheUrlVariableNamesAndValues() + { + _result = _finder.Find(_downstreamUrlPath, _query, _downstreamPathTemplate); + } + + private void GivenIHaveAQuery(string query) + { + _query = query; + } + } +} From b0bdeb94027f080c4ec62bbd7e1c69e5d5611ebb Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 25 Aug 2018 12:32:56 +0100 Subject: [PATCH 39/50] #534 fixed failing tests for this issue (#575) --- .../Creator/UpstreamTemplatePatternCreator.cs | 173 +++--- test/Ocelot.AcceptanceTests/RoutingTests.cs | 35 ++ .../UpstreamTemplatePatternCreatorTests.cs | 504 ++++++++-------- .../UrlMatcher/RegExUrlMatcherTests.cs | 560 +++++++++--------- 4 files changed, 676 insertions(+), 596 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs index 90ad721d..fbf62d4a 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs @@ -1,84 +1,93 @@ -using System.Collections.Generic; -using Ocelot.Configuration.File; -using Ocelot.Values; - -namespace Ocelot.Configuration.Creator -{ - public class UpstreamTemplatePatternCreator : IUpstreamTemplatePatternCreator - { - private const string RegExMatchOneOrMoreOfEverything = ".+"; - private const string RegExMatchEndString = "$"; - private const string RegExIgnoreCase = "(?i)"; - private const string RegExForwardSlashOnly = "^/$"; - private const string RegExForwardSlashAndOnePlaceHolder = "^/.*"; - - public UpstreamPathTemplate Create(IReRoute reRoute) - { +using System.Collections.Generic; +using Ocelot.Configuration.File; +using Ocelot.Values; + +namespace Ocelot.Configuration.Creator +{ + public class UpstreamTemplatePatternCreator : IUpstreamTemplatePatternCreator + { + private const string RegExMatchOneOrMoreOfEverything = ".+"; + private const string RegExMatchOneOrMoreOfEverythingUntilNextForwardSlash = "[^/]+"; + private const string RegExMatchEndString = "$"; + private const string RegExIgnoreCase = "(?i)"; + private const string RegExForwardSlashOnly = "^/$"; + private const string RegExForwardSlashAndOnePlaceHolder = "^/.*"; + + public UpstreamPathTemplate Create(IReRoute reRoute) + { var upstreamTemplate = reRoute.UpstreamPathTemplate; - - - var placeholders = new List(); - - for (var i = 0; i < upstreamTemplate.Length; i++) - { - if (IsPlaceHolder(upstreamTemplate, i)) - { - var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i); - var difference = postitionOfPlaceHolderClosingBracket - i + 1; - var placeHolderName = upstreamTemplate.Substring(i, difference); - placeholders.Add(placeHolderName); - - //hack to handle /{url} case - if(ForwardSlashAndOnePlaceHolder(upstreamTemplate, placeholders, postitionOfPlaceHolderClosingBracket)) - { - return new UpstreamPathTemplate(RegExForwardSlashAndOnePlaceHolder, 0, false); - } - } + + var placeholders = new List(); + + for (var i = 0; i < upstreamTemplate.Length; i++) + { + if (IsPlaceHolder(upstreamTemplate, i)) + { + var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i); + var difference = postitionOfPlaceHolderClosingBracket - i + 1; + var placeHolderName = upstreamTemplate.Substring(i, difference); + placeholders.Add(placeHolderName); + + //hack to handle /{url} case + if(ForwardSlashAndOnePlaceHolder(upstreamTemplate, placeholders, postitionOfPlaceHolderClosingBracket)) + { + return new UpstreamPathTemplate(RegExForwardSlashAndOnePlaceHolder, 0, false); + } + } } - - var containsQueryString = false; - - if (upstreamTemplate.Contains("?")) - { - containsQueryString = true; - upstreamTemplate = upstreamTemplate.Replace("?", "\\?"); - } - - foreach (var placeholder in placeholders) - { - upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchOneOrMoreOfEverything); - } - - if (upstreamTemplate == "/") - { - return new UpstreamPathTemplate(RegExForwardSlashOnly, reRoute.Priority, containsQueryString); - } - - if(upstreamTemplate.EndsWith("/")) - { - upstreamTemplate = upstreamTemplate.Remove(upstreamTemplate.Length -1, 1) + "(/|)"; - } - - var route = reRoute.ReRouteIsCaseSensitive - ? $"^{upstreamTemplate}{RegExMatchEndString}" - : $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; - - return new UpstreamPathTemplate(route, reRoute.Priority, containsQueryString); - } - - private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List placeholders, int postitionOfPlaceHolderClosingBracket) - { - if(upstreamTemplate.Substring(0, 2) == "/{" && placeholders.Count == 1 && upstreamTemplate.Length == postitionOfPlaceHolderClosingBracket + 1) - { - return true; - } - - return false; - } - - private bool IsPlaceHolder(string upstreamTemplate, int i) - { - return upstreamTemplate[i] == '{'; - } - } -} + + var containsQueryString = false; + + if (upstreamTemplate.Contains("?")) + { + containsQueryString = true; + upstreamTemplate = upstreamTemplate.Replace("?", "\\?"); + } + + for (int i = 0; i < placeholders.Count; i++) + { + var indexOfPlaceholder = upstreamTemplate.IndexOf(placeholders[i]); + var indexOfNextForwardSlash = upstreamTemplate.IndexOf("/", indexOfPlaceholder); + if(indexOfNextForwardSlash < indexOfPlaceholder || (containsQueryString && upstreamTemplate.IndexOf("?") < upstreamTemplate.IndexOf(placeholders[i]))) + { + upstreamTemplate = upstreamTemplate.Replace(placeholders[i], RegExMatchOneOrMoreOfEverything); + } + else + { + upstreamTemplate = upstreamTemplate.Replace(placeholders[i], RegExMatchOneOrMoreOfEverythingUntilNextForwardSlash); + } + } + + if (upstreamTemplate == "/") + { + return new UpstreamPathTemplate(RegExForwardSlashOnly, reRoute.Priority, containsQueryString); + } + + if(upstreamTemplate.EndsWith("/")) + { + upstreamTemplate = upstreamTemplate.Remove(upstreamTemplate.Length -1, 1) + "(/|)"; + } + + var route = reRoute.ReRouteIsCaseSensitive + ? $"^{upstreamTemplate}{RegExMatchEndString}" + : $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; + + return new UpstreamPathTemplate(route, reRoute.Priority, containsQueryString); + } + + private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List placeholders, int postitionOfPlaceHolderClosingBracket) + { + if(upstreamTemplate.Substring(0, 2) == "/{" && placeholders.Count == 1 && upstreamTemplate.Length == postitionOfPlaceHolderClosingBracket + 1) + { + return true; + } + + return false; + } + + private bool IsPlaceHolder(string upstreamTemplate, int i) + { + return upstreamTemplate[i] == '{'; + } + } +} diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 19b47575..5fa7bcff 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -21,6 +21,41 @@ namespace Ocelot.AcceptanceTests _steps = new Steps(); } + [Fact] + public void should_not_match_forward_slash_in_pattern_before_next_forward_slash() + { + var port = 31879; + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/v{apiVersion}/cards", + DownstreamScheme = "http", + UpstreamPathTemplate = "/api/v{apiVersion}/cards", + UpstreamHttpMethod = new List { "Get" }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + } + }, + Priority = 1 + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}/", "/api/v1/aaaaaaaaa/cards", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/v1/aaaaaaaaa/cards")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + [Fact] public void should_return_response_404_when_no_configuration_at_all() { diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs index d924586d..1aec3ec8 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs @@ -1,244 +1,260 @@ -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Ocelot.Values; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class UpstreamTemplatePatternCreatorTests - { - private FileReRoute _fileReRoute; - private readonly UpstreamTemplatePatternCreator _creator; - private UpstreamPathTemplate _result; - - public UpstreamTemplatePatternCreatorTests() - { - _creator = new UpstreamTemplatePatternCreator(); - } - - [Fact] - public void should_use_re_route_priority() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/orders/{catchAll}", - Priority = 0 - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^(?i)/orders/.+$")) - .And(x => ThenThePriorityIs(0)) - .BDDfy(); - } - - [Fact] - public void should_use_zero_priority() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/{catchAll}", - Priority = 1 - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^/.*")) - .And(x => ThenThePriorityIs(0)) - .BDDfy(); - } - - [Fact] - public void should_set_upstream_template_pattern_to_ignore_case_sensitivity() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/PRODUCTS/{productId}", - ReRouteIsCaseSensitive = false - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS/.+$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - [Fact] - public void should_match_forward_slash_or_no_forward_slash_if_template_end_with_forward_slash() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/PRODUCTS/", - ReRouteIsCaseSensitive = false - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS(/|)$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - [Fact] - public void should_set_upstream_template_pattern_to_respect_case_sensitivity() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/PRODUCTS/{productId}", - ReRouteIsCaseSensitive = true - }; - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^/PRODUCTS/.+$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_matches_anything_to_end_of_string() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - ReRouteIsCaseSensitive = true - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^/api/products/.+$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_matches_more_than_one_placeholder() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}", - ReRouteIsCaseSensitive = true - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^/api/products/.+/variants/.+$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_matches_more_than_one_placeholder_with_trailing_slash() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}/", - ReRouteIsCaseSensitive = true - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^/api/products/.+/variants/.+(/|)$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_matches_to_end_of_string() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/" - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^/$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_matches_to_end_of_string_when_slash_and_placeholder() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/{url}" - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^/.*")) - .And(x => ThenThePriorityIs(0)) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_starts_with_placeholder_then_has_another_later() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/{productId}/products/variants/{variantId}/", - ReRouteIsCaseSensitive = true - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^/.+/products/variants/.+(/|)$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_matches_query_string() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}" - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^(?i)/api/subscriptions/.+/updates\\?unitId=.+$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_matches_query_string_with_multiple_params() - { - var fileReRoute = new FileReRoute - { - UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}&productId={productId}" - }; - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .When(x => x.WhenICreateTheTemplatePattern()) - .Then(x => x.ThenTheFollowingIsReturned("^(?i)/api/subscriptions/.+/updates\\?unitId=.+&productId=.+$")) - .And(x => ThenThePriorityIs(1)) - .BDDfy(); - } - - private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute) - { - _fileReRoute = fileReRoute; - } - - private void WhenICreateTheTemplatePattern() - { - _result = _creator.Create(_fileReRoute); - } - - private void ThenTheFollowingIsReturned(string expected) - { - _result.Template.ShouldBe(expected); - } - - private void ThenThePriorityIs(int v) - { - _result.Priority.ShouldBe(v); - } - } -} +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class UpstreamTemplatePatternCreatorTests + { + private FileReRoute _fileReRoute; + private readonly UpstreamTemplatePatternCreator _creator; + private UpstreamPathTemplate _result; + + public UpstreamTemplatePatternCreatorTests() + { + _creator = new UpstreamTemplatePatternCreator(); + } + + [Fact] + public void should_match_up_to_next_slash() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/api/v{apiVersion}/cards", + Priority = 0 + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^(?i)/api/v[^/]+/cards$")) + .And(x => ThenThePriorityIs(0)) + .BDDfy(); + } + + [Fact] + public void should_use_re_route_priority() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/orders/{catchAll}", + Priority = 0 + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^(?i)/orders/.+$")) + .And(x => ThenThePriorityIs(0)) + .BDDfy(); + } + + [Fact] + public void should_use_zero_priority() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/{catchAll}", + Priority = 1 + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/.*")) + .And(x => ThenThePriorityIs(0)) + .BDDfy(); + } + + [Fact] + public void should_set_upstream_template_pattern_to_ignore_case_sensitivity() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/PRODUCTS/{productId}", + ReRouteIsCaseSensitive = false + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS/.+$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + [Fact] + public void should_match_forward_slash_or_no_forward_slash_if_template_end_with_forward_slash() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/PRODUCTS/", + ReRouteIsCaseSensitive = false + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS(/|)$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + [Fact] + public void should_set_upstream_template_pattern_to_respect_case_sensitivity() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/PRODUCTS/{productId}", + ReRouteIsCaseSensitive = true + }; + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/PRODUCTS/.+$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_matches_anything_to_end_of_string() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}", + ReRouteIsCaseSensitive = true + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/api/products/.+$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_matches_more_than_one_placeholder() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}", + ReRouteIsCaseSensitive = true + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/api/products/[^/]+/variants/.+$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_matches_more_than_one_placeholder_with_trailing_slash() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}/", + ReRouteIsCaseSensitive = true + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/api/products/[^/]+/variants/[^/]+(/|)$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_matches_to_end_of_string() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/" + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_matches_to_end_of_string_when_slash_and_placeholder() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/{url}" + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/.*")) + .And(x => ThenThePriorityIs(0)) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_starts_with_placeholder_then_has_another_later() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/{productId}/products/variants/{variantId}/", + ReRouteIsCaseSensitive = true + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/[^/]+/products/variants/[^/]+(/|)$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_matches_query_string() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}" + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^(?i)/api/subscriptions/[^/]+/updates\\?unitId=.+$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_matches_query_string_with_multiple_params() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}&productId={productId}" + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^(?i)/api/subscriptions/[^/]+/updates\\?unitId=.+&productId=.+$")) + .And(x => ThenThePriorityIs(1)) + .BDDfy(); + } + + private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void WhenICreateTheTemplatePattern() + { + _result = _creator.Create(_fileReRoute); + } + + private void ThenTheFollowingIsReturned(string expected) + { + _result.Template.ShouldBe(expected); + } + + private void ThenThePriorityIs(int v) + { + _result.Priority.ShouldBe(v); + } + } +} diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs index 10fe106e..36f2f852 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs @@ -1,271 +1,291 @@ -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher -{ - public class RegExUrlMatcherTests - { - private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; - private string _path; - private string _downstreamPathTemplate; - private Response _result; - private string _queryString; - private bool _containsQueryString; - - public RegExUrlMatcherTests() - { - _urlMatcher = new RegExUrlMatcher(); - } - - [Fact] - public void should_match_path_with_no_query_string() - { - const string regExForwardSlashAndOnePlaceHolder = "^(?i)/newThing$"; - - this.Given(x => x.GivenIHaveAUpstreamPath("/newThing")) - .And(_ => GivenIHaveAQueryString("?DeviceType=IphoneApp&Browser=moonpigIphone&BrowserString=-&CountryCode=123&DeviceName=iPhone 5 (GSM+CDMA)&OperatingSystem=iPhone OS 7.1.2&BrowserVersion=3708AdHoc&ipAddress=-")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder)) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsTrue()) - .BDDfy(); +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher +{ + public class RegExUrlMatcherTests + { + private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; + private string _path; + private string _downstreamPathTemplate; + private Response _result; + private string _queryString; + private bool _containsQueryString; + + public RegExUrlMatcherTests() + { + _urlMatcher = new RegExUrlMatcher(); } - - [Fact] - public void should_match_query_string() - { - const string regExForwardSlashAndOnePlaceHolder = "^(?i)/api/subscriptions/.+/updates\\?unitId=.+$"; - - this.Given(x => x.GivenIHaveAUpstreamPath("/api/subscriptions/1/updates")) - .And(_ => GivenIHaveAQueryString("?unitId=2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder)) - .And(_ => GivenThereIsAQueryInTemplate()) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void should_match_query_string_with_multiple_params() - { - const string regExForwardSlashAndOnePlaceHolder = "^(?i)/api/subscriptions/.+/updates\\?unitId=.+&productId=.+$"; - - this.Given(x => x.GivenIHaveAUpstreamPath("/api/subscriptions/1/updates?unitId=2")) - .And(_ => GivenIHaveAQueryString("?unitId=2&productId=2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder)) - .And(_ => GivenThereIsAQueryInTemplate()) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void should_not_match_slash_becaue_we_need_to_match_something_after_it() - { - const string regExForwardSlashAndOnePlaceHolder = "^/[0-9a-zA-Z].+"; - - this.Given(x => x.GivenIHaveAUpstreamPath("/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder)) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsFalse()) - .BDDfy(); - } - - [Fact] - public void should_not_match_forward_slash_only_regex() - { - this.Given(x => x.GivenIHaveAUpstreamPath("/working/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsFalse()) - .BDDfy(); - } - - [Fact] - public void should_not_match_issue_134() - { - this.Given(x => x.GivenIHaveAUpstreamPath("/api/vacancy/1/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)/vacancy/.+/$")) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsFalse()) - .BDDfy(); - } - - [Fact] - public void should_match_forward_slash_only_regex() - { - this.Given(x => x.GivenIHaveAUpstreamPath("/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void should_find_match_when_template_smaller_than_valid_path() - { - this.Given(x => x.GivenIHaveAUpstreamPath("/api/products/2354325435624623464235")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/api/products/.+$")) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void should_not_find_match() - { - this.Given(x => x.GivenIHaveAUpstreamPath("/api/values")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsFalse()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url() - { - this.Given(x => x.GivenIHaveAUpstreamPath("")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^$")) - .When(x => x.WhenIMatchThePaths()) - .And(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_no_slash() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_one_slash() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_one_place_holder() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_two_place_holders() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/2")) - .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/.+$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_two_place_holders_seperated_by_something() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/categories/.+$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_three_place_holders_seperated_by_something() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/123")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/categories/.+/variant/.+$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void can_match_down_stream_url_with_downstream_template_with_three_place_holders() - { - this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/categories/.+/variant/$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void should_ignore_case_sensitivity() - { - this.Given(x => x.GivenIHaveAUpstreamPath("API/product/products/1/categories/2/variant/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)api/product/products/.+/categories/.+/variant/$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsTrue()) - .BDDfy(); - } - - [Fact] - public void should_respect_case_sensitivity() - { - this.Given(x => x.GivenIHaveAUpstreamPath("API/product/products/1/categories/2/variant/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/categories/.+/variant/$")) - .When(x => x.WhenIMatchThePaths()) - .Then(x => x.ThenTheResultIsFalse()) - .BDDfy(); - } - - private void GivenIHaveAUpstreamPath(string path) - { - _path = path; - } - - private void GivenIHaveAQueryString(string queryString) - { - _queryString = queryString; - } - - private void GivenIHaveAnUpstreamUrlTemplatePattern(string downstreamUrlTemplate) - { - _downstreamPathTemplate = downstreamUrlTemplate; - } - - private void WhenIMatchThePaths() - { - _result = _urlMatcher.Match(_path, _queryString, _downstreamPathTemplate, _containsQueryString); - } - - private void ThenTheResultIsTrue() - { - _result.Data.Match.ShouldBeTrue(); - } - - private void ThenTheResultIsFalse() - { - _result.Data.Match.ShouldBeFalse(); - } - - private void GivenThereIsAQueryInTemplate() - { - _containsQueryString = true; - } - } -} + + [Fact] + public void should_not_match() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/api/v1/aaaaaaaaa/cards")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)/api/v[^/]+/cards$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void should_match() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/api/v1/cards")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)/api/v[^/]+/cards$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_match_path_with_no_query_string() + { + const string regExForwardSlashAndOnePlaceHolder = "^(?i)/newThing$"; + + this.Given(x => x.GivenIHaveAUpstreamPath("/newThing")) + .And(_ => GivenIHaveAQueryString("?DeviceType=IphoneApp&Browser=moonpigIphone&BrowserString=-&CountryCode=123&DeviceName=iPhone 5 (GSM+CDMA)&OperatingSystem=iPhone OS 7.1.2&BrowserVersion=3708AdHoc&ipAddress=-")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder)) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_match_query_string() + { + const string regExForwardSlashAndOnePlaceHolder = "^(?i)/api/subscriptions/[^/]+/updates\\?unitId=.+$"; + + this.Given(x => x.GivenIHaveAUpstreamPath("/api/subscriptions/1/updates")) + .And(_ => GivenIHaveAQueryString("?unitId=2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder)) + .And(_ => GivenThereIsAQueryInTemplate()) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_match_query_string_with_multiple_params() + { + const string regExForwardSlashAndOnePlaceHolder = "^(?i)/api/subscriptions/[^/]+/updates\\?unitId=.+&productId=.+$"; + + this.Given(x => x.GivenIHaveAUpstreamPath("/api/subscriptions/1/updates?unitId=2")) + .And(_ => GivenIHaveAQueryString("?unitId=2&productId=2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder)) + .And(_ => GivenThereIsAQueryInTemplate()) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_not_match_slash_becaue_we_need_to_match_something_after_it() + { + const string regExForwardSlashAndOnePlaceHolder = "^/[0-9a-zA-Z].+"; + + this.Given(x => x.GivenIHaveAUpstreamPath("/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder)) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void should_not_match_forward_slash_only_regex() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/working/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void should_not_match_issue_134() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/api/vacancy/1/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)/vacancy/[^/]+/$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void should_match_forward_slash_only_regex() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_find_match_when_template_smaller_than_valid_path() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/api/products/2354325435624623464235")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/api/products/.+$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_not_find_match() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/api/values")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url() + { + this.Given(x => x.GivenIHaveAUpstreamPath("")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_no_slash() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_one_slash() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_one_place_holder() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_two_place_holders() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/2")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/[^/]+/.+$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_two_place_holders_seperated_by_something() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/[^/]+/categories/.+$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_three_place_holders_seperated_by_something() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/123")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/[^/]+/categories/[^/]+/variant/.+$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_downstream_template_with_three_place_holders() + { + this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/[^/]+/categories/[^/]+/variant/$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_ignore_case_sensitivity() + { + this.Given(x => x.GivenIHaveAUpstreamPath("API/product/products/1/categories/2/variant/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)api/product/products/[^/]+/categories/[^/]+/variant/$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + + [Fact] + public void should_respect_case_sensitivity() + { + this.Given(x => x.GivenIHaveAUpstreamPath("API/product/products/1/categories/2/variant/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/[^/]+/categories/[^/]+/variant/$")) + .When(x => x.WhenIMatchThePaths()) + .Then(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + private void GivenIHaveAUpstreamPath(string path) + { + _path = path; + } + + private void GivenIHaveAQueryString(string queryString) + { + _queryString = queryString; + } + + private void GivenIHaveAnUpstreamUrlTemplatePattern(string downstreamUrlTemplate) + { + _downstreamPathTemplate = downstreamUrlTemplate; + } + + private void WhenIMatchThePaths() + { + _result = _urlMatcher.Match(_path, _queryString, _downstreamPathTemplate, _containsQueryString); + } + + private void ThenTheResultIsTrue() + { + _result.Data.Match.ShouldBeTrue(); + } + + private void ThenTheResultIsFalse() + { + _result.Data.Match.ShouldBeFalse(); + } + + private void GivenThereIsAQueryInTemplate() + { + _containsQueryString = true; + } + } +} From 369fc5b7a465a75d10e0242dc425c9afcf88f8ff Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 25 Aug 2018 12:33:49 +0100 Subject: [PATCH 40/50] #555 added some tests for this issue but struggling to replicate (#570) * #555 added some tests for this issue but struggling to replicate * #555 removed cmd=instance from service fabric code as someone who knows service fabric says its not needed I * #555 renamed test --- .../DownstreamUrlCreatorMiddleware.cs | 14 +------ .../ServiceFabricTests.cs | 41 ++++++++++++++++++- .../DownstreamUrlCreatorMiddlewareTests.cs | 4 +- .../Request/DownstreamRequestTests.cs | 21 ++++++++++ 4 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 test/Ocelot.UnitTests/Request/DownstreamRequestTests.cs diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 855efa34..81f5019b 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -117,24 +117,12 @@ namespace Ocelot.DownstreamUrlCreator.Middleware { var query = context.DownstreamRequest.Query; var serviceFabricPath = $"/{context.DownstreamReRoute.ServiceName + dsPath.Data.Value}"; - - if (RequestForStatefullService(query)) - { - return (serviceFabricPath, query); - } - - var split = string.IsNullOrEmpty(query) ? "?" : "&"; - return (serviceFabricPath, $"{query}{split}cmd=instance"); + return (serviceFabricPath, query); } private static bool ServiceFabricRequest(DownstreamContext context) { return context.Configuration.ServiceProviderConfiguration.Type == "ServiceFabric" && context.DownstreamReRoute.UseServiceDiscovery; } - - private static bool RequestForStatefullService(string query) - { - return query.Contains("PartitionKind") && query.Contains("PartitionKey"); - } } } diff --git a/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs b/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs index e4c2abc4..19268f0d 100644 --- a/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs @@ -20,6 +20,43 @@ namespace Ocelot.AcceptanceTests _steps = new Steps(); } + [Fact] + public void should_fix_issue_555() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/{everything}", + DownstreamScheme = "http", + UpstreamPathTemplate = "/{everything}", + UpstreamHttpMethod = new List { "Get" }, + UseServiceDiscovery = true, + ServiceName = "OcelotServiceApplication/OcelotApplicationService" + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = 19081, + Type = "ServiceFabric" + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:19081", "/OcelotServiceApplication/OcelotApplicationService/a", 200, "Hello from Laura", "b=c")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/a?b=c")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + [Fact] public void should_support_service_fabric_naming_and_dns_service_stateless_and_guest() { @@ -48,10 +85,10 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:19081", "/OcelotServiceApplication/OcelotApplicationService/api/values", 200, "Hello from Laura", "cmd=instance")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:19081", "/OcelotServiceApplication/OcelotApplicationService/api/values", 200, "Hello from Laura", "test=best")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/EquipmentInterfaces")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/EquipmentInterfaces?test=best")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .BDDfy(); diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index 2da93538..ca190240 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -225,7 +225,7 @@ .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081")) .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1?cmd=instance")) + .Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1")) .BDDfy(); } @@ -255,7 +255,7 @@ .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081?Tom=test&laura=1")) .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1?Tom=test&laura=1&cmd=instance")) + .Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1?Tom=test&laura=1")) .BDDfy(); } diff --git a/test/Ocelot.UnitTests/Request/DownstreamRequestTests.cs b/test/Ocelot.UnitTests/Request/DownstreamRequestTests.cs new file mode 100644 index 00000000..eee88460 --- /dev/null +++ b/test/Ocelot.UnitTests/Request/DownstreamRequestTests.cs @@ -0,0 +1,21 @@ +using System; +using System.Net.Http; +using Ocelot.Request.Middleware; +using Shouldly; +using Xunit; + +namespace Ocelot.UnitTests.Request +{ + public class DownstreamRequestTests + { + [Fact] + public void should_have_question_mark_with_question_mark_prefixed() + { + var httpRequestMessage = new HttpRequestMessage(); + httpRequestMessage.RequestUri = new Uri("https://example.com/a?b=c"); + var downstreamRequest = new DownstreamRequest(httpRequestMessage); + var result = downstreamRequest.ToHttpRequestMessage(); + result.RequestUri.Query.ShouldBe("?b=c"); + } + } +} From 6f9811cb1ba3235a6cd18406510458384597f340 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sun, 26 Aug 2018 11:56:18 +0100 Subject: [PATCH 41/50] make sure files are copied for manual tests, was not working --- .../Ocelot.ManualTest.csproj | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj index 2f81d2b9..1a26cd0f 100644 --- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj +++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj @@ -9,20 +9,19 @@ osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 ..\..\codeanalysis.ruleset - - + + + Always PreserveNewest - - - - + + + Always PreserveNewest - - - - - PreserveNewest - + + + Always + PreserveNewest + From e257d82abf67cc8596fea61087ddb85bb8694605 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sun, 26 Aug 2018 12:38:42 +0100 Subject: [PATCH 42/50] Revert "make sure files are copied for manual tests, was not working" This reverts commit 6f9811cb1ba3235a6cd18406510458384597f340. --- .../Ocelot.ManualTest.csproj | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj index 1a26cd0f..2f81d2b9 100644 --- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj +++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj @@ -9,19 +9,20 @@ osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 ..\..\codeanalysis.ruleset - - - Always + + PreserveNewest - - - Always + + + + PreserveNewest - - - Always - PreserveNewest - + + + + + PreserveNewest + From 8db5570840691e6db3b32d67207f97b432686273 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Mon, 27 Aug 2018 17:47:45 +0100 Subject: [PATCH 43/50] Feature/fix #568 (#576) * #568 Ocelot wont start if QoSOptions specified and no QoS DelegatingHandler registered e.g. no use of Ocelot.Provider.Polly. Also adds a NoQosDelegatingHandler and logs an error if ive missed something and the user can get to making the request * #568 sadly something wierd with IServiceProvider and FluentValidation so I'm just defaulting to warning and noqosdelegatinghandler if someone doesnt register but provides options, also added basic in memory cache in case people dont use a specific package --- src/Ocelot/Cache/CacheObject.cs | 16 +++++ src/Ocelot/Cache/IOcelotCache.cs | 21 ------ src/Ocelot/Cache/InMemoryCache.cs | 71 +++++++++++++++++++ .../Creator/QoSOptionsCreator.cs | 4 +- .../File/FileGlobalConfiguration.cs | 2 +- .../FileConfigurationFluentValidator.cs | 13 +++- .../Validator/ReRouteFluentValidator.cs | 29 +++++--- .../DependencyInjection/OcelotBuilder.cs | 32 ++++----- .../DelegatingHandlerHandlerFactory.cs | 23 +++--- .../IDelegatingHandlerHandlerFactory.cs | 14 ++-- .../Requester/NoQosDelegatingHandler.cs | 8 +++ .../CannotStartOcelotTests.cs | 13 ++-- .../ReturnsErrorTests.cs | 1 + test/Ocelot.AcceptanceTests/RoutingTests.cs | 8 +-- .../Cache/InMemoryCacheTests.cs | 69 ++++++++++++++++++ .../ConfigurationFluentValidationTests.cs | 52 +++++++++----- ...atingHandlerHandlerProviderFactoryTests.cs | 70 +++++++++++++++--- .../Requester/HttpClientBuilderTests.cs | 34 ++++----- 18 files changed, 351 insertions(+), 129 deletions(-) create mode 100644 src/Ocelot/Cache/CacheObject.cs create mode 100644 src/Ocelot/Cache/InMemoryCache.cs create mode 100644 src/Ocelot/Requester/NoQosDelegatingHandler.cs create mode 100644 test/Ocelot.UnitTests/Cache/InMemoryCacheTests.cs diff --git a/src/Ocelot/Cache/CacheObject.cs b/src/Ocelot/Cache/CacheObject.cs new file mode 100644 index 00000000..adee3ed8 --- /dev/null +++ b/src/Ocelot/Cache/CacheObject.cs @@ -0,0 +1,16 @@ +namespace Ocelot.Cache +{ + using System; + + class CacheObject + { + public CacheObject(T value, DateTime expires) + { + Value = value; + Expires = expires; + } + + public T Value { get; } + public DateTime Expires { get; } + } +} diff --git a/src/Ocelot/Cache/IOcelotCache.cs b/src/Ocelot/Cache/IOcelotCache.cs index e70afec1..c2054f6e 100644 --- a/src/Ocelot/Cache/IOcelotCache.cs +++ b/src/Ocelot/Cache/IOcelotCache.cs @@ -5,28 +5,7 @@ namespace Ocelot.Cache public interface IOcelotCache { void Add(string key, T value, TimeSpan ttl, string region); - void AddAndDelete(string key, T value, TimeSpan ttl, string region); T Get(string key, string region); void ClearRegion(string region); - } - - public class NoCache : IOcelotCache - { - public void Add(string key, T value, TimeSpan ttl, string region) - { - } - - public void AddAndDelete(string key, T value, TimeSpan ttl, string region) - { - } - - public void ClearRegion(string region) - { - } - - public T Get(string key, string region) - { - return default(T); - } } } diff --git a/src/Ocelot/Cache/InMemoryCache.cs b/src/Ocelot/Cache/InMemoryCache.cs new file mode 100644 index 00000000..fe1c8791 --- /dev/null +++ b/src/Ocelot/Cache/InMemoryCache.cs @@ -0,0 +1,71 @@ +namespace Ocelot.Cache +{ + using System; + using System.Collections.Generic; + + public class InMemoryCache : IOcelotCache + { + private readonly Dictionary> _cache; + private readonly Dictionary> _regions; + + public InMemoryCache() + { + _cache = new Dictionary>(); + _regions = new Dictionary>(); + } + + public void Add(string key, T value, TimeSpan ttl, string region) + { + if (ttl.TotalMilliseconds <= 0) + { + return; + } + + var expires = DateTime.UtcNow.Add(ttl); + + _cache.Add(key, new CacheObject(value, expires)); + + if (_regions.ContainsKey(region)) + { + var current = _regions[region]; + if (!current.Contains(key)) + { + current.Add(key); + } + } + else + { + _regions.Add(region, new List{ key }); + } + } + + public void ClearRegion(string region) + { + if (_regions.ContainsKey(region)) + { + var keys = _regions[region]; + foreach (var key in keys) + { + _cache.Remove(key); + } + } + } + + public T Get(string key, string region) + { + if (_cache.ContainsKey(key)) + { + var cached = _cache[key]; + + if (cached.Expires > DateTime.UtcNow) + { + return cached.Value; + } + + _cache.Remove(key); + } + + return default(T); + } + } +} diff --git a/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs b/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs index 30adaed2..6d0af409 100644 --- a/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs @@ -17,8 +17,8 @@ namespace Ocelot.Configuration.Creator public QoSOptions Create(FileQoSOptions options, string pathTemplate, string[] httpMethods) { - var key = CreateKey(pathTemplate, httpMethods); - + var key = CreateKey(pathTemplate, httpMethods); + return Map(key, options.TimeoutValue, options.DurationOfBreak, options.ExceptionsAllowedBeforeBreaking); } diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs index 27f1f2ae..8d1ca3f5 100644 --- a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs @@ -23,7 +23,7 @@ public FileLoadBalancerOptions LoadBalancerOptions { get; set; } - public string DownstreamScheme { get; set; } + public string DownstreamScheme { get; set; } public FileHttpHandlerOptions HttpHandlerOptions { get; set; } } diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs index d0c6de0e..ebac0a30 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs @@ -9,13 +9,20 @@ using System.Threading.Tasks; namespace Ocelot.Configuration.Validator { + using System; + using Microsoft.Extensions.DependencyInjection; + using Requester; + public class FileConfigurationFluentValidator : AbstractValidator, IConfigurationValidator { - public FileConfigurationFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider) + private readonly IServiceProvider _provider; + + public FileConfigurationFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, IServiceProvider provider) { + _provider = provider; RuleFor(configuration => configuration.ReRoutes) - .SetCollectionValidator(new ReRouteFluentValidator(authenticationSchemeProvider)); - + .SetCollectionValidator(new ReRouteFluentValidator(authenticationSchemeProvider, provider)); + RuleForEach(configuration => configuration.ReRoutes) .Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.ReRoutes)) .WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate"); diff --git a/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs b/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs index c2f4dc62..c54fc2aa 100644 --- a/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs @@ -1,19 +1,24 @@ -using FluentValidation; -using Microsoft.AspNetCore.Authentication; -using Ocelot.Configuration.File; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Ocelot.Configuration.Validator +namespace Ocelot.Configuration.Validator { + using FluentValidation; + using Microsoft.AspNetCore.Authentication; + using Ocelot.Configuration.File; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using System; + using Microsoft.Extensions.DependencyInjection; + using Requester; + public class ReRouteFluentValidator : AbstractValidator { private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider; + private readonly IServiceProvider _serviceProvider; - public ReRouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider) + public ReRouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, IServiceProvider serviceProvider) { _authenticationSchemeProvider = authenticationSchemeProvider; + _serviceProvider = serviceProvider; RuleFor(reRoute => reRoute.DownstreamPathTemplate) .Must(path => path.StartsWith("/")) @@ -48,11 +53,13 @@ namespace Ocelot.Configuration.Validator .WithMessage("{PropertyValue} is unsupported authentication provider"); When(reRoute => reRoute.UseServiceDiscovery, () => { - RuleFor(r => r.ServiceName).NotEmpty().WithMessage("ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!"); + RuleFor(r => r.ServiceName).NotEmpty() + .WithMessage("ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!"); }); When(reRoute => !reRoute.UseServiceDiscovery, () => { - RuleFor(r => r.DownstreamHostAndPorts).NotEmpty().WithMessage("When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!"); + RuleFor(r => r.DownstreamHostAndPorts).NotEmpty() + .WithMessage("When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!"); }); When(reRoute => !reRoute.UseServiceDiscovery, () => { diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 62adf1fb..90907465 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -45,13 +45,10 @@ namespace Ocelot.DependencyInjection { Configuration = configurationRoot; Services = services; - Services.Configure(configurationRoot); - - //default no caches... - Services.TryAddSingleton, NoCache>(); - Services.TryAddSingleton, NoCache>(); + Services.TryAddSingleton, InMemoryCache>(); + Services.TryAddSingleton, InMemoryCache>(); Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); @@ -105,6 +102,18 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); Services.AddMemoryCache(); Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.AddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); //add asp.net services.. var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly; @@ -118,19 +127,6 @@ namespace Ocelot.DependencyInjection Services.AddLogging(); Services.AddMiddlewareAnalysis(); Services.AddWebEncoders(); - - Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.AddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); } public IOcelotBuilder AddSingletonDefinedAggregator() diff --git a/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs index 89de2840..45858787 100644 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs +++ b/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs @@ -4,6 +4,7 @@ namespace Ocelot.Requester using System.Collections.Generic; using System.Linq; using System.Net.Http; + using Logging; using Microsoft.Extensions.DependencyInjection; using Ocelot.Configuration; using Ocelot.Responses; @@ -14,18 +15,21 @@ namespace Ocelot.Requester private readonly ITracingHandlerFactory _tracingFactory; private readonly IQoSFactory _qoSFactory; private readonly IServiceProvider _serviceProvider; + private readonly IOcelotLogger _logger; public DelegatingHandlerHandlerFactory( ITracingHandlerFactory tracingFactory, IQoSFactory qoSFactory, - IServiceProvider serviceProvider) + IServiceProvider serviceProvider, + IOcelotLoggerFactory loggerFactory) { + _logger = loggerFactory.CreateLogger(); _serviceProvider = serviceProvider; _tracingFactory = tracingFactory; _qoSFactory = qoSFactory; } - public Response>> Get(DownstreamReRoute request) + public Response>> Get(DownstreamReRoute downstreamReRoute) { var globalDelegatingHandlers = _serviceProvider .GetServices() @@ -39,7 +43,7 @@ namespace Ocelot.Requester foreach (var handler in globalDelegatingHandlers) { - if (GlobalIsInHandlersConfig(request, handler)) + if (GlobalIsInHandlersConfig(downstreamReRoute, handler)) { reRouteSpecificHandlers.Add(handler.DelegatingHandler); } @@ -49,9 +53,9 @@ namespace Ocelot.Requester } } - if (request.DelegatingHandlers.Any()) + if (downstreamReRoute.DelegatingHandlers.Any()) { - var sorted = SortByConfigOrder(request, reRouteSpecificHandlers); + var sorted = SortByConfigOrder(downstreamReRoute, reRouteSpecificHandlers); foreach (var handler in sorted) { @@ -59,14 +63,14 @@ namespace Ocelot.Requester } } - if (request.HttpHandlerOptions.UseTracing) + if (downstreamReRoute.HttpHandlerOptions.UseTracing) { handlers.Add(() => (DelegatingHandler)_tracingFactory.Get()); } - if (request.QosOptions.UseQos) + if (downstreamReRoute.QosOptions.UseQos) { - var handler = _qoSFactory.Get(request); + var handler = _qoSFactory.Get(downstreamReRoute); if (handler != null && !handler.IsError) { @@ -74,7 +78,8 @@ namespace Ocelot.Requester } else { - return new ErrorResponse>>(handler?.Errors); + _logger.LogWarning($"ReRoute {downstreamReRoute.UpstreamPathTemplate} specifies use QoS but no QosHandler found in DI container. Will use not use a QosHandler, please check your setup!"); + handlers.Add(() => new NoQosDelegatingHandler()); } } diff --git a/src/Ocelot/Requester/IDelegatingHandlerHandlerFactory.cs b/src/Ocelot/Requester/IDelegatingHandlerHandlerFactory.cs index 53b6a73c..4d905c35 100644 --- a/src/Ocelot/Requester/IDelegatingHandlerHandlerFactory.cs +++ b/src/Ocelot/Requester/IDelegatingHandlerHandlerFactory.cs @@ -1,13 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using Ocelot.Configuration; -using Ocelot.Responses; - namespace Ocelot.Requester { + using System; + using System.Collections.Generic; + using System.Net.Http; + using Ocelot.Configuration; + using Ocelot.Responses; + public interface IDelegatingHandlerHandlerFactory { - Response>> Get(DownstreamReRoute request); + Response>> Get(DownstreamReRoute downstreamReRoute); } } diff --git a/src/Ocelot/Requester/NoQosDelegatingHandler.cs b/src/Ocelot/Requester/NoQosDelegatingHandler.cs new file mode 100644 index 00000000..995b50ed --- /dev/null +++ b/src/Ocelot/Requester/NoQosDelegatingHandler.cs @@ -0,0 +1,8 @@ +namespace Ocelot.Requester +{ + using System.Net.Http; + + public class NoQosDelegatingHandler : DelegatingHandler + { + } +} diff --git a/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs b/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs index e555ade7..5ecedd1a 100644 --- a/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs +++ b/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs @@ -1,11 +1,11 @@ -using System; -using System.Collections.Generic; -using Ocelot.Configuration.File; -using Shouldly; -using Xunit; - namespace Ocelot.AcceptanceTests { + using System; + using System.Collections.Generic; + using Ocelot.Configuration.File; + using Shouldly; + using Xunit; + public class CannotStartOcelotTests : IDisposable { private readonly Steps _steps; @@ -42,6 +42,7 @@ namespace Ocelot.AcceptanceTests } exception.ShouldNotBeNull(); + exception.Message.ShouldBe("One or more errors occurred. (Unable to start Ocelot, errors are: Downstream Path Template test doesnt start with forward slash,Upstream Path Template api doesnt start with forward slash,When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!)"); } public void Dispose() diff --git a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs index 26ccd1cd..7615e0b4 100644 --- a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs +++ b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Net; + using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; using TestStack.BDDfy; using Xunit; diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 5fa7bcff..23cb19dd 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -523,13 +523,7 @@ namespace Ocelot.AcceptanceTests } }, UpstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - QoSOptions = new FileQoSOptions() - { - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak = 5, - TimeoutValue = 5000 - } + UpstreamHttpMethod = new List { "Get" } } } }; diff --git a/test/Ocelot.UnitTests/Cache/InMemoryCacheTests.cs b/test/Ocelot.UnitTests/Cache/InMemoryCacheTests.cs new file mode 100644 index 00000000..8b2e58b9 --- /dev/null +++ b/test/Ocelot.UnitTests/Cache/InMemoryCacheTests.cs @@ -0,0 +1,69 @@ +namespace Ocelot.UnitTests.Cache +{ + using System; + using System.Threading; + using Ocelot.Cache; + using Shouldly; + using Xunit; + + public class InMemoryCacheTests + { + private readonly InMemoryCache _cache; + + public InMemoryCacheTests() + { + _cache = new InMemoryCache(); + } + + [Fact] + public void should_cache() + { + var fake = new Fake(1); + _cache.Add("1", fake, TimeSpan.FromSeconds(100), "region"); + var result = _cache.Get("1", "region"); + result.ShouldBe(fake); + fake.Value.ShouldBe(1); + } + + [Fact] + public void should_clear_region() + { + var fake = new Fake(1); + _cache.Add("1", fake, TimeSpan.FromSeconds(100), "region"); + _cache.ClearRegion("region"); + var result = _cache.Get("1", "region"); + result.ShouldBeNull(); + } + + [Fact] + public void should_clear_key_if_ttl_expired() + { + var fake = new Fake(1); + _cache.Add("1", fake, TimeSpan.FromMilliseconds(50), "region"); + Thread.Sleep(200); + var result = _cache.Get("1", "region"); + result.ShouldBeNull(); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + public void should_not_add_to_cache_if_timespan_empty(int ttl) + { + var fake = new Fake(1); + _cache.Add("1", fake, TimeSpan.FromSeconds(ttl), "region"); + var result = _cache.Get("1", "region"); + result.ShouldBeNull(); + } + + class Fake + { + public Fake(int value) + { + Value = value; + } + + public int Value { get; } + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs b/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs index d33bb6f4..da8d0e75 100644 --- a/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs @@ -1,23 +1,26 @@ -using System.Collections.Generic; -using System.Security.Claims; -using System.Text.Encodings.Web; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Moq; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Validator; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration +namespace Ocelot.UnitTests.Configuration { + using System.Collections.Generic; + using System.Security.Claims; + using System.Text.Encodings.Web; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Authentication; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + using Moq; + using Ocelot.Configuration.File; + using Ocelot.Configuration.Validator; + using Ocelot.Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.Requester; + using Requester; + public class ConfigurationFluentValidationTests { - private readonly IConfigurationValidator _configurationValidator; + private IConfigurationValidator _configurationValidator; private FileConfiguration _fileConfiguration; private Response _result; private readonly Mock _provider; @@ -25,8 +28,10 @@ namespace Ocelot.UnitTests.Configuration public ConfigurationFluentValidationTests() { _provider = new Mock(); - _configurationValidator = new FileConfigurationFluentValidator(_provider.Object); - } + var provider = new ServiceCollection() + .BuildServiceProvider(); + _configurationValidator = new FileConfigurationFluentValidator(_provider.Object, provider); + } [Fact] public void configuration_is_valid_if_aggregates_are_valid() @@ -1046,6 +1051,15 @@ namespace Ocelot.UnitTests.Configuration }); } + private void GivenAQosDelegate() + { + var services = new ServiceCollection(); + QosDelegatingHandlerDelegate del = (a, b) => new FakeDelegatingHandler(); + services.AddSingleton(del); + var provider = services.BuildServiceProvider(); + _configurationValidator = new FileConfigurationFluentValidator(_provider.Object, provider); + } + private class TestOptions : AuthenticationSchemeOptions { } diff --git a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs index 679b051c..f63c2f34 100644 --- a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs @@ -22,7 +22,8 @@ namespace Ocelot.UnitTests.Requester { private DelegatingHandlerHandlerFactory _factory; private readonly Mock _loggerFactory; - private DownstreamReRoute _request; + private readonly Mock _logger; + private DownstreamReRoute _downstreamReRoute; private Response>> _result; private readonly Mock _qosFactory; private readonly Mock _tracingFactory; @@ -36,6 +37,8 @@ namespace Ocelot.UnitTests.Requester _tracingFactory = new Mock(); _qosFactory = new Mock(); _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _services = new ServiceCollection(); _services.AddSingleton(_qosDelegate); } @@ -300,7 +303,7 @@ namespace Ocelot.UnitTests.Requester } [Fact] - public void should_return_error() + public void should_log_error_and_return_no_qos_provider_delegate_when_qos_factory_returns_error() { var qosOptions = new QoSOptionsBuilder() .WithTimeoutValue(1) @@ -310,16 +313,60 @@ namespace Ocelot.UnitTests.Requester var reRoute = new DownstreamReRouteBuilder() .WithQosOptions(qosOptions) - .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false, true)).WithLoadBalancerKey("").Build(); + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true, true)) + .WithLoadBalancerKey("") + .Build(); this.Given(x => GivenTheFollowingRequest(reRoute)) .And(x => GivenTheQosFactoryReturnsError()) - .And(x => GivenTheServiceProviderReturnsNothing()) + .And(x => GivenTheTracingFactoryReturns()) + .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) + .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) .When(x => WhenIGet()) - .Then(x => ThenAnErrorIsReturned()) + .Then(x => ThenThereIsDelegatesInProvider(4)) + .And(x => ThenHandlerAtPositionIs(0)) + .And(x => ThenHandlerAtPositionIs(1)) + .And(x => ThenHandlerAtPositionIs(2)) + .And(x => ThenHandlerAtPositionIs(3)) + .And(_ => ThenTheWarningIsLogged()) .BDDfy(); } + [Fact] + public void should_log_error_and_return_no_qos_provider_delegate_when_qos_factory_returns_null() + { + var qosOptions = new QoSOptionsBuilder() + .WithTimeoutValue(1) + .WithDurationOfBreak(1) + .WithExceptionsAllowedBeforeBreaking(1) + .Build(); + + var reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true, true)) + .WithLoadBalancerKey("") + .Build(); + + this.Given(x => GivenTheFollowingRequest(reRoute)) + .And(x => GivenTheQosFactoryReturnsNull()) + .And(x => GivenTheTracingFactoryReturns()) + .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)) + .And(_ => ThenTheWarningIsLogged()) + .BDDfy(); + } + + private void ThenTheWarningIsLogged() + { + _logger.Verify(x => x.LogWarning($"ReRoute {_downstreamReRoute.UpstreamPathTemplate} specifies use QoS but no QosHandler found in DI container. Will use not use a QosHandler, please check your setup!"), Times.Once); + } + private void ThenHandlerAtPositionIs(int pos) where T : DelegatingHandler { @@ -396,6 +443,13 @@ namespace Ocelot.UnitTests.Requester .Returns(new ErrorResponse(new AnyError())); } + private void GivenTheQosFactoryReturnsNull() + { + _qosFactory + .Setup(x => x.Get(It.IsAny())) + .Returns((ErrorResponse)null); + } + private void ThenItIsQosHandler(int i) { var delegates = _result.Data; @@ -411,14 +465,14 @@ namespace Ocelot.UnitTests.Requester private void GivenTheFollowingRequest(DownstreamReRoute request) { - _request = request; + _downstreamReRoute = request; } private void WhenIGet() { _serviceProvider = _services.BuildServiceProvider(); - _factory = new DelegatingHandlerHandlerFactory(_tracingFactory.Object, _qosFactory.Object, _serviceProvider); - _result = _factory.Get(_request); + _factory = new DelegatingHandlerHandlerFactory(_tracingFactory.Object, _qosFactory.Object, _serviceProvider, _loggerFactory.Object); + _result = _factory.Get(_downstreamReRoute); } private void ThenNoDelegatesAreInTheProvider() diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index 429c5a90..1b0d17eb 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -89,22 +89,6 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } - private void GivenARealCache() - { - _realCache = new MemoryHttpClientCache(); - _builder = new HttpClientBuilder(_factory.Object, _realCache, _logger.Object); - } - - private void ThenTheHttpClientIsFromTheCache() - { - _againHttpClient.ShouldBe(_firstHttpClient); - } - - private void WhenISave() - { - _builder.Save(); - } - [Fact] public void should_log_if_ignoring_ssl_errors() { @@ -214,9 +198,25 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } + private void GivenARealCache() + { + _realCache = new MemoryHttpClientCache(); + _builder = new HttpClientBuilder(_factory.Object, _realCache, _logger.Object); + } + + private void ThenTheHttpClientIsFromTheCache() + { + _againHttpClient.ShouldBe(_firstHttpClient); + } + + private void WhenISave() + { + _builder.Save(); + } + private void GivenCacheIsCalledWithExpectedKey(string expectedKey) { - this._cacheHandlers.Verify(x => x.Get(It.Is(p => p.Equals(expectedKey, StringComparison.OrdinalIgnoreCase))), Times.Once); + _cacheHandlers.Verify(x => x.Get(It.Is(p => p.Equals(expectedKey, StringComparison.OrdinalIgnoreCase))), Times.Once); } private void ThenTheDangerousAcceptAnyServerCertificateValidatorWarningIsLogged() From 29a7af9486fb2fd94313a58e81d5704159183b05 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Tue, 28 Aug 2018 18:57:21 +0100 Subject: [PATCH 44/50] =?UTF-8?q?#568=20worked=20out=20how=20to=20check=20?= =?UTF-8?q?if=20qos=20handler=20present=20and=20kill=20Ocelot=20i=E2=80=A6?= =?UTF-8?q?=20(#578)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * #568 worked out how to check if qos handler present and kill Ocelot if options specified but no handler, need to refactor this into fluent validation style * #568 acceptance tests to make sure Ocelot won't start if the user specifies QoSOptions but doesnt have a QoSHandler registered --- .../ConfigurationValidationResult.cs | 54 +- .../FileConfigurationFluentValidator.cs | 262 +- .../FileGlobalConfigurationFluentValidator.cs | 21 + .../FileQoSOptionsFluentValidator.cs | 28 + .../Validator/ReRouteFluentValidator.cs | 187 +- .../CannotStartOcelotTests.cs | 201 +- ... FileConfigurationFluentValidatorTests.cs} | 2318 +++++++++-------- 7 files changed, 1694 insertions(+), 1377 deletions(-) create mode 100644 src/Ocelot/Configuration/Validator/FileGlobalConfigurationFluentValidator.cs create mode 100644 src/Ocelot/Configuration/Validator/FileQoSOptionsFluentValidator.cs rename test/Ocelot.UnitTests/Configuration/{ConfigurationFluentValidationTests.cs => FileConfigurationFluentValidatorTests.cs} (85%) diff --git a/src/Ocelot/Configuration/Validator/ConfigurationValidationResult.cs b/src/Ocelot/Configuration/Validator/ConfigurationValidationResult.cs index 7e06cb6b..aa21cd26 100644 --- a/src/Ocelot/Configuration/Validator/ConfigurationValidationResult.cs +++ b/src/Ocelot/Configuration/Validator/ConfigurationValidationResult.cs @@ -1,24 +1,30 @@ -using System.Collections.Generic; -using Ocelot.Errors; - -namespace Ocelot.Configuration.Validator -{ - public class ConfigurationValidationResult - { - public ConfigurationValidationResult(bool isError) - { - IsError = isError; - Errors = new List(); - } - - public ConfigurationValidationResult(bool isError, List errors) - { - IsError = isError; - Errors = errors; - } - - public bool IsError { get; } - - public List Errors { get; } - } -} +using System.Collections.Generic; +using Ocelot.Errors; + +namespace Ocelot.Configuration.Validator +{ + public class ConfigurationValidationResult + { + public ConfigurationValidationResult(bool isError) + { + IsError = isError; + Errors = new List(); + } + + public ConfigurationValidationResult(bool isError, Error error) + { + IsError = isError; + Errors = new List { error }; + } + + public ConfigurationValidationResult(bool isError, List errors) + { + IsError = isError; + Errors = errors; + } + + public bool IsError { get; } + + public List Errors { get; } + } +} diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs index ebac0a30..5e6ac61b 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs @@ -1,129 +1,133 @@ -using FluentValidation; -using Microsoft.AspNetCore.Authentication; -using Ocelot.Configuration.File; -using Ocelot.Errors; -using Ocelot.Responses; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.Configuration.Validator -{ - using System; - using Microsoft.Extensions.DependencyInjection; - using Requester; - - public class FileConfigurationFluentValidator : AbstractValidator, IConfigurationValidator - { - private readonly IServiceProvider _provider; - - public FileConfigurationFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, IServiceProvider provider) - { - _provider = provider; - RuleFor(configuration => configuration.ReRoutes) - .SetCollectionValidator(new ReRouteFluentValidator(authenticationSchemeProvider, provider)); - - RuleForEach(configuration => configuration.ReRoutes) - .Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.ReRoutes)) - .WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate"); - - RuleForEach(configuration => configuration.ReRoutes) - .Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.Aggregates)) - .WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate aggregate"); - - RuleForEach(configuration => configuration.Aggregates) - .Must((config, aggregateReRoute) => IsNotDuplicateIn(aggregateReRoute, config.Aggregates)) - .WithMessage((config, aggregate) => $"{nameof(aggregate)} {aggregate.UpstreamPathTemplate} has duplicate aggregate"); - - RuleForEach(configuration => configuration.Aggregates) - .Must((config, aggregateReRoute) => AllReRoutesForAggregateExist(aggregateReRoute, config.ReRoutes)) - .WithMessage((config, aggregateReRoute) => $"ReRoutes for {nameof(aggregateReRoute)} {aggregateReRoute.UpstreamPathTemplate} either do not exist or do not have correct ServiceName property"); - - RuleForEach(configuration => configuration.Aggregates) - .Must((config, aggregateReRoute) => DoesNotContainReRoutesWithSpecificRequestIdKeys(aggregateReRoute, config.ReRoutes)) - .WithMessage((config, aggregateReRoute) => $"{nameof(aggregateReRoute)} {aggregateReRoute.UpstreamPathTemplate} contains ReRoute with specific RequestIdKey, this is not possible with Aggregates"); - } - - private bool AllReRoutesForAggregateExist(FileAggregateReRoute fileAggregateReRoute, List reRoutes) - { - var reRoutesForAggregate = reRoutes.Where(r => fileAggregateReRoute.ReRouteKeys.Contains(r.Key)); - - return reRoutesForAggregate.Count() == fileAggregateReRoute.ReRouteKeys.Count; - } - - public async Task> IsValid(FileConfiguration configuration) - { - var validateResult = await ValidateAsync(configuration); - - if (validateResult.IsValid) - { - return new OkResponse(new ConfigurationValidationResult(false)); - } - - var errors = validateResult.Errors.Select(failure => new FileValidationFailedError(failure.ErrorMessage)); - - var result = new ConfigurationValidationResult(true, errors.Cast().ToList()); - - return new OkResponse(result); - } - - private static bool DoesNotContainReRoutesWithSpecificRequestIdKeys(FileAggregateReRoute fileAggregateReRoute, - List reRoutes) - { - var reRoutesForAggregate = reRoutes.Where(r => fileAggregateReRoute.ReRouteKeys.Contains(r.Key)); - - return reRoutesForAggregate.All(r => string.IsNullOrEmpty(r.RequestIdKey)); - } - - private static bool IsNotDuplicateIn(FileReRoute reRoute, - List reRoutes) - { - var matchingReRoutes = reRoutes - .Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate - && (r.UpstreamHost != reRoute.UpstreamHost || reRoute.UpstreamHost == null)) - .ToList(); - - if(matchingReRoutes.Count == 1) - { - return true; - } - - var allowAllVerbs = matchingReRoutes.Any(x => x.UpstreamHttpMethod.Count == 0); - - var duplicateAllowAllVerbs = matchingReRoutes.Count(x => x.UpstreamHttpMethod.Count == 0) > 1; - - var specificVerbs = matchingReRoutes.Any(x => x.UpstreamHttpMethod.Count != 0); - - var duplicateSpecificVerbs = matchingReRoutes.SelectMany(x => x.UpstreamHttpMethod).GroupBy(x => x.ToLower()).SelectMany(x => x.Skip(1)).Any(); - - if (duplicateAllowAllVerbs || duplicateSpecificVerbs || (allowAllVerbs && specificVerbs)) - { - return false; - } - - return true; - } - - private static bool IsNotDuplicateIn(FileReRoute reRoute, - List aggregateReRoutes) - { - var duplicate = aggregateReRoutes - .Any(a => a.UpstreamPathTemplate == reRoute.UpstreamPathTemplate - && a.UpstreamHost == reRoute.UpstreamHost - && reRoute.UpstreamHttpMethod.Select(x => x.ToLower()).Contains("get")); - - return !duplicate; - } - - private static bool IsNotDuplicateIn(FileAggregateReRoute reRoute, - List aggregateReRoutes) - { - var matchingReRoutes = aggregateReRoutes - .Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate - && r.UpstreamHost == reRoute.UpstreamHost) - .ToList(); - - return matchingReRoutes.Count <= 1; - } - } -} +using FluentValidation; +using Microsoft.AspNetCore.Authentication; +using Ocelot.Configuration.File; +using Ocelot.Errors; +using Ocelot.Responses; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Configuration.Validator +{ + using System; + using Microsoft.Extensions.DependencyInjection; + using Requester; + + public class FileConfigurationFluentValidator : AbstractValidator, IConfigurationValidator + { + private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate; + + public FileConfigurationFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, IServiceProvider provider) + { + _qosDelegatingHandlerDelegate = provider.GetService(); + + RuleFor(configuration => configuration.ReRoutes) + .SetCollectionValidator(new ReRouteFluentValidator(authenticationSchemeProvider, _qosDelegatingHandlerDelegate)); + + RuleFor(configuration => configuration.GlobalConfiguration) + .SetValidator(new FileGlobalConfigurationFluentValidator(_qosDelegatingHandlerDelegate)); + + RuleForEach(configuration => configuration.ReRoutes) + .Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.ReRoutes)) + .WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate"); + + RuleForEach(configuration => configuration.ReRoutes) + .Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.Aggregates)) + .WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate aggregate"); + + RuleForEach(configuration => configuration.Aggregates) + .Must((config, aggregateReRoute) => IsNotDuplicateIn(aggregateReRoute, config.Aggregates)) + .WithMessage((config, aggregate) => $"{nameof(aggregate)} {aggregate.UpstreamPathTemplate} has duplicate aggregate"); + + RuleForEach(configuration => configuration.Aggregates) + .Must((config, aggregateReRoute) => AllReRoutesForAggregateExist(aggregateReRoute, config.ReRoutes)) + .WithMessage((config, aggregateReRoute) => $"ReRoutes for {nameof(aggregateReRoute)} {aggregateReRoute.UpstreamPathTemplate} either do not exist or do not have correct ServiceName property"); + + RuleForEach(configuration => configuration.Aggregates) + .Must((config, aggregateReRoute) => DoesNotContainReRoutesWithSpecificRequestIdKeys(aggregateReRoute, config.ReRoutes)) + .WithMessage((config, aggregateReRoute) => $"{nameof(aggregateReRoute)} {aggregateReRoute.UpstreamPathTemplate} contains ReRoute with specific RequestIdKey, this is not possible with Aggregates"); + } + + public async Task> IsValid(FileConfiguration configuration) + { + var validateResult = await ValidateAsync(configuration); + + if (validateResult.IsValid) + { + return new OkResponse(new ConfigurationValidationResult(false)); + } + + var errors = validateResult.Errors.Select(failure => new FileValidationFailedError(failure.ErrorMessage)); + + var result = new ConfigurationValidationResult(true, errors.Cast().ToList()); + + return new OkResponse(result); + } + + private bool AllReRoutesForAggregateExist(FileAggregateReRoute fileAggregateReRoute, List reRoutes) + { + var reRoutesForAggregate = reRoutes.Where(r => fileAggregateReRoute.ReRouteKeys.Contains(r.Key)); + + return reRoutesForAggregate.Count() == fileAggregateReRoute.ReRouteKeys.Count; + } + + private static bool DoesNotContainReRoutesWithSpecificRequestIdKeys(FileAggregateReRoute fileAggregateReRoute, + List reRoutes) + { + var reRoutesForAggregate = reRoutes.Where(r => fileAggregateReRoute.ReRouteKeys.Contains(r.Key)); + + return reRoutesForAggregate.All(r => string.IsNullOrEmpty(r.RequestIdKey)); + } + + private static bool IsNotDuplicateIn(FileReRoute reRoute, + List reRoutes) + { + var matchingReRoutes = reRoutes + .Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate + && (r.UpstreamHost != reRoute.UpstreamHost || reRoute.UpstreamHost == null)) + .ToList(); + + if (matchingReRoutes.Count == 1) + { + return true; + } + + var allowAllVerbs = matchingReRoutes.Any(x => x.UpstreamHttpMethod.Count == 0); + + var duplicateAllowAllVerbs = matchingReRoutes.Count(x => x.UpstreamHttpMethod.Count == 0) > 1; + + var specificVerbs = matchingReRoutes.Any(x => x.UpstreamHttpMethod.Count != 0); + + var duplicateSpecificVerbs = matchingReRoutes.SelectMany(x => x.UpstreamHttpMethod).GroupBy(x => x.ToLower()).SelectMany(x => x.Skip(1)).Any(); + + if (duplicateAllowAllVerbs || duplicateSpecificVerbs || (allowAllVerbs && specificVerbs)) + { + return false; + } + + return true; + } + + private static bool IsNotDuplicateIn(FileReRoute reRoute, + List aggregateReRoutes) + { + var duplicate = aggregateReRoutes + .Any(a => a.UpstreamPathTemplate == reRoute.UpstreamPathTemplate + && a.UpstreamHost == reRoute.UpstreamHost + && reRoute.UpstreamHttpMethod.Select(x => x.ToLower()).Contains("get")); + + return !duplicate; + } + + private static bool IsNotDuplicateIn(FileAggregateReRoute reRoute, + List aggregateReRoutes) + { + var matchingReRoutes = aggregateReRoutes + .Where(r => r.UpstreamPathTemplate == reRoute.UpstreamPathTemplate + && r.UpstreamHost == reRoute.UpstreamHost) + .ToList(); + + return matchingReRoutes.Count <= 1; + } + } +} diff --git a/src/Ocelot/Configuration/Validator/FileGlobalConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileGlobalConfigurationFluentValidator.cs new file mode 100644 index 00000000..7bac74f7 --- /dev/null +++ b/src/Ocelot/Configuration/Validator/FileGlobalConfigurationFluentValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Validator +{ + using Requester; + + public class FileGlobalConfigurationFluentValidator : AbstractValidator + { + private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate; + + public FileGlobalConfigurationFluentValidator(QosDelegatingHandlerDelegate qosDelegatingHandlerDelegate) + { + _qosDelegatingHandlerDelegate = qosDelegatingHandlerDelegate; + + RuleFor(configuration => configuration.QoSOptions) + .SetValidator(new FileQoSOptionsFluentValidator(_qosDelegatingHandlerDelegate)); + } + + } +} diff --git a/src/Ocelot/Configuration/Validator/FileQoSOptionsFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileQoSOptionsFluentValidator.cs new file mode 100644 index 00000000..42fc9120 --- /dev/null +++ b/src/Ocelot/Configuration/Validator/FileQoSOptionsFluentValidator.cs @@ -0,0 +1,28 @@ +using FluentValidation; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Validator +{ + using Requester; + + public class FileQoSOptionsFluentValidator : AbstractValidator + { + private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate; + + public FileQoSOptionsFluentValidator(QosDelegatingHandlerDelegate qosDelegatingHandlerDelegate) + { + _qosDelegatingHandlerDelegate = qosDelegatingHandlerDelegate; + + When(qosOptions => qosOptions.TimeoutValue > 0 && qosOptions.ExceptionsAllowedBeforeBreaking > 0, () => { + RuleFor(qosOptions => qosOptions) + .Must(HaveQosHandlerRegistered) + .WithMessage("Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using QoSOptions but no QosDelegatingHandlerDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Polly and services.AddPolly()?"); + }); + } + + private bool HaveQosHandlerRegistered(FileQoSOptions arg) + { + return _qosDelegatingHandlerDelegate != null; + } + } +} diff --git a/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs b/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs index c54fc2aa..74b0934e 100644 --- a/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs @@ -1,92 +1,95 @@ -namespace Ocelot.Configuration.Validator -{ - using FluentValidation; - using Microsoft.AspNetCore.Authentication; - using Ocelot.Configuration.File; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using System; - using Microsoft.Extensions.DependencyInjection; - using Requester; - - public class ReRouteFluentValidator : AbstractValidator - { - private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider; - private readonly IServiceProvider _serviceProvider; - - public ReRouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, IServiceProvider serviceProvider) - { - _authenticationSchemeProvider = authenticationSchemeProvider; - _serviceProvider = serviceProvider; - - RuleFor(reRoute => reRoute.DownstreamPathTemplate) - .Must(path => path.StartsWith("/")) - .WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash"); - - RuleFor(reRoute => reRoute.UpstreamPathTemplate) - .Must(path => !path.Contains("//")) - .WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature."); - - RuleFor(reRoute => reRoute.DownstreamPathTemplate) - .Must(path => !path.Contains("//")) - .WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature."); - - RuleFor(reRoute => reRoute.UpstreamPathTemplate) - .Must(path => path.StartsWith("/")) - .WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash"); - - RuleFor(reRoute => reRoute.DownstreamPathTemplate) - .Must(path => !path.Contains("https://") && !path.Contains("http://")) - .WithMessage("{PropertyName} {PropertyValue} contains scheme"); - - RuleFor(reRoute => reRoute.UpstreamPathTemplate) - .Must(path => !path.Contains("https://") && !path.Contains("http://")) - .WithMessage("{PropertyName} {PropertyValue} contains scheme"); - - RuleFor(reRoute => reRoute.RateLimitOptions) - .Must(IsValidPeriod) - .WithMessage("RateLimitOptions.Period does not contains (s,m,h,d)"); - - RuleFor(reRoute => reRoute.AuthenticationOptions) - .MustAsync(IsSupportedAuthenticationProviders) - .WithMessage("{PropertyValue} is unsupported authentication provider"); - - When(reRoute => reRoute.UseServiceDiscovery, () => { - RuleFor(r => r.ServiceName).NotEmpty() - .WithMessage("ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!"); - }); - - When(reRoute => !reRoute.UseServiceDiscovery, () => { - RuleFor(r => r.DownstreamHostAndPorts).NotEmpty() - .WithMessage("When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!"); - }); - - When(reRoute => !reRoute.UseServiceDiscovery, () => { - RuleFor(reRoute => reRoute.DownstreamHostAndPorts) - .SetCollectionValidator(new HostAndPortValidator()); - }); - } - - private async Task IsSupportedAuthenticationProviders(FileAuthenticationOptions authenticationOptions, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(authenticationOptions.AuthenticationProviderKey)) - { - return true; - } - - var schemes = await _authenticationSchemeProvider.GetAllSchemesAsync(); - - var supportedSchemes = schemes.Select(scheme => scheme.Name).ToList(); - - return supportedSchemes.Contains(authenticationOptions.AuthenticationProviderKey); - } - - private static bool IsValidPeriod(FileRateLimitRule rateLimitOptions) - { - string period = rateLimitOptions.Period; - - return !rateLimitOptions.EnableRateLimiting || period.Contains("s") || period.Contains("m") || period.Contains("h") || period.Contains("d"); - } - } -} +namespace Ocelot.Configuration.Validator +{ + using FluentValidation; + using Microsoft.AspNetCore.Authentication; + using Ocelot.Configuration.File; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using System; + using Microsoft.Extensions.DependencyInjection; + using Requester; + + public class ReRouteFluentValidator : AbstractValidator + { + private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider; + private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate; + + public ReRouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, QosDelegatingHandlerDelegate qosDelegatingHandlerDelegate) + { + _authenticationSchemeProvider = authenticationSchemeProvider; + _qosDelegatingHandlerDelegate = qosDelegatingHandlerDelegate; + + RuleFor(reRoute => reRoute.QoSOptions) + .SetValidator(new FileQoSOptionsFluentValidator(_qosDelegatingHandlerDelegate)); + + RuleFor(reRoute => reRoute.DownstreamPathTemplate) + .Must(path => path.StartsWith("/")) + .WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash"); + + RuleFor(reRoute => reRoute.UpstreamPathTemplate) + .Must(path => !path.Contains("//")) + .WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature."); + + RuleFor(reRoute => reRoute.DownstreamPathTemplate) + .Must(path => !path.Contains("//")) + .WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature."); + + RuleFor(reRoute => reRoute.UpstreamPathTemplate) + .Must(path => path.StartsWith("/")) + .WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash"); + + RuleFor(reRoute => reRoute.DownstreamPathTemplate) + .Must(path => !path.Contains("https://") && !path.Contains("http://")) + .WithMessage("{PropertyName} {PropertyValue} contains scheme"); + + RuleFor(reRoute => reRoute.UpstreamPathTemplate) + .Must(path => !path.Contains("https://") && !path.Contains("http://")) + .WithMessage("{PropertyName} {PropertyValue} contains scheme"); + + RuleFor(reRoute => reRoute.RateLimitOptions) + .Must(IsValidPeriod) + .WithMessage("RateLimitOptions.Period does not contains (s,m,h,d)"); + + RuleFor(reRoute => reRoute.AuthenticationOptions) + .MustAsync(IsSupportedAuthenticationProviders) + .WithMessage("{PropertyValue} is unsupported authentication provider"); + + When(reRoute => reRoute.UseServiceDiscovery, () => { + RuleFor(r => r.ServiceName).NotEmpty() + .WithMessage("ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!"); + }); + + When(reRoute => !reRoute.UseServiceDiscovery, () => { + RuleFor(r => r.DownstreamHostAndPorts).NotEmpty() + .WithMessage("When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!"); + }); + + When(reRoute => !reRoute.UseServiceDiscovery, () => { + RuleFor(reRoute => reRoute.DownstreamHostAndPorts) + .SetCollectionValidator(new HostAndPortValidator()); + }); + } + + private async Task IsSupportedAuthenticationProviders(FileAuthenticationOptions authenticationOptions, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(authenticationOptions.AuthenticationProviderKey)) + { + return true; + } + + var schemes = await _authenticationSchemeProvider.GetAllSchemesAsync(); + + var supportedSchemes = schemes.Select(scheme => scheme.Name).ToList(); + + return supportedSchemes.Contains(authenticationOptions.AuthenticationProviderKey); + } + + private static bool IsValidPeriod(FileRateLimitRule rateLimitOptions) + { + string period = rateLimitOptions.Period; + + return !rateLimitOptions.EnableRateLimiting || period.Contains("s") || period.Contains("m") || period.Contains("h") || period.Contains("d"); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs b/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs index 5ecedd1a..35029a13 100644 --- a/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs +++ b/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs @@ -1,53 +1,148 @@ -namespace Ocelot.AcceptanceTests -{ - using System; - using System.Collections.Generic; - using Ocelot.Configuration.File; - using Shouldly; - using Xunit; - - public class CannotStartOcelotTests : IDisposable - { - private readonly Steps _steps; - - public CannotStartOcelotTests() - { - _steps = new Steps(); - } - - [Fact] - public void should_throw_exception_if_cannot_start() - { - var invalidConfig = new FileConfiguration() - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "api", - DownstreamPathTemplate = "test" - } - } - }; - - Exception exception = null; - _steps.GivenThereIsAConfiguration(invalidConfig); - try - { - _steps.GivenOcelotIsRunning(); - } - catch(Exception ex) - { - exception = ex; - } - - exception.ShouldNotBeNull(); - exception.Message.ShouldBe("One or more errors occurred. (Unable to start Ocelot, errors are: Downstream Path Template test doesnt start with forward slash,Upstream Path Template api doesnt start with forward slash,When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!)"); - } - - public void Dispose() - { - _steps.Dispose(); - } - } -} +namespace Ocelot.AcceptanceTests +{ + using System; + using System.Collections.Generic; + using Ocelot.Configuration.File; + using Shouldly; + using Xunit; + + public class CannotStartOcelotTests : IDisposable + { + private readonly Steps _steps; + + public CannotStartOcelotTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_throw_exception_if_cannot_start_because_no_qos_delegate_registered_globally() + { + var invalidConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + QoSOptions = new FileQoSOptions + { + TimeoutValue = 1, + ExceptionsAllowedBeforeBreaking = 1 + } + } + }; + + Exception exception = null; + _steps.GivenThereIsAConfiguration(invalidConfig); + try + { + _steps.GivenOcelotIsRunning(); + } + catch (Exception ex) + { + exception = ex; + } + + exception.ShouldNotBeNull(); + exception.Message.ShouldBe("One or more errors occurred. (Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using QoSOptions but no QosDelegatingHandlerDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Polly and services.AddPolly()?)"); + } + + [Fact] + public void should_throw_exception_if_cannot_start_because_no_qos_delegate_registered_for_re_route() + { + var invalidConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + QoSOptions = new FileQoSOptions + { + TimeoutValue = 1, + ExceptionsAllowedBeforeBreaking = 1 + } + } + } + }; + + Exception exception = null; + _steps.GivenThereIsAConfiguration(invalidConfig); + try + { + _steps.GivenOcelotIsRunning(); + } + catch (Exception ex) + { + exception = ex; + } + + exception.ShouldNotBeNull(); + exception.Message.ShouldBe("One or more errors occurred. (Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using QoSOptions but no QosDelegatingHandlerDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Polly and services.AddPolly()?)"); + } + + [Fact] + public void should_throw_exception_if_cannot_start() + { + var invalidConfig = new FileConfiguration() + { + ReRoutes = new List + { + new FileReRoute + { + UpstreamPathTemplate = "api", + DownstreamPathTemplate = "test" + } + } + }; + + Exception exception = null; + _steps.GivenThereIsAConfiguration(invalidConfig); + try + { + _steps.GivenOcelotIsRunning(); + } + catch (Exception ex) + { + exception = ex; + } + + exception.ShouldNotBeNull(); + exception.Message.ShouldBe("One or more errors occurred. (Unable to start Ocelot, errors are: Downstream Path Template test doesnt start with forward slash,Upstream Path Template api doesnt start with forward slash,When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!)"); + } + + public void Dispose() + { + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationFluentValidatorTests.cs similarity index 85% rename from test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs rename to test/Ocelot.UnitTests/Configuration/FileConfigurationFluentValidatorTests.cs index da8d0e75..9fec1700 100644 --- a/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationFluentValidatorTests.cs @@ -1,1080 +1,1240 @@ -namespace Ocelot.UnitTests.Configuration -{ - using System.Collections.Generic; - using System.Security.Claims; - using System.Text.Encodings.Web; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Authentication; - using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Options; - using Moq; - using Ocelot.Configuration.File; - using Ocelot.Configuration.Validator; - using Ocelot.Responses; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - using Microsoft.Extensions.DependencyInjection; - using Ocelot.Requester; - using Requester; - - public class ConfigurationFluentValidationTests - { - private IConfigurationValidator _configurationValidator; - private FileConfiguration _fileConfiguration; - private Response _result; - private readonly Mock _provider; - - public ConfigurationFluentValidationTests() - { - _provider = new Mock(); - var provider = new ServiceCollection() - .BuildServiceProvider(); - _configurationValidator = new FileConfigurationFluentValidator(_provider.Object, provider); +namespace Ocelot.UnitTests.Configuration +{ + using System.Collections.Generic; + using System.Security.Claims; + using System.Text.Encodings.Web; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Authentication; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + using Moq; + using Ocelot.Configuration.File; + using Ocelot.Configuration.Validator; + using Ocelot.Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.Requester; + using Requester; + + public class FileConfigurationFluentValidatorTests + { + private IConfigurationValidator _configurationValidator; + private FileConfiguration _fileConfiguration; + private Response _result; + private readonly Mock _authProvider; + + public FileConfigurationFluentValidatorTests() + { + _authProvider = new Mock(); + var provider = new ServiceCollection() + .BuildServiceProvider(); + _configurationValidator = new FileConfigurationFluentValidator(_authProvider.Object, provider); } - - [Fact] - public void configuration_is_valid_if_aggregates_are_valid() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51878, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura" - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51880, - } - }, - UpstreamPathTemplate = "/tom", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Tom", - "Laura" - } - } - } - }; - - this.Given(x => x.GivenAConfiguration(configuration)) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_if_aggregates_are_duplicate_of_re_routes() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51878, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura" - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51880, - } - }, - UpstreamPathTemplate = "/tom", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom", - UpstreamHost = "localhost" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/tom", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Tom", - "Laura" - }, - } - } - }; - - this.Given(x => x.GivenAConfiguration(configuration)) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "reRoute /tom has duplicate aggregate")) - .BDDfy(); - } - - [Fact] - public void configuration_is_valid_if_aggregates_are_not_duplicate_of_re_routes() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51878, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura" - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51880, - } - }, - UpstreamPathTemplate = "/tom", - UpstreamHttpMethod = new List { "Post" }, - Key = "Tom", - UpstreamHost = "localhost" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/tom", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Tom", - "Laura" - }, - } - } - }; - - this.Given(x => x.GivenAConfiguration(configuration)) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_if_aggregates_are_duplicate_of_aggregates() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51878, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura" - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51880, - } - }, - UpstreamPathTemplate = "/lol", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/tom", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Tom", - "Laura" - } - }, - new FileAggregateReRoute - { - UpstreamPathTemplate = "/tom", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Tom", - "Laura" - } - } - } - }; - - this.Given(x => x.GivenAConfiguration(configuration)) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "aggregate /tom has duplicate aggregate")) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_if_re_routes_dont_exist_for_aggregate() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51878, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Tom", - "Laura" - } - } - } - }; - - this.Given(x => x.GivenAConfiguration(configuration)) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "ReRoutes for aggregateReRoute / either do not exist or do not have correct ServiceName property")) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_if_aggregate_has_re_routes_with_specific_request_id_keys() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51878, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura" - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51880, - } - }, - UpstreamPathTemplate = "/tom", - UpstreamHttpMethod = new List { "Get" }, - RequestIdKey = "should_fail", - Key = "Tom" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Tom", - "Laura" - } - } - } - }; - - this.Given(x => x.GivenAConfiguration(configuration)) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "aggregateReRoute / contains ReRoute with specific RequestIdKey, this is not possible with Aggregates")) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_if_scheme_in_downstream_or_upstream_template() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "http://www.bbc.co.uk/api/products/{productId}", - UpstreamPathTemplate = "http://asdf.com" - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .Then(x => x.ThenTheErrorIs()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} doesnt start with forward slash")) - .And(x => x.ThenTheErrorMessageAtPositionIs(1, "Upstream Path Template http://asdf.com contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) - .And(x => x.ThenTheErrorMessageAtPositionIs(2, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) - .And(x => x.ThenTheErrorMessageAtPositionIs(3, "Upstream Path Template http://asdf.com doesnt start with forward slash")) - .And(x => x.ThenTheErrorMessageAtPositionIs(4, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} contains scheme")) - .And(x => x.ThenTheErrorMessageAtPositionIs(5, "Upstream Path Template http://asdf.com contains scheme")) - .BDDfy(); - } - - [Fact] - public void configuration_is_valid_with_one_reroute() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "bbc.co.uk" - } - }, - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_without_slash_prefix_downstream_path_template() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "api/products/", - UpstreamPathTemplate = "/asdf/" - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Downstream Path Template api/products/ doesnt start with forward slash")) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_without_slash_prefix_upstream_path_template() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "api/prod/", - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Upstream Path Template api/prod/ doesnt start with forward slash")) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_if_upstream_url_contains_forward_slash_then_another_forward_slash() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "//api/prod/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "bbc.co.uk", - Port = 80 - } - }, - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Upstream Path Template //api/prod/ contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_if_downstream_url_contains_forward_slash_then_another_forward_slash() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "//api/products/", - UpstreamPathTemplate = "/api/prod/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "bbc.co.uk", - Port = 80 - } - }, - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Downstream Path Template //api/products/ contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) - .BDDfy(); - } - - [Fact] - public void configuration_is_valid_with_valid_authentication_provider() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "bbc.co.uk", - } - }, - AuthenticationOptions = new FileAuthenticationOptions() - { - AuthenticationProviderKey = "Test" - } - } - } - })) - .And(x => x.GivenTheAuthSchemeExists("Test")) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_with_invalid_authentication_provider() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - AuthenticationOptions = new FileAuthenticationOptions() - { - AuthenticationProviderKey = "Test" - } - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "AuthenticationProviderKey:Test,AllowedScopes:[] is unsupported authentication provider")) - .BDDfy(); - } - - [Fact] - public void configuration_is_not_valid_with_duplicate_reroutes_all_verbs() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "bb.co.uk" - } - }, - }, - new FileReRoute - { - DownstreamPathTemplate = "/www/test/", - UpstreamPathTemplate = "/asdf/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "bb.co.uk" - } - }, - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "reRoute /asdf/ has duplicate")) - .BDDfy(); - } - - [Fact] - public void configuration_is_valid_with_duplicate_reroutes_all_verbs_but_different_hosts() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "bb.co.uk" - } - }, - UpstreamHost = "host1" - }, - new FileReRoute - { - DownstreamPathTemplate = "/www/test/", - UpstreamPathTemplate = "/asdf/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "bb.co.uk" - } - }, - UpstreamHost = "host1" - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); - } - - [Fact] - public void configuration_is_not_valid_with_duplicate_reroutes_specific_verbs() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "bbc.co.uk", - } - }, - UpstreamHttpMethod = new List {"Get"} - }, - new FileReRoute - { - DownstreamPathTemplate = "/www/test/", - UpstreamPathTemplate = "/asdf/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "bbc.co.uk", - } - }, - UpstreamHttpMethod = new List {"Get"} - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "reRoute /asdf/ has duplicate")) - .BDDfy(); - } - - [Fact] - public void configuration_is_valid_with_duplicate_reroutes_different_verbs() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "bbc.co.uk", - } - }, - }, - new FileReRoute - { - DownstreamPathTemplate = "/www/test/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Post"}, - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "bbc.co.uk", - } - }, - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_with_invalid_rate_limit_configuration() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "bbc.co.uk", - } - }, - RateLimitOptions = new FileRateLimitRule - { - Period = "1x", - EnableRateLimiting = true - } - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "RateLimitOptions.Period does not contains (s,m,h,d)")) - .BDDfy(); - } - - [Fact] - public void configuration_is_valid_with_valid_rate_limit_configuration() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "bbc.co.uk", - } - }, - RateLimitOptions = new FileRateLimitRule - { - Period = "1d", - EnableRateLimiting = true - } - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - public void configuration_is_invalid_with_using_service_discovery_and_no_service_name(string serviceName) - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = true, - ServiceName = serviceName - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!")) - .BDDfy(); - } - - [Fact] - public void configuration_is_valid_with_using_service_discovery_and_service_name() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = true, - ServiceName = "Test" - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - public void configuration_is_invalid_when_not_using_service_discovery_and_host(string downstreamHost) - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = downstreamHost, - } - }, - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "When not using service discovery Host must be set on DownstreamHostAndPorts if you are not using ReRoute.Host or Ocelot cannot find your service!")) - .BDDfy(); - } - - [Fact] - public void configuration_is_valid_when_not_using_service_discovery_and_host_is_set() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "bbc.co.uk" - } - }, - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); - } - - [Fact] - public void configuration_is_valid_when_no_downstream_but_has_host_and_port() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "test" - } - } - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsValid()) - .BDDfy(); - } - - [Fact] - public void configuration_is_not_valid_when_no_host_and_port() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, - DownstreamHostAndPorts = new List - { - } - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!")) - .BDDfy(); - } - - [Fact] - public void configuration_is_not_valid_when_host_and_port_is_empty() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, - DownstreamHostAndPorts = new List - { - new FileHostAndPort() - } - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "When not using service discovery Host must be set on DownstreamHostAndPorts if you are not using ReRoute.Host or Ocelot cannot find your service!")) - .BDDfy(); - } - - private void GivenAConfiguration(FileConfiguration fileConfiguration) - { - _fileConfiguration = fileConfiguration; - } - - private void WhenIValidateTheConfiguration() - { - _result = _configurationValidator.IsValid(_fileConfiguration).Result; - } - - private void ThenTheResultIsValid() - { - _result.Data.IsError.ShouldBeFalse(); - } - - private void ThenTheResultIsNotValid() - { - _result.Data.IsError.ShouldBeTrue(); - } - - private void ThenTheErrorIs() - { - _result.Data.Errors[0].ShouldBeOfType(); - } - - private void ThenTheErrorMessageAtPositionIs(int index, string expected) - { - _result.Data.Errors[index].Message.ShouldBe(expected); - } - - private void GivenTheAuthSchemeExists(string name) - { - _provider.Setup(x => x.GetAllSchemesAsync()).ReturnsAsync(new List - { - new AuthenticationScheme(name, name, typeof(TestHandler)) - }); - } - - private void GivenAQosDelegate() - { - var services = new ServiceCollection(); - QosDelegatingHandlerDelegate del = (a, b) => new FakeDelegatingHandler(); - services.AddSingleton(del); - var provider = services.BuildServiceProvider(); - _configurationValidator = new FileConfigurationFluentValidator(_provider.Object, provider); - } - - private class TestOptions : AuthenticationSchemeOptions - { - } - - private class TestHandler : AuthenticationHandler - { - public TestHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) - { - } - - protected override Task HandleAuthenticateAsync() - { - var principal = new ClaimsPrincipal(); - return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal, new AuthenticationProperties(), Scheme.Name))); - } - } - } -} + + [Fact] + public void configuration_is_valid_if_qos_options_specified_and_has_qos_handler() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + QoSOptions = new FileQoSOptions + { + TimeoutValue = 1, + ExceptionsAllowedBeforeBreaking = 1 + } + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .And(x => x.GivenAQoSHandler()) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_if_qos_options_specified_globally_and_has_qos_handler() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + QoSOptions = new FileQoSOptions + { + TimeoutValue = 1, + ExceptionsAllowedBeforeBreaking = 1 + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .And(x => x.GivenAQoSHandler()) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_qos_options_specified_but_no_qos_handler() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + QoSOptions = new FileQoSOptions + { + TimeoutValue = 1, + ExceptionsAllowedBeforeBreaking = 1 + } + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorIs()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using QoSOptions but no QosDelegatingHandlerDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Polly and services.AddPolly()?")) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_qos_options_specified_globally_but_no_qos_handler() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + QoSOptions = new FileQoSOptions + { + TimeoutValue = 1, + ExceptionsAllowedBeforeBreaking = 1 + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorIs()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using QoSOptions but no QosDelegatingHandlerDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Polly and services.AddPolly()?")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_if_aggregates_are_valid() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_aggregates_are_duplicate_of_re_routes() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom", + UpstreamHost = "localhost" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/tom", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + }, + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "reRoute /tom has duplicate aggregate")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_if_aggregates_are_not_duplicate_of_re_routes() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Post" }, + Key = "Tom", + UpstreamHost = "localhost" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/tom", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + }, + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_aggregates_are_duplicate_of_aggregates() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/lol", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/tom", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + }, + new FileAggregateReRoute + { + UpstreamPathTemplate = "/tom", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "aggregate /tom has duplicate aggregate")) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_re_routes_dont_exist_for_aggregate() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "ReRoutes for aggregateReRoute / either do not exist or do not have correct ServiceName property")) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_aggregate_has_re_routes_with_specific_request_id_keys() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51878, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51880, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + RequestIdKey = "should_fail", + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + } + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "aggregateReRoute / contains ReRoute with specific RequestIdKey, this is not possible with Aggregates")) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_scheme_in_downstream_or_upstream_template() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "http://www.bbc.co.uk/api/products/{productId}", + UpstreamPathTemplate = "http://asdf.com" + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .Then(x => x.ThenTheErrorIs()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} doesnt start with forward slash")) + .And(x => x.ThenTheErrorMessageAtPositionIs(1, "Upstream Path Template http://asdf.com contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) + .And(x => x.ThenTheErrorMessageAtPositionIs(2, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) + .And(x => x.ThenTheErrorMessageAtPositionIs(3, "Upstream Path Template http://asdf.com doesnt start with forward slash")) + .And(x => x.ThenTheErrorMessageAtPositionIs(4, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} contains scheme")) + .And(x => x.ThenTheErrorMessageAtPositionIs(5, "Upstream Path Template http://asdf.com contains scheme")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_with_one_reroute() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk" + } + }, + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_without_slash_prefix_downstream_path_template() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "api/products/", + UpstreamPathTemplate = "/asdf/" + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Downstream Path Template api/products/ doesnt start with forward slash")) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_without_slash_prefix_upstream_path_template() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "api/prod/", + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Upstream Path Template api/prod/ doesnt start with forward slash")) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_upstream_url_contains_forward_slash_then_another_forward_slash() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "//api/prod/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + Port = 80 + } + }, + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Upstream Path Template //api/prod/ contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_downstream_url_contains_forward_slash_then_another_forward_slash() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "//api/products/", + UpstreamPathTemplate = "/api/prod/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + Port = 80 + } + }, + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Downstream Path Template //api/products/ contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_with_valid_authentication_provider() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + } + }, + AuthenticationOptions = new FileAuthenticationOptions() + { + AuthenticationProviderKey = "Test" + } + } + } + })) + .And(x => x.GivenTheAuthSchemeExists("Test")) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_with_invalid_authentication_provider() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + AuthenticationOptions = new FileAuthenticationOptions() + { + AuthenticationProviderKey = "Test" + } + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "AuthenticationProviderKey:Test,AllowedScopes:[] is unsupported authentication provider")) + .BDDfy(); + } + + [Fact] + public void configuration_is_not_valid_with_duplicate_reroutes_all_verbs() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bb.co.uk" + } + }, + }, + new FileReRoute + { + DownstreamPathTemplate = "/www/test/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bb.co.uk" + } + }, + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "reRoute /asdf/ has duplicate")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_with_duplicate_reroutes_all_verbs_but_different_hosts() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bb.co.uk" + } + }, + UpstreamHost = "host1" + }, + new FileReRoute + { + DownstreamPathTemplate = "/www/test/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bb.co.uk" + } + }, + UpstreamHost = "host1" + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_not_valid_with_duplicate_reroutes_specific_verbs() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + } + }, + UpstreamHttpMethod = new List {"Get"} + }, + new FileReRoute + { + DownstreamPathTemplate = "/www/test/", + UpstreamPathTemplate = "/asdf/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + } + }, + UpstreamHttpMethod = new List {"Get"} + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "reRoute /asdf/ has duplicate")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_with_duplicate_reroutes_different_verbs() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + } + }, + }, + new FileReRoute + { + DownstreamPathTemplate = "/www/test/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Post"}, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + } + }, + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_with_invalid_rate_limit_configuration() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + } + }, + RateLimitOptions = new FileRateLimitRule + { + Period = "1x", + EnableRateLimiting = true + } + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "RateLimitOptions.Period does not contains (s,m,h,d)")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_with_valid_rate_limit_configuration() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk", + } + }, + RateLimitOptions = new FileRateLimitRule + { + Period = "1d", + EnableRateLimiting = true + } + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void configuration_is_invalid_with_using_service_discovery_and_no_service_name(string serviceName) + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + UseServiceDiscovery = true, + ServiceName = serviceName + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_with_using_service_discovery_and_service_name() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + UseServiceDiscovery = true, + ServiceName = "Test" + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void configuration_is_invalid_when_not_using_service_discovery_and_host(string downstreamHost) + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + UseServiceDiscovery = false, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = downstreamHost, + } + }, + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "When not using service discovery Host must be set on DownstreamHostAndPorts if you are not using ReRoute.Host or Ocelot cannot find your service!")) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_when_not_using_service_discovery_and_host_is_set() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + UseServiceDiscovery = false, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "bbc.co.uk" + } + }, + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_when_no_downstream_but_has_host_and_port() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + UseServiceDiscovery = false, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "test" + } + } + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_not_valid_when_no_host_and_port() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + UseServiceDiscovery = false, + DownstreamHostAndPorts = new List + { + } + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!")) + .BDDfy(); + } + + [Fact] + public void configuration_is_not_valid_when_host_and_port_is_empty() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products/", + UpstreamPathTemplate = "/asdf/", + UpstreamHttpMethod = new List {"Get"}, + UseServiceDiscovery = false, + DownstreamHostAndPorts = new List + { + new FileHostAndPort() + } + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "When not using service discovery Host must be set on DownstreamHostAndPorts if you are not using ReRoute.Host or Ocelot cannot find your service!")) + .BDDfy(); + } + + private void GivenAConfiguration(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + } + + private void WhenIValidateTheConfiguration() + { + _result = _configurationValidator.IsValid(_fileConfiguration).Result; + } + + private void ThenTheResultIsValid() + { + _result.Data.IsError.ShouldBeFalse(); + } + + private void ThenTheResultIsNotValid() + { + _result.Data.IsError.ShouldBeTrue(); + } + + private void ThenTheErrorIs() + { + _result.Data.Errors[0].ShouldBeOfType(); + } + + private void ThenTheErrorMessageAtPositionIs(int index, string expected) + { + _result.Data.Errors[index].Message.ShouldBe(expected); + } + + private void GivenTheAuthSchemeExists(string name) + { + _authProvider.Setup(x => x.GetAllSchemesAsync()).ReturnsAsync(new List + { + new AuthenticationScheme(name, name, typeof(TestHandler)) + }); + } + + private void GivenAQoSHandler() + { + var collection = new ServiceCollection(); + QosDelegatingHandlerDelegate del = (a,b) => new FakeDelegatingHandler(); + collection.AddSingleton(del); + var provider = collection.BuildServiceProvider(); + _configurationValidator = new FileConfigurationFluentValidator(_authProvider.Object, provider); + } + + private class TestOptions : AuthenticationSchemeOptions + { + } + + private class TestHandler : AuthenticationHandler + { + public TestHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) + { + } + + protected override Task HandleAuthenticateAsync() + { + var principal = new ClaimsPrincipal(); + return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal, new AuthenticationProperties(), Scheme.Name))); + } + } + } +} From 1ea59f12b325adba96ee43a5e7b46d770d95bd83 Mon Sep 17 00:00:00 2001 From: Andrew Rissing Date: Thu, 30 Aug 2018 00:54:11 -0500 Subject: [PATCH 45/50] Correcting minor typos. (#583) --- docs/features/loadbalancer.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/features/loadbalancer.rst b/docs/features/loadbalancer.rst index 30d56598..1aa65bed 100644 --- a/docs/features/loadbalancer.rst +++ b/docs/features/loadbalancer.rst @@ -18,7 +18,7 @@ You must choose in your configuration which load balancer to use. Configuration ^^^^^^^^^^^^^ -The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up. +The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeastConnection load balancer. This is the simplest way to get load balancing set up. .. code-block:: json @@ -46,7 +46,7 @@ The following shows how to set up multiple downstream services for a ReRoute usi Service Discovery ^^^^^^^^^^^^^^^^^ -The following shows how to set up a ReRoute using service discovery then select the LeadConnection load balancer. +The following shows how to set up a ReRoute using service discovery then select the LeastConnection load balancer. .. code-block:: json @@ -108,4 +108,4 @@ subsequent requests. This means the sessions will be stuck across ReRoutes. Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the -moment but could be changed. \ No newline at end of file +moment but could be changed. From 0a7d81038e4abb431af3b1b08911e6a507ca10c5 Mon Sep 17 00:00:00 2001 From: Justin <31854768+js8080@users.noreply.github.com> Date: Fri, 31 Aug 2018 12:20:13 -0500 Subject: [PATCH 46/50] Update requestid.rst (#587) fixing a typo --- docs/features/requestid.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/requestid.rst b/docs/features/requestid.rst index 37752eda..56c97b29 100644 --- a/docs/features/requestid.rst +++ b/docs/features/requestid.rst @@ -8,7 +8,7 @@ Ocelot will also forward the request id with the specified header to the downstr You can still get the asp.net core request id in the logs if you set IncludeScopes true in your logging config. -In order to use the reques tid feature you have two options. +In order to use the request id feature you have two options. *Global* From 55277cac4569f28ed98df3fe95f7634f889d88fc Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Fri, 31 Aug 2018 18:28:43 +0100 Subject: [PATCH 47/50] Feature/should not start if specified using service discovery but no provider registered (#584) * #580 added failing test * #580 added failing test for dynamic reroutes * #580 added failing test for validator * #580 validation tests passing * #580 acceptance tests passing * #580 got rid of the list in sdp factory * +semver: breaking #580 service discovery provider returned by delegate must be the same as name in config, this is a breaking change because you have to specify consul now * #580 removed use servide discovery property from file config, we dont need it, just use the service name as indicator the user wants to use service discovery for the given reroute --- docs/features/configuration.rst | 1 - docs/features/loadbalancer.rst | 441 +++++++++++++----- docs/features/routing.rst | 4 +- docs/features/servicediscovery.rst | 7 +- docs/features/servicefabric.rst | 3 +- samples/OcelotEureka/ApiGateway/ocelot.json | 1 - .../OcelotApplicationApiGateway/ocelot.json | 3 +- .../FileInternalConfigurationCreator.cs | 4 +- .../File/FileGlobalConfiguration.cs | 60 +-- src/Ocelot/Configuration/File/FileReRoute.cs | 1 - .../FileConfigurationFluentValidator.cs | 49 +- .../Validator/ReRouteFluentValidator.cs | 9 +- .../DownstreamUrlCreatorMiddleware.cs | 2 +- .../LoadBalancers/ILoadBalancerFactory.cs | 11 +- .../LoadBalancers/LoadBalancerFactory.cs | 20 +- .../LoadBalancers/LoadBalancerHouse.cs | 22 +- .../IServiceDiscoveryProviderFactory.cs | 21 +- .../ServiceDiscoveryProviderFactory.cs | 28 +- ...ableToFindServiceDiscoveryProviderError.cs | 11 + .../CannotStartOcelotTests.cs | 73 +++ test/Ocelot.AcceptanceTests/RoutingTests.cs | 4 - .../ServiceFabricTests.cs | 3 - .../FileConfigurationFluentValidatorTests.cs | 247 ++++++++-- .../LoadBalancer/LoadBalancerFactoryTests.cs | 7 +- .../LoadBalancer/LoadBalancerHouseTests.cs | 4 +- ...> ServiceDiscoveryProviderFactoryTests.cs} | 40 +- 26 files changed, 821 insertions(+), 255 deletions(-) create mode 100644 src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs rename test/Ocelot.UnitTests/ServiceDiscovery/{ServiceProviderFactoryTests.cs => ServiceDiscoveryProviderFactoryTests.cs} (80%) diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index bad1e6c5..4790afc5 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -64,7 +64,6 @@ Here is an example ReRoute configuration, You don't need to set all of these thi "UseCookieContainer": true, "UseTracing": true }, - "UseServiceDiscovery": false, "DangerousAcceptAnyServerCertificateValidator": false } diff --git a/docs/features/loadbalancer.rst b/docs/features/loadbalancer.rst index 1aa65bed..3c05a913 100644 --- a/docs/features/loadbalancer.rst +++ b/docs/features/loadbalancer.rst @@ -1,111 +1,330 @@ -Load Balancer -============= - -Ocelot can load balance across available downstream services for each ReRoute. This means you can scale your downstream services and Ocelot can use them effectively. - -The type of load balancer available are: - - LeastConnection - tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorythm state is not distributed across a cluster of Ocelot's. - - RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot's. - - NoLoadBalancer - takes the first available service from config or service discovery. - - CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below. - -You must choose in your configuration which load balancer to use. - -Configuration -^^^^^^^^^^^^^ - -The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeastConnection load balancer. This is the simplest way to get load balancing set up. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.1.10", - "Port": 5000, - }, - { - "Host": "10.0.1.11", - "Port": 5000, - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "LoadBalancerOptions": { - "Type": "LeastConnection" - }, - "UpstreamHttpMethod": [ "Put", "Delete" ] - } - - -Service Discovery -^^^^^^^^^^^^^^^^^ - -The following shows how to set up a ReRoute using service discovery then select the LeastConnection load balancer. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Put" ], - "ServiceName": "product", - "LoadBalancerOptions": { - "Type": "LeastConnection" - }, - "UseServiceDiscovery": true - } - -When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the -service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added. - -CookieStickySessions -^^^^^^^^^^^^^^^^^^^^ - -I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream -servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each -time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 `_ -though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have! - -In order to set up CookieStickySessions load balancer you need to do something like the following. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.1.10", - "Port": 5000, - }, - { - "Host": "10.0.1.11", - "Port": 5000, - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "LoadBalancerOptions": { - "Type": "CookieStickySessions", - "Key": "ASP.NET_SessionId", - "Expiry": 1800000 - }, - "UpstreamHttpMethod": [ "Put", "Delete" ] - } - -The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you -wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this -refreshes on every request which is meant to mimick how sessions work usually. - -If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there -subsequent requests. This means the sessions will be stuck across ReRoutes. - -Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul -and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the -moment but could be changed. + +Ocelot can load balance across available downstream services for each ReRoute. This means you can scale your downstream services and Ocelot can use them effectively. + +The type of load balancer available are: + + LeastConnection - tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorythm state is not distributed across a cluster of Ocelot's. + + RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot's. + + NoLoadBalancer - takes the first available service from config or service discovery. + + CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below. + +You must choose in your configuration which load balancer to use. + +Configuration +^^^^^^^^^^^^^ + +The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.1.10", + "Port": 5000, + }, + { + "Host": "10.0.1.11", + "Port": 5000, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "LoadBalancerOptions": { + "Type": "LeastConnection" + }, + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + + +Service Discovery +^^^^^^^^^^^^^^^^^ + +The following shows how to set up a ReRoute using service discovery then select the LeadConnection load balancer. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put" ], + "ServiceName": "product", + "LoadBalancerOptions": { + "Type": "LeastConnection" + }, + } + +When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the +service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added. + +CookieStickySessions +^^^^^^^^^^^^^^^^^^^^ + +I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream +servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each +time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 `_ +though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have! + +In order to set up CookieStickySessions load balancer you need to do something like the following. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.1.10", + "Port": 5000, + }, + { + "Host": "10.0.1.11", + "Port": 5000, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "LoadBalancerOptions": { + "Type": "CookieStickySessions", + "Key": "ASP.NET_SessionId", + "Expiry": 1800000 + }, + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + +The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you +wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this +refreshes on every request which is meant to mimick how sessions work usually. + +If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there +subsequent requests. This means the sessions will be stuck across ReRoutes. + +Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul +and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the +moment but could be changed. +||||||| merged common ancestors +Load Balancer +============= + +Ocelot can load balance across available downstream services for each ReRoute. This means you can scale your downstream services and Ocelot can use them effectively. + +The type of load balancer available are: + + LeastConnection - tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorythm state is not distributed across a cluster of Ocelot's. + + RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot's. + + NoLoadBalancer - takes the first available service from config or service discovery. + + CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below. + +You must choose in your configuration which load balancer to use. + +Configuration +^^^^^^^^^^^^^ + +The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.1.10", + "Port": 5000, + }, + { + "Host": "10.0.1.11", + "Port": 5000, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "LoadBalancerOptions": { + "Type": "LeastConnection" + }, + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + + +Service Discovery +^^^^^^^^^^^^^^^^^ + +The following shows how to set up a ReRoute using service discovery then select the LeadConnection load balancer. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put" ], + "ServiceName": "product", + "LoadBalancerOptions": { + "Type": "LeastConnection" + }, + } + +When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the +service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added. + +CookieStickySessions +^^^^^^^^^^^^^^^^^^^^ + +I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream +servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each +time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 `_ +though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have! + +In order to set up CookieStickySessions load balancer you need to do something like the following. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.1.10", + "Port": 5000, + }, + { + "Host": "10.0.1.11", + "Port": 5000, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "LoadBalancerOptions": { + "Type": "CookieStickySessions", + "Key": "ASP.NET_SessionId", + "Expiry": 1800000 + }, + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + +The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you +wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this +refreshes on every request which is meant to mimick how sessions work usually. + +If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there +subsequent requests. This means the sessions will be stuck across ReRoutes. + +Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul +and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the +moment but could be changed. +======= +Load Balancer +============= + +Ocelot can load balance across available downstream services for each ReRoute. This means you can scale your downstream services and Ocelot can use them effectively. + +The type of load balancer available are: + + LeastConnection - tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorythm state is not distributed across a cluster of Ocelot's. + + RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot's. + + NoLoadBalancer - takes the first available service from config or service discovery. + + CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below. + +You must choose in your configuration which load balancer to use. + +Configuration +^^^^^^^^^^^^^ + +The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeastConnection load balancer. This is the simplest way to get load balancing set up. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.1.10", + "Port": 5000, + }, + { + "Host": "10.0.1.11", + "Port": 5000, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "LoadBalancerOptions": { + "Type": "LeastConnection" + }, + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + + +Service Discovery +^^^^^^^^^^^^^^^^^ + +The following shows how to set up a ReRoute using service discovery then select the LeastConnection load balancer. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put" ], + "ServiceName": "product", + "LoadBalancerOptions": { + "Type": "LeastConnection" + }, + } + +When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the +service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added. + +CookieStickySessions +^^^^^^^^^^^^^^^^^^^^ + +I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream +servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each +time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 `_ +though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have! + +In order to set up CookieStickySessions load balancer you need to do something like the following. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.1.10", + "Port": 5000, + }, + { + "Host": "10.0.1.11", + "Port": 5000, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "LoadBalancerOptions": { + "Type": "CookieStickySessions", + "Key": "ASP.NET_SessionId", + "Expiry": 1800000 + }, + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + +The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you +wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this +refreshes on every request which is meant to mimick how sessions work usually. + +If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there +subsequent requests. This means the sessions will be stuck across ReRoutes. + +Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul +and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the +moment but could be changed. diff --git a/docs/features/routing.rst b/docs/features/routing.rst index c27de9a6..2ac2230e 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -211,7 +211,6 @@ Ocelot allows you to specify a querystring as part of the DownstreamPathTemplate } ], "GlobalConfiguration": { - "UseServiceDiscovery": false } } @@ -239,9 +238,8 @@ Ocelot will also allow you to put query string parameters in the UpstreamPathTem } ], "GlobalConfiguration": { - "UseServiceDiscovery": false } } In this example Ocelot will only match requests that have a matching url path and the querystring starts with unitId=something. You can have other queries after this -but you must start with the matching parameter. Also Ocelot will swap the {unitId} parameter from the query string and use it in the downstream request path. \ No newline at end of file +but you must start with the matching parameter. Also Ocelot will swap the {unitId} parameter from the query string and use it in the downstream request path. diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index c24a2e39..5a1f85ac 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -36,7 +36,7 @@ will be used. In the future we can add a feature that allows ReRoute specfic configuration. In order to tell Ocelot a ReRoute is to use the service discovery provider for its host and port you must add the -ServiceName, UseServiceDiscovery and load balancer you wish to use when making requests downstream. At the moment Ocelot has a RoundRobin +ServiceName and load balancer you wish to use when making requests downstream. At the moment Ocelot has a RoundRobin and LeastConnection algorithm you can use. If no load balancer is specified Ocelot will not load balance requests. .. code-block:: json @@ -50,7 +50,6 @@ and LeastConnection algorithm you can use. If no load balancer is specified Ocel "LoadBalancerOptions": { "Type": "LeastConnection" }, - "UseServiceDiscovery": true } When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. @@ -105,7 +104,8 @@ If you are using ACL with Consul Ocelot supports adding the X-Consul-Token heade "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 8500, - "Token": "footoken" + "Token": "footoken", + "Type": "Consul" } Ocelot will add this token to the consul client that it uses to make requests and that is then used for every request. @@ -232,6 +232,7 @@ Ocelot also allows you to set DynamicReRoutes which lets you set rate limiting r "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 8523, + "Type": "Consul" }, "RateLimitOptions": { "ClientIdHeader": "ClientId", diff --git a/docs/features/servicefabric.rst b/docs/features/servicefabric.rst index d3faa40f..0bc3ee1b 100644 --- a/docs/features/servicefabric.rst +++ b/docs/features/servicefabric.rst @@ -4,7 +4,7 @@ Service Fabric If you have services deployed in Service Fabric you will normally use the naming service to access them. The following example shows how to set up a ReRoute that will work in Service Fabric. The most important thing is the ServiceName which is made up of the -Service Fabric application name then the specific service name. We also need to set UseServiceDiscovery as true and set up the ServiceDiscoveryProvider in +Service Fabric application name then the specific service name. We also need to set up the ServiceDiscoveryProvider in GlobalConfiguration. The example here shows a typical configuration. It assumes service fabric is running on localhost and that the naming service is on port 19081. The example below is taken from the samples folder so please check it if this doesnt make sense! @@ -21,7 +21,6 @@ The example below is taken from the samples folder so please check it if this do ], "DownstreamScheme": "http", "ServiceName": "OcelotServiceApplication/OcelotApplicationService", - "UseServiceDiscovery" : true } ], "GlobalConfiguration": { diff --git a/samples/OcelotEureka/ApiGateway/ocelot.json b/samples/OcelotEureka/ApiGateway/ocelot.json index b8694ddd..963160f9 100644 --- a/samples/OcelotEureka/ApiGateway/ocelot.json +++ b/samples/OcelotEureka/ApiGateway/ocelot.json @@ -4,7 +4,6 @@ "DownstreamPathTemplate": "/api/Category", "DownstreamScheme": "http", "UpstreamPathTemplate": "/Category", - "UseServiceDiscovery": true, "ServiceName": "ncore-rat", "UpstreamHttpMethod": [ "Get" ], "QoSOptions": { diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ocelot.json b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ocelot.json index dce3d55e..8a679243 100644 --- a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ocelot.json +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ocelot.json @@ -7,8 +7,7 @@ "Get" ], "DownstreamScheme": "http", - "ServiceName": "OcelotServiceApplication/OcelotApplicationService", - "UseServiceDiscovery" : true + "ServiceName": "OcelotServiceApplication/OcelotApplicationService" } ], "GlobalConfiguration": { diff --git a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs index 89bd7a41..6aea0702 100644 --- a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs @@ -224,6 +224,8 @@ namespace Ocelot.Configuration.Creator var lbOptions = CreateLoadBalancerOptions(fileReRoute.LoadBalancerOptions); + var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName); + var reRoute = new DownstreamReRouteBuilder() .WithKey(fileReRoute.Key) .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) @@ -249,7 +251,7 @@ namespace Ocelot.Configuration.Creator .WithRateLimitOptions(rateLimitOption) .WithHttpHandlerOptions(httpHandlerOptions) .WithServiceName(fileReRoute.ServiceName) - .WithUseServiceDiscovery(fileReRoute.UseServiceDiscovery) + .WithUseServiceDiscovery(useServiceDiscovery) .WithUpstreamHeaderFindAndReplace(hAndRs.Upstream) .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) .WithUpstreamHost(fileReRoute.UpstreamHost) diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs index 8d1ca3f5..a13284ad 100644 --- a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs @@ -1,30 +1,30 @@ -namespace Ocelot.Configuration.File -{ - public class FileGlobalConfiguration - { - public FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider(); - RateLimitOptions = new FileRateLimitOptions(); - LoadBalancerOptions = new FileLoadBalancerOptions(); - QoSOptions = new FileQoSOptions(); - HttpHandlerOptions = new FileHttpHandlerOptions(); - } - - public string RequestIdKey { get; set; } - - public FileServiceDiscoveryProvider ServiceDiscoveryProvider { get;set; } - - public FileRateLimitOptions RateLimitOptions { get; set; } - - public FileQoSOptions QoSOptions { get; set; } - - public string BaseUrl { get ;set; } - - public FileLoadBalancerOptions LoadBalancerOptions { get; set; } - - public string DownstreamScheme { get; set; } - - public FileHttpHandlerOptions HttpHandlerOptions { get; set; } - } -} +namespace Ocelot.Configuration.File +{ + public class FileGlobalConfiguration + { + public FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider(); + RateLimitOptions = new FileRateLimitOptions(); + LoadBalancerOptions = new FileLoadBalancerOptions(); + QoSOptions = new FileQoSOptions(); + HttpHandlerOptions = new FileHttpHandlerOptions(); + } + + public string RequestIdKey { get; set; } + + public FileServiceDiscoveryProvider ServiceDiscoveryProvider { get;set; } + + public FileRateLimitOptions RateLimitOptions { get; set; } + + public FileQoSOptions QoSOptions { get; set; } + + public string BaseUrl { get ;set; } + + public FileLoadBalancerOptions LoadBalancerOptions { get; set; } + + public string DownstreamScheme { get; set; } + + public FileHttpHandlerOptions HttpHandlerOptions { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 44f9cd9e..f03156e4 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -43,7 +43,6 @@ namespace Ocelot.Configuration.File public FileRateLimitRule RateLimitOptions { get; set; } public FileAuthenticationOptions AuthenticationOptions { get; set; } public FileHttpHandlerOptions HttpHandlerOptions { get; set; } - public bool UseServiceDiscovery { get;set; } public List DownstreamHostAndPorts {get;set;} public string UpstreamHost { get; set; } public string Key { get;set; } diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs index 5e6ac61b..ef380a21 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs @@ -11,15 +11,19 @@ namespace Ocelot.Configuration.Validator { using System; using Microsoft.Extensions.DependencyInjection; + using Ocelot.ServiceDiscovery; using Requester; public class FileConfigurationFluentValidator : AbstractValidator, IConfigurationValidator { private readonly QosDelegatingHandlerDelegate _qosDelegatingHandlerDelegate; - + private readonly List _serviceDiscoveryFinderDelegates; public FileConfigurationFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, IServiceProvider provider) { _qosDelegatingHandlerDelegate = provider.GetService(); + _serviceDiscoveryFinderDelegates = provider + .GetServices() + .ToList(); RuleFor(configuration => configuration.ReRoutes) .SetCollectionValidator(new ReRouteFluentValidator(authenticationSchemeProvider, _qosDelegatingHandlerDelegate)); @@ -31,6 +35,14 @@ namespace Ocelot.Configuration.Validator .Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.ReRoutes)) .WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate"); + RuleForEach(configuration => configuration.ReRoutes) + .Must((config, reRoute) => HaveServiceDiscoveryProviderRegitered(reRoute, config.GlobalConfiguration.ServiceDiscoveryProvider)) + .WithMessage((config, reRoute) => $"Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?"); + + RuleFor(configuration => configuration.GlobalConfiguration.ServiceDiscoveryProvider) + .Must((config) => HaveServiceDiscoveryProviderRegitered(config)) + .WithMessage((config, reRoute) => $"Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?"); + RuleForEach(configuration => configuration.ReRoutes) .Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.Aggregates)) .WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate aggregate"); @@ -48,6 +60,41 @@ namespace Ocelot.Configuration.Validator .WithMessage((config, aggregateReRoute) => $"{nameof(aggregateReRoute)} {aggregateReRoute.UpstreamPathTemplate} contains ReRoute with specific RequestIdKey, this is not possible with Aggregates"); } + private bool HaveServiceDiscoveryProviderRegitered(FileReRoute reRoute, FileServiceDiscoveryProvider serviceDiscoveryProvider) + { + if (string.IsNullOrEmpty(reRoute.ServiceName)) + { + return true; + } + + if (serviceDiscoveryProvider?.Type?.ToLower() == "servicefabric") + { + return true; + } + + return _serviceDiscoveryFinderDelegates.Any(); + } + + private bool HaveServiceDiscoveryProviderRegitered(FileServiceDiscoveryProvider serviceDiscoveryProvider) + { + if(serviceDiscoveryProvider == null) + { + return true; + } + + if (serviceDiscoveryProvider?.Type?.ToLower() == "servicefabric") + { + return true; + } + + if(string.IsNullOrEmpty(serviceDiscoveryProvider.Type)) + { + return true; + } + + return _serviceDiscoveryFinderDelegates.Any(); + } + public async Task> IsValid(FileConfiguration configuration) { var validateResult = await ValidateAsync(configuration); diff --git a/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs b/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs index 74b0934e..a6ea82cb 100644 --- a/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs @@ -55,17 +55,12 @@ .MustAsync(IsSupportedAuthenticationProviders) .WithMessage("{PropertyValue} is unsupported authentication provider"); - When(reRoute => reRoute.UseServiceDiscovery, () => { - RuleFor(r => r.ServiceName).NotEmpty() - .WithMessage("ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!"); - }); - - When(reRoute => !reRoute.UseServiceDiscovery, () => { + When(reRoute => string.IsNullOrEmpty(reRoute.ServiceName), () => { RuleFor(r => r.DownstreamHostAndPorts).NotEmpty() .WithMessage("When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!"); }); - When(reRoute => !reRoute.UseServiceDiscovery, () => { + When(reRoute => string.IsNullOrEmpty(reRoute.ServiceName), () => { RuleFor(reRoute => reRoute.DownstreamHostAndPorts) .SetCollectionValidator(new HostAndPortValidator()); }); diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 81f5019b..02b40c07 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -122,7 +122,7 @@ namespace Ocelot.DownstreamUrlCreator.Middleware private static bool ServiceFabricRequest(DownstreamContext context) { - return context.Configuration.ServiceProviderConfiguration.Type == "ServiceFabric" && context.DownstreamReRoute.UseServiceDiscovery; + return context.Configuration.ServiceProviderConfiguration.Type?.ToLower() == "servicefabric" && context.DownstreamReRoute.UseServiceDiscovery; } } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs index 93082de6..7ca857f5 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs @@ -1,10 +1,11 @@ -using System.Threading.Tasks; -using Ocelot.Configuration; - -namespace Ocelot.LoadBalancer.LoadBalancers +namespace Ocelot.LoadBalancer.LoadBalancers { + using System.Threading.Tasks; + using Ocelot.Configuration; + using Ocelot.Responses; + public interface ILoadBalancerFactory { - Task Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config); + Task> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config); } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs index c9a9cbe8..dce8d405 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Ocelot.Configuration; using Ocelot.Infrastructure; +using Ocelot.Responses; using Ocelot.ServiceDiscovery; namespace Ocelot.LoadBalancer.LoadBalancers @@ -14,22 +15,29 @@ namespace Ocelot.LoadBalancer.LoadBalancers _serviceProviderFactory = serviceProviderFactory; } - public async Task Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config) + public async Task> Get(DownstreamReRoute reRoute, ServiceProviderConfiguration config) { - var serviceProvider = _serviceProviderFactory.Get(config, reRoute); + var response = _serviceProviderFactory.Get(config, reRoute); + + if(response.IsError) + { + return new ErrorResponse(response.Errors); + } + + var serviceProvider = response.Data; switch (reRoute.LoadBalancerOptions?.Type) { case nameof(RoundRobin): - return new RoundRobin(async () => await serviceProvider.Get()); + return new OkResponse(new RoundRobin(async () => await serviceProvider.Get())); case nameof(LeastConnection): - return new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName); + return new OkResponse(new LeastConnection(async () => await serviceProvider.Get(), reRoute.ServiceName)); case nameof(CookieStickySessions): var loadBalancer = new RoundRobin(async () => await serviceProvider.Get()); var bus = new InMemoryBus(); - return new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, reRoute.LoadBalancerOptions.ExpiryInMs, bus); + return new OkResponse(new CookieStickySessions(loadBalancer, reRoute.LoadBalancerOptions.Key, reRoute.LoadBalancerOptions.ExpiryInMs, bus)); default: - return new NoLoadBalancer(async () => await serviceProvider.Get()); + return new OkResponse(new NoLoadBalancer(async () => await serviceProvider.Get())); } } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs index 8ca3fab1..6d195d4d 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -22,24 +22,36 @@ namespace Ocelot.LoadBalancer.LoadBalancers { try { - if(_loadBalancers.TryGetValue(reRoute.LoadBalancerKey, out var loadBalancer)) + Response result; + + if (_loadBalancers.TryGetValue(reRoute.LoadBalancerKey, out var loadBalancer)) { loadBalancer = _loadBalancers[reRoute.LoadBalancerKey]; - if(reRoute.LoadBalancerOptions.Type != loadBalancer.GetType().Name) + if (reRoute.LoadBalancerOptions.Type != loadBalancer.GetType().Name) { - loadBalancer = await _factory.Get(reRoute, config); + result = await _factory.Get(reRoute, config); + if (result.IsError) + { + return new ErrorResponse(result.Errors); + } + loadBalancer = result.Data; AddLoadBalancer(reRoute.LoadBalancerKey, loadBalancer); } return new OkResponse(loadBalancer); } - loadBalancer = await _factory.Get(reRoute, config); + result = await _factory.Get(reRoute, config); + if (result.IsError) + { + return new ErrorResponse(result.Errors); + } + loadBalancer = result.Data; AddLoadBalancer(reRoute.LoadBalancerKey, loadBalancer); return new OkResponse(loadBalancer); } - catch(Exception ex) + catch (Exception ex) { return new ErrorResponse(new List() { diff --git a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs index 91e9c700..a3c3b18b 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs @@ -1,10 +1,11 @@ -using Ocelot.Configuration; -using Ocelot.ServiceDiscovery.Providers; - -namespace Ocelot.ServiceDiscovery -{ - public interface IServiceDiscoveryProviderFactory - { - IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute); - } -} +namespace Ocelot.ServiceDiscovery +{ + using Ocelot.Configuration; + using Ocelot.Responses; + using Ocelot.ServiceDiscovery.Providers; + + public interface IServiceDiscoveryProviderFactory + { + Response Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute); + } +} diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index 503814e9..1fa8c7c0 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -9,23 +9,22 @@ namespace Ocelot.ServiceDiscovery using System; using System.Linq; using Microsoft.Extensions.DependencyInjection; + using Ocelot.Responses; public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory { private readonly IOcelotLoggerFactory _factory; - private readonly List _delegates; + private readonly ServiceDiscoveryFinderDelegate _delegates; private readonly IServiceProvider _provider; public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IServiceProvider provider) { _factory = factory; _provider = provider; - _delegates = provider - .GetServices() - .ToList(); + _delegates = provider.GetService(); } - public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute) + public Response Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute) { if (reRoute.UseServiceDiscovery) { @@ -37,31 +36,32 @@ namespace Ocelot.ServiceDiscovery foreach (var downstreamAddress in reRoute.DownstreamAddresses) { var service = new Service(reRoute.ServiceName, new ServiceHostAndPort(downstreamAddress.Host, downstreamAddress.Port), string.Empty, string.Empty, new string[0]); - + services.Add(service); } - return new ConfigurationServiceProvider(services); + return new OkResponse(new ConfigurationServiceProvider(services)); } - private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration config, string key) + private Response GetServiceDiscoveryProvider(ServiceProviderConfiguration config, string key) { if (config.Type?.ToLower() == "servicefabric") { var sfConfig = new ServiceFabricConfiguration(config.Host, config.Port, key); - return new ServiceFabricServiceDiscoveryProvider(sfConfig); + return new OkResponse(new ServiceFabricServiceDiscoveryProvider(sfConfig)); } - foreach (var serviceDiscoveryFinderDelegate in _delegates) + if (_delegates != null) { - var provider = serviceDiscoveryFinderDelegate?.Invoke(_provider, config, key); - if (provider != null) + var provider = _delegates?.Invoke(_provider, config, key); + + if(provider.GetType().Name.ToLower() == config.Type.ToLower()) { - return provider; + return new OkResponse(provider); } } - return null; + return new ErrorResponse(new UnableToFindServiceDiscoveryProviderError($"Unable to find service discovery provider for type: {config.Type}")); } } } diff --git a/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs b/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs new file mode 100644 index 00000000..3ac838d2 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs @@ -0,0 +1,11 @@ +namespace Ocelot.ServiceDiscovery +{ + using Ocelot.Errors; + + public class UnableToFindServiceDiscoveryProviderError : Error + { + public UnableToFindServiceDiscoveryProviderError(string message) : base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError) + { + } + } +} diff --git a/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs b/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs index 35029a13..5ba30fa3 100644 --- a/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs +++ b/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs @@ -15,6 +15,79 @@ namespace Ocelot.AcceptanceTests _steps = new Steps(); } + [Fact] + public void should_throw_exception_if_cannot_start_because_service_discovery_provider_specified_in_config_but_no_service_discovery_provider_registered_with_dynamic_re_routes() + { + var invalidConfig = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Type = "consul", + Port = 8500 + } + } + }; + + Exception exception = null; + _steps.GivenThereIsAConfiguration(invalidConfig); + try + { + _steps.GivenOcelotIsRunning(); + } + catch (Exception ex) + { + exception = ex; + } + + exception.ShouldNotBeNull(); + exception.Message.ShouldBe("One or more errors occurred. (Unable to start Ocelot, errors are: Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?)"); + } + + [Fact] + public void should_throw_exception_if_cannot_start_because_service_discovery_provider_specified_in_config_but_no_service_discovery_provider_registered() + { + var invalidConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + ServiceName = "test" + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Type = "consul", + Port = 8500 + } + } + }; + + Exception exception = null; + _steps.GivenThereIsAConfiguration(invalidConfig); + try + { + _steps.GivenOcelotIsRunning(); + } + catch (Exception ex) + { + exception = ex; + } + + exception.ShouldNotBeNull(); + exception.Message.ShouldBe("One or more errors occurred. (Unable to start Ocelot, errors are: Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?,Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?)"); + } + [Fact] public void should_throw_exception_if_cannot_start_because_no_qos_delegate_registered_globally() { diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 23cb19dd..d3379aa1 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -336,7 +336,6 @@ namespace Ocelot.AcceptanceTests }, UpstreamPathTemplate = "/vacancy/", UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, - ServiceName = "botCore", LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" } }, new FileReRoute @@ -353,7 +352,6 @@ namespace Ocelot.AcceptanceTests }, UpstreamPathTemplate = "/vacancy/{vacancyId}", UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, - ServiceName = "botCore", LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" } } } @@ -828,7 +826,6 @@ namespace Ocelot.AcceptanceTests }, UpstreamPathTemplate = "/vacancy/", UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, - ServiceName = "botCore", LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" } }, new FileReRoute @@ -845,7 +842,6 @@ namespace Ocelot.AcceptanceTests }, UpstreamPathTemplate = "/vacancy/{vacancyId}", UpstreamHttpMethod = new List { "Options", "Put", "Get", "Post", "Delete" }, - ServiceName = "botCore", LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" } } } diff --git a/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs b/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs index 19268f0d..4e92b54d 100644 --- a/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs @@ -33,7 +33,6 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", UpstreamPathTemplate = "/{everything}", UpstreamHttpMethod = new List { "Get" }, - UseServiceDiscovery = true, ServiceName = "OcelotServiceApplication/OcelotApplicationService" } }, @@ -70,7 +69,6 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", UpstreamPathTemplate = "/EquipmentInterfaces", UpstreamHttpMethod = new List { "Get" }, - UseServiceDiscovery = true, ServiceName = "OcelotServiceApplication/OcelotApplicationService" } }, @@ -107,7 +105,6 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", UpstreamPathTemplate = "/EquipmentInterfaces", UpstreamHttpMethod = new List { "Get" }, - UseServiceDiscovery = true, ServiceName = "OcelotServiceApplication/OcelotApplicationService" } }, diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationFluentValidatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationFluentValidatorTests.cs index 9fec1700..9b473c6f 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationFluentValidatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationFluentValidatorTests.cs @@ -17,6 +17,9 @@ using Microsoft.Extensions.DependencyInjection; using Ocelot.Requester; using Requester; + using Ocelot.ServiceDiscovery.Providers; + using Ocelot.Values; + using Ocelot.ServiceDiscovery; public class FileConfigurationFluentValidatorTests { @@ -33,6 +36,191 @@ _configurationValidator = new FileConfigurationFluentValidator(_authProvider.Object, provider); } + [Fact] + public void configuration_is_valid_if_service_discovery_options_specified_and_has_service_fabric_as_option() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + ServiceName = "test" + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Type = "ServiceFabric", + Port = 8500 + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_if_service_discovery_options_specified_and_has_service_discovery_handler() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + ServiceName = "test" + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Type = "FakeServiceDiscoveryProvider", + Port = 8500 + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .And(x => x.GivenAServiceDiscoveryHandler()) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_valid_if_service_discovery_options_specified_dynamically_and_has_service_discovery_handler() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Type = "FakeServiceDiscoveryProvider", + Port = 8500 + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .And(x => x.GivenAServiceDiscoveryHandler()) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_service_discovery_options_specified_but_no_service_discovery_handler() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + ServiceName = "test" + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Type = "FakeServiceDiscoveryProvider", + Port = 8500 + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorIs()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?")) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_service_discovery_options_specified_dynamically_but_service_discovery_handler() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Type = "FakeServiceDiscoveryProvider", + Port = 8500 + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorIs()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?")) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_service_discovery_options_specified_but_no_service_discovery_handler_with_matching_name() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + ServiceName = "test" + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Type = "consul", + Port = 8500 + } + } + }; + + this.Given(x => x.GivenAConfiguration(configuration)) + .When(x => x.WhenIValidateTheConfiguration()) + .And(x => x.GivenAServiceDiscoveryHandler()) + .Then(x => x.ThenTheResultIsNotValid()) + .And(x => x.ThenTheErrorIs()) + .And(x => x.ThenTheErrorMessageAtPositionIs(0, "Unable to start Ocelot, errors are: Unable to start Ocelot because either a ReRoute or GlobalConfiguration are using ServiceDiscoveryOptions but no ServiceDiscoveryFinderDelegate has been registered in dependency injection container. Are you missing a package like Ocelot.Provider.Consul and services.AddConsul() or Ocelot.Provider.Eureka and services.AddEureka()?")) + .BDDfy(); + } + [Fact] public void configuration_is_valid_if_qos_options_specified_and_has_qos_handler() { @@ -151,7 +339,7 @@ .BDDfy(); } - [Fact] + [Fact] public void configuration_is_invalid_if_qos_options_specified_globally_but_no_qos_handler() { var configuration = new FileConfiguration @@ -988,31 +1176,6 @@ .BDDfy(); } - [Theory] - [InlineData(null)] - [InlineData("")] - public void configuration_is_invalid_with_using_service_discovery_and_no_service_name(string serviceName) - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/products/", - UpstreamPathTemplate = "/asdf/", - UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = true, - ServiceName = serviceName - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorMessageAtPositionIs(0, "ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!")) - .BDDfy(); - } - [Fact] public void configuration_is_valid_with_using_service_discovery_and_service_name() { @@ -1025,9 +1188,17 @@ DownstreamPathTemplate = "/api/products/", UpstreamPathTemplate = "/asdf/", UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = true, ServiceName = "Test" } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Type = "servicefabric", + Host = "localhost", + Port = 1234 + } } })) .When(x => x.WhenIValidateTheConfiguration()) @@ -1049,7 +1220,6 @@ DownstreamPathTemplate = "/api/products/", UpstreamPathTemplate = "/asdf/", UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, DownstreamHostAndPorts = new List { new FileHostAndPort @@ -1078,7 +1248,6 @@ DownstreamPathTemplate = "/api/products/", UpstreamPathTemplate = "/asdf/", UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, DownstreamHostAndPorts = new List { new FileHostAndPort @@ -1106,7 +1275,6 @@ DownstreamPathTemplate = "/api/products/", UpstreamPathTemplate = "/asdf/", UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, DownstreamHostAndPorts = new List { new FileHostAndPort @@ -1134,7 +1302,6 @@ DownstreamPathTemplate = "/api/products/", UpstreamPathTemplate = "/asdf/", UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, DownstreamHostAndPorts = new List { } @@ -1159,7 +1326,6 @@ DownstreamPathTemplate = "/api/products/", UpstreamPathTemplate = "/asdf/", UpstreamHttpMethod = new List {"Get"}, - UseServiceDiscovery = false, DownstreamHostAndPorts = new List { new FileHostAndPort() @@ -1220,6 +1386,23 @@ _configurationValidator = new FileConfigurationFluentValidator(_authProvider.Object, provider); } + private void GivenAServiceDiscoveryHandler() + { + var collection = new ServiceCollection(); + ServiceDiscoveryFinderDelegate del = (a,b,c) => new FakeServiceDiscoveryProvider(); + collection.AddSingleton(del); + var provider = collection.BuildServiceProvider(); + _configurationValidator = new FileConfigurationFluentValidator(_authProvider.Object, provider); + } + + private class FakeServiceDiscoveryProvider : IServiceDiscoveryProvider + { + public Task> Get() + { + throw new System.NotImplementedException(); + } + } + private class TestOptions : AuthenticationSchemeOptions { } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index 703078ce..652193ca 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using Ocelot.ServiceDiscovery.Providers; using TestStack.BDDfy; using Xunit; +using Ocelot.Responses; namespace Ocelot.UnitTests.LoadBalancer { @@ -15,7 +16,7 @@ namespace Ocelot.UnitTests.LoadBalancer { private DownstreamReRoute _reRoute; private readonly LoadBalancerFactory _factory; - private ILoadBalancer _result; + private Response _result; private readonly Mock _serviceProviderFactory; private readonly Mock _serviceProvider; private ServiceProviderConfiguration _serviceProviderConfig; @@ -115,7 +116,7 @@ namespace Ocelot.UnitTests.LoadBalancer { _serviceProviderFactory .Setup(x => x.Get(It.IsAny(), It.IsAny())) - .Returns(_serviceProvider.Object); + .Returns(new OkResponse(_serviceProvider.Object)); } private void ThenTheServiceProviderIsCalledCorrectly() @@ -136,7 +137,7 @@ namespace Ocelot.UnitTests.LoadBalancer private void ThenTheLoadBalancerIsReturned() { - _result.ShouldBeOfType(); + _result.Data.ShouldBeOfType(); } } } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs index dbab895d..e74f5728 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -111,7 +111,7 @@ namespace Ocelot.UnitTests.LoadBalancer private void WhenIGetTheReRouteWithTheSameKeyButDifferentLoadBalancer(DownstreamReRoute reRoute) { _reRoute = reRoute; - _factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(new LeastConnection(null, null)); + _factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(new OkResponse(new LeastConnection(null, null))); _getResult = _loadBalancerHouse.Get(_reRoute, _serviceProviderConfig).Result; } @@ -138,7 +138,7 @@ namespace Ocelot.UnitTests.LoadBalancer { _reRoute = reRoute; _loadBalancer = loadBalancer; - _factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(loadBalancer); + _factory.Setup(x => x.Get(_reRoute, _serviceProviderConfig)).ReturnsAsync(new OkResponse(loadBalancer)); _getResult = _loadBalancerHouse.Get(reRoute, _serviceProviderConfig).Result; } diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceDiscoveryProviderFactoryTests.cs similarity index 80% rename from test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs rename to test/Ocelot.UnitTests/ServiceDiscovery/ServiceDiscoveryProviderFactoryTests.cs index 6db38012..46cec86e 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceDiscoveryProviderFactoryTests.cs @@ -14,11 +14,12 @@ namespace Ocelot.UnitTests.ServiceDiscovery using Shouldly; using TestStack.BDDfy; using Xunit; + using Ocelot.Responses; - public class ServiceProviderFactoryTests + public class ServiceDiscoveryProviderFactoryTests { private ServiceProviderConfiguration _serviceConfig; - private IServiceDiscoveryProvider _result; + private Response _result; private ServiceDiscoveryProviderFactory _factory; private DownstreamReRoute _reRoute; private readonly Mock _loggerFactory; @@ -26,7 +27,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery private IServiceProvider _provider; private readonly IServiceCollection _collection; - public ServiceProviderFactoryTests() + public ServiceDiscoveryProviderFactoryTests() { _loggerFactory = new Mock(); _logger = new Mock(); @@ -71,7 +72,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery } [Fact] - public void should_call_delegate() + public void should_return_provider_because_type_matches_reflected_type_from_delegate() { var reRoute = new DownstreamReRouteBuilder() .WithServiceName("product") @@ -79,6 +80,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery .Build(); var serviceConfig = new ServiceProviderConfigurationBuilder() + .WithType(nameof(Fake)) .Build(); this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) @@ -88,6 +90,25 @@ namespace Ocelot.UnitTests.ServiceDiscovery .BDDfy(); } + [Fact] + public void should_not_return_provider_because_type_doesnt_match_reflected_type_from_delegate() + { + var reRoute = new DownstreamReRouteBuilder() + .WithServiceName("product") + .WithUseServiceDiscovery(true) + .Build(); + + var serviceConfig = new ServiceProviderConfigurationBuilder() + .WithType("Wookie") + .Build(); + + this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) + .And(x => GivenAFakeDelegate()) + .When(x => x.WhenIGetTheServiceProvider()) + .Then(x => x.ThenTheResultIsError()) + .BDDfy(); + } + [Fact] public void should_return_service_fabric_provider() { @@ -124,12 +145,17 @@ namespace Ocelot.UnitTests.ServiceDiscovery private void ThenTheDelegateIsCalled() { - _result.GetType().Name.ShouldBe("Fake"); + _result.Data.GetType().Name.ShouldBe("Fake"); + } + + private void ThenTheResultIsError() + { + _result.IsError.ShouldBeTrue(); } private void ThenTheFollowingServicesAreReturned(List downstreamAddresses) { - var result = (ConfigurationServiceProvider)_result; + var result = (ConfigurationServiceProvider)_result.Data; var services = result.Get().Result; for (int i = 0; i < services.Count; i++) @@ -155,7 +181,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery private void ThenTheServiceProviderIs() { - _result.ShouldBeOfType(); + _result.Data.ShouldBeOfType(); } } } From 66b68fc6859af2cf1268083e19cdec49c1631e27 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 1 Sep 2018 13:10:45 +0100 Subject: [PATCH 48/50] Feature/#574 look at httpclient cache key (#589) * #574 consolidate some code, man the config stuff is a mess! * #574 just use the downstream re route and the key for caching http clients * #574 added benchmark, i was suprised to learn using a complex type was faster than a string in benchmark .net dictionary tests, hey ho probably dont have enough data in the type... --- .../Middleware/AuthorisationMiddleware.cs | 10 +- .../Builder/DownstreamReRouteBuilder.cs | 11 +- .../Configuration/Builder/ReRouteBuilder.cs | 22 +-- .../Builder/UpstreamPathTemplateBuilder.cs | 41 ++++ .../FileInternalConfigurationCreator.cs | 3 - .../Creator/UpstreamTemplatePatternCreator.cs | 6 +- src/Ocelot/Configuration/DownstreamReRoute.cs | 10 +- src/Ocelot/Configuration/ReRoute.cs | 15 +- .../Finder/DownstreamRouteFinder.cs | 2 +- .../Finder/DownstreamRouteProviderFactory.cs | 4 +- .../DownstreamRouteFinderMiddleware.cs | 2 +- .../UrlMatcher/RegExUrlMatcher.cs | 2 +- .../DownstreamUrlCreatorMiddleware.cs | 2 +- ...wnstreamTemplatePathPlaceholderReplacer.cs | 6 +- .../IDownstreamPathPlaceholderReplacer.cs | 4 +- .../HttpRequestHeadersBuilderMiddleware.cs | 2 +- .../QueryStringBuilderMiddleware.cs | 2 +- .../Middleware/ClientRateLimitMiddleware.cs | 6 +- src/Ocelot/Requester/HttpClientBuilder.cs | 15 +- src/Ocelot/Requester/IHttpClientCache.cs | 5 +- src/Ocelot/Requester/MemoryHttpClientCache.cs | 9 +- src/Ocelot/Requester/QoS/QosFactory.cs | 2 +- ...hTemplate.cs => DownstreamPathTemplate.cs} | 24 +-- src/Ocelot/Values/UpstreamPathTemplate.cs | 8 +- .../HttpClientCachingTests.cs | 163 ++++++++++++++++ test/Ocelot.AcceptanceTests/Steps.cs | 30 +++ .../Ocelot.Benchmarks/DictionaryBenchmarks.cs | 77 ++++++++ test/Ocelot.Benchmarks/Program.cs | 1 + .../AuthorisationMiddlewareTests.cs | 2 + .../FileInternalConfigurationCreatorTests.cs | 122 ++++++++---- .../InMemoryConfigurationRepositoryTests.cs | 2 +- .../ConfigurationBuilderExtensionsTests.cs | 2 +- .../DownstreamRouteCreatorTests.cs | 8 +- .../DownstreamRouteFinderTests.cs | 182 +++++++----------- .../DownstreamRouteProviderFactoryTests.cs | 23 +-- .../DownstreamUrlCreatorMiddlewareTests.cs | 4 +- ...eamUrlPathTemplateVariableReplacerTests.cs | 2 +- .../ClientRateLimitMiddlewareTests.cs | 25 ++- .../Requester/HttpClientBuilderTests.cs | 94 ++++++++- .../Requester/HttpClientHttpRequesterTest.cs | 11 +- 40 files changed, 672 insertions(+), 289 deletions(-) create mode 100644 src/Ocelot/Configuration/Builder/UpstreamPathTemplateBuilder.cs rename src/Ocelot/Values/{PathTemplate.cs => DownstreamPathTemplate.cs} (58%) create mode 100644 test/Ocelot.AcceptanceTests/HttpClientCachingTests.cs create mode 100644 test/Ocelot.Benchmarks/DictionaryBenchmarks.cs diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs index 82fc3af7..fb4f7a26 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs @@ -50,7 +50,7 @@ Logger.LogWarning("user scopes is not authorised setting pipeline error"); SetPipelineError(context, new UnauthorisedError( - $"{context.HttpContext.User.Identity.Name} unable to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}")); + $"{context.HttpContext.User.Identity.Name} unable to access {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}")); } } @@ -70,19 +70,19 @@ if (IsAuthorised(authorised)) { - Logger.LogInformation($"{context.HttpContext.User.Identity.Name} has succesfully been authorised for {context.DownstreamReRoute.UpstreamPathTemplate.Value}."); + Logger.LogInformation($"{context.HttpContext.User.Identity.Name} has succesfully been authorised for {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}."); await _next.Invoke(context); } else { - Logger.LogWarning($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}. Setting pipeline error"); + Logger.LogWarning($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}. Setting pipeline error"); - SetPipelineError(context, new UnauthorisedError($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}")); + SetPipelineError(context, new UnauthorisedError($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}")); } } else { - Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised"); + Logger.LogInformation($"{context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value} route does not require user to be authorised"); await _next.Invoke(context); } } diff --git a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs index b782ab0f..2ed2ef44 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs @@ -11,7 +11,6 @@ namespace Ocelot.Configuration.Builder private AuthenticationOptions _authenticationOptions; private string _loadBalancerKey; private string _downstreamPathTemplate; - private string _upstreamTemplate; private UpstreamPathTemplate _upstreamTemplatePattern; private List _upstreamHttpMethod; private bool _isAuthenticated; @@ -79,12 +78,6 @@ namespace Ocelot.Configuration.Builder return this; } - public DownstreamReRouteBuilder WithUpstreamPathTemplate(string input) - { - _upstreamTemplate = input; - return this; - } - public DownstreamReRouteBuilder WithUpstreamTemplatePattern(UpstreamPathTemplate input) { _upstreamTemplatePattern = input; @@ -245,7 +238,7 @@ namespace Ocelot.Configuration.Builder { return new DownstreamReRoute( _key, - new PathTemplate(_upstreamTemplate), + _upstreamTemplatePattern, _upstreamHeaderFindAndReplace, _downstreamHeaderFindAndReplace, _downstreamAddresses, @@ -267,7 +260,7 @@ namespace Ocelot.Configuration.Builder _isAuthenticated, _isAuthorised, _authenticationOptions, - new PathTemplate(_downstreamPathTemplate), + new DownstreamPathTemplate(_downstreamPathTemplate), _loadBalancerKey, _delegatingHandlers, _addHeadersToDownstream, diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 31367f26..b31a14ff 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -1,15 +1,12 @@ -using System.Collections.Generic; -using System.Net.Http; -using Ocelot.Values; -using System.Linq; -using Ocelot.Configuration.Creator; -using System; - -namespace Ocelot.Configuration.Builder +namespace Ocelot.Configuration.Builder { + using System.Collections.Generic; + using System.Net.Http; + using Ocelot.Values; + using System.Linq; + public class ReRouteBuilder { - private string _upstreamTemplate; private UpstreamPathTemplate _upstreamTemplatePattern; private List _upstreamHttpMethod; private string _upstreamHost; @@ -39,12 +36,6 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithUpstreamPathTemplate(string input) - { - _upstreamTemplate = input; - return this; - } - public ReRouteBuilder WithUpstreamTemplatePattern(UpstreamPathTemplate input) { _upstreamTemplatePattern = input; @@ -67,7 +58,6 @@ namespace Ocelot.Configuration.Builder { return new ReRoute( _downstreamReRoutes, - new PathTemplate(_upstreamTemplate), _upstreamHttpMethod, _upstreamTemplatePattern, _upstreamHost, diff --git a/src/Ocelot/Configuration/Builder/UpstreamPathTemplateBuilder.cs b/src/Ocelot/Configuration/Builder/UpstreamPathTemplateBuilder.cs new file mode 100644 index 00000000..21b21302 --- /dev/null +++ b/src/Ocelot/Configuration/Builder/UpstreamPathTemplateBuilder.cs @@ -0,0 +1,41 @@ +namespace Ocelot.Configuration.Builder +{ + using Values; + + public class UpstreamPathTemplateBuilder + { + private string _template; + private int _priority; + private bool _containsQueryString; + private string _originalValue; + + public UpstreamPathTemplateBuilder WithTemplate(string template) + { + _template = template; + return this; + } + + public UpstreamPathTemplateBuilder WithPriority(int priority) + { + _priority = priority; + return this; + } + + public UpstreamPathTemplateBuilder WithContainsQueryString(bool containsQueryString) + { + _containsQueryString = containsQueryString; + return this; + } + + public UpstreamPathTemplateBuilder WithOriginalValue(string originalValue) + { + _originalValue = originalValue; + return this; + } + + public UpstreamPathTemplate Build() + { + return new UpstreamPathTemplate(_template, _priority, _containsQueryString, _originalValue); + } + } +} diff --git a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs index 6aea0702..51dad93d 100644 --- a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs @@ -166,7 +166,6 @@ namespace Ocelot.Configuration.Creator var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(aggregateReRoute); var reRoute = new ReRouteBuilder() - .WithUpstreamPathTemplate(aggregateReRoute.UpstreamPathTemplate) .WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod) .WithUpstreamTemplatePattern(upstreamTemplatePattern) .WithDownstreamReRoutes(applicableReRoutes) @@ -182,7 +181,6 @@ namespace Ocelot.Configuration.Creator var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); var reRoute = new ReRouteBuilder() - .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) .WithUpstreamTemplatePattern(upstreamTemplatePattern) .WithDownstreamReRoute(downstreamReRoutes) @@ -229,7 +227,6 @@ namespace Ocelot.Configuration.Creator var reRoute = new DownstreamReRouteBuilder() .WithKey(fileReRoute.Key) .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) - .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) .WithUpstreamTemplatePattern(upstreamTemplatePattern) .WithIsAuthenticated(fileReRouteOptions.IsAuthenticated) diff --git a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs index fbf62d4a..b5acc10f 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs @@ -31,7 +31,7 @@ namespace Ocelot.Configuration.Creator //hack to handle /{url} case if(ForwardSlashAndOnePlaceHolder(upstreamTemplate, placeholders, postitionOfPlaceHolderClosingBracket)) { - return new UpstreamPathTemplate(RegExForwardSlashAndOnePlaceHolder, 0, false); + return new UpstreamPathTemplate(RegExForwardSlashAndOnePlaceHolder, 0, false, reRoute.UpstreamPathTemplate); } } } @@ -60,7 +60,7 @@ namespace Ocelot.Configuration.Creator if (upstreamTemplate == "/") { - return new UpstreamPathTemplate(RegExForwardSlashOnly, reRoute.Priority, containsQueryString); + return new UpstreamPathTemplate(RegExForwardSlashOnly, reRoute.Priority, containsQueryString, reRoute.UpstreamPathTemplate); } if(upstreamTemplate.EndsWith("/")) @@ -72,7 +72,7 @@ namespace Ocelot.Configuration.Creator ? $"^{upstreamTemplate}{RegExMatchEndString}" : $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; - return new UpstreamPathTemplate(route, reRoute.Priority, containsQueryString); + return new UpstreamPathTemplate(route, reRoute.Priority, containsQueryString, reRoute.UpstreamPathTemplate); } private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List placeholders, int postitionOfPlaceHolderClosingBracket) diff --git a/src/Ocelot/Configuration/DownstreamReRoute.cs b/src/Ocelot/Configuration/DownstreamReRoute.cs index f6cc876d..7e3c707c 100644 --- a/src/Ocelot/Configuration/DownstreamReRoute.cs +++ b/src/Ocelot/Configuration/DownstreamReRoute.cs @@ -8,7 +8,7 @@ namespace Ocelot.Configuration { public DownstreamReRoute( string key, - PathTemplate upstreamPathTemplate, + UpstreamPathTemplate upstreamPathTemplate, List upstreamHeadersFindAndReplace, List downstreamHeadersFindAndReplace, List downstreamAddresses, @@ -30,7 +30,7 @@ namespace Ocelot.Configuration bool isAuthenticated, bool isAuthorised, AuthenticationOptions authenticationOptions, - PathTemplate downstreamPathTemplate, + DownstreamPathTemplate downstreamDownstreamPathTemplate, string loadBalancerKey, List delegatingHandlers, List addHeadersToDownstream, @@ -63,13 +63,13 @@ namespace Ocelot.Configuration IsAuthenticated = isAuthenticated; IsAuthorised = isAuthorised; AuthenticationOptions = authenticationOptions; - DownstreamPathTemplate = downstreamPathTemplate; + DownstreamDownstreamPathTemplate = downstreamDownstreamPathTemplate; LoadBalancerKey = loadBalancerKey; AddHeadersToUpstream = addHeadersToUpstream; } public string Key { get; } - public PathTemplate UpstreamPathTemplate { get; } + public UpstreamPathTemplate UpstreamPathTemplate { get; } public List UpstreamHeadersFindAndReplace { get; } public List DownstreamHeadersFindAndReplace { get; } public List DownstreamAddresses { get; } @@ -91,7 +91,7 @@ namespace Ocelot.Configuration public bool IsAuthenticated { get; } public bool IsAuthorised { get; } public AuthenticationOptions AuthenticationOptions { get; } - public PathTemplate DownstreamPathTemplate { get; } + public DownstreamPathTemplate DownstreamDownstreamPathTemplate { get; } public string LoadBalancerKey { get; } public List DelegatingHandlers { get; } public List AddHeadersToDownstream { get; } diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index c4c3952d..f1faf975 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -1,15 +1,12 @@ -using System.Collections.Generic; -using System.Net.Http; -using Ocelot.Configuration.Creator; -using Ocelot.Requester.QoS; -using Ocelot.Values; - -namespace Ocelot.Configuration +namespace Ocelot.Configuration { + using System.Collections.Generic; + using System.Net.Http; + using Ocelot.Values; + public class ReRoute { public ReRoute(List downstreamReRoute, - PathTemplate upstreamPathTemplate, List upstreamHttpMethod, UpstreamPathTemplate upstreamTemplatePattern, string upstreamHost, @@ -17,13 +14,11 @@ namespace Ocelot.Configuration { UpstreamHost = upstreamHost; DownstreamReRoute = downstreamReRoute; - UpstreamPathTemplate = upstreamPathTemplate; UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; Aggregator = aggregator; } - public PathTemplate UpstreamPathTemplate { get; private set; } public UpstreamPathTemplate UpstreamTemplatePattern { get; private set; } public List UpstreamHttpMethod { get; private set; } public string UpstreamHost { get; private set; } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 7c7264de..d3114020 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -55,7 +55,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder private DownstreamRoute GetPlaceholderNamesAndValues(string path, string query, ReRoute reRoute) { - var templatePlaceholderNameAndValues = _placeholderNameAndValueFinder.Find(path, query, reRoute.UpstreamPathTemplate.Value); + var templatePlaceholderNameAndValues = _placeholderNameAndValueFinder.Find(path, query, reRoute.UpstreamTemplatePattern.OriginalValue); return new DownstreamRoute(templatePlaceholderNameAndValues.Data, reRoute); } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs index 080172d5..319e9191 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs @@ -10,7 +10,7 @@ public class DownstreamRouteProviderFactory : IDownstreamRouteProviderFactory { private readonly Dictionary _providers; - private IOcelotLogger _logger; + private readonly IOcelotLogger _logger; public DownstreamRouteProviderFactory(IServiceProvider provider, IOcelotLoggerFactory factory) { @@ -22,7 +22,7 @@ { //todo - this is a bit hacky we are saying there are no reRoutes or there are reRoutes but none of them have //an upstream path template which means they are dyanmic and service discovery is on... - if((!config.ReRoutes.Any() || config.ReRoutes.All(x => string.IsNullOrEmpty(x.UpstreamPathTemplate.Value))) && IsServiceDiscovery(config.ServiceProviderConfiguration)) + if((!config.ReRoutes.Any() || config.ReRoutes.All(x => string.IsNullOrEmpty(x.UpstreamTemplatePattern?.OriginalValue))) && IsServiceDiscovery(config.ServiceProviderConfiguration)) { _logger.LogInformation($"Selected {nameof(DownstreamRouteCreator)} as DownstreamRouteProvider for this request"); return _providers[nameof(DownstreamRouteCreator)]; diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index 3916721d..cf777056 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -51,7 +51,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware return; } - var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value)); + var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamDownstreamPathTemplate.Value)); Logger.LogDebug($"downstream templates are {downstreamPathTemplates}"); diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs index d1706118..ad1117af 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs @@ -14,7 +14,7 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher return regex.IsMatch(upstreamUrlPath) ? new OkResponse(new UrlMatch(true)) : new OkResponse(new UrlMatch(false)); - } + } return regex.IsMatch($"{upstreamUrlPath}{upstreamQueryString}") ? new OkResponse(new UrlMatch(true)) diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 02b40c07..e8efc60d 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -27,7 +27,7 @@ namespace Ocelot.DownstreamUrlCreator.Middleware public async Task Invoke(DownstreamContext context) { var response = _replacer - .Replace(context.DownstreamReRoute.DownstreamPathTemplate, context.TemplatePlaceholderNameAndValues); + .Replace(context.DownstreamReRoute.DownstreamDownstreamPathTemplate, context.TemplatePlaceholderNameAndValues); if (response.IsError) { diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamTemplatePathPlaceholderReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamTemplatePathPlaceholderReplacer.cs index 1b744819..8597a1a6 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamTemplatePathPlaceholderReplacer.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamTemplatePathPlaceholderReplacer.cs @@ -8,11 +8,11 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer { public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer { - public Response Replace(PathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues) + public Response Replace(DownstreamPathTemplate downstreamDownstreamPathTemplate, List urlPathPlaceholderNameAndValues) { var downstreamPath = new StringBuilder(); - downstreamPath.Append(downstreamPathTemplate.Value); + downstreamPath.Append(downstreamDownstreamPathTemplate.Value); foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues) { @@ -22,4 +22,4 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer return new OkResponse(new DownstreamPath(downstreamPath.ToString())); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamPathPlaceholderReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamPathPlaceholderReplacer.cs index 46e998d4..8455d048 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamPathPlaceholderReplacer.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamPathPlaceholderReplacer.cs @@ -7,6 +7,6 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer { public interface IDownstreamPathPlaceholderReplacer { - Response Replace(PathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues); + Response Replace(DownstreamPathTemplate downstreamDownstreamPathTemplate, List urlPathPlaceholderNameAndValues); } -} \ No newline at end of file +} diff --git a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs index ba340b3a..12332cb2 100644 --- a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs @@ -26,7 +26,7 @@ namespace Ocelot.Headers.Middleware { if (context.DownstreamReRoute.ClaimsToHeaders.Any()) { - Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to headers"); + Logger.LogInformation($"{context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value} has instructions to convert claims to headers"); var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.ClaimsToHeaders, context.HttpContext.User.Claims, context.DownstreamRequest); diff --git a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs index bc886c5d..d5ad5002 100644 --- a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs +++ b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs @@ -26,7 +26,7 @@ namespace Ocelot.QueryStrings.Middleware { if (context.DownstreamReRoute.ClaimsToQueries.Any()) { - Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to queries"); + Logger.LogInformation($"{context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value} has instructions to convert claims to queries"); var response = _addQueriesToRequest.SetQueriesOnDownstreamRequest(context.DownstreamReRoute.ClaimsToQueries, context.HttpContext.User.Claims, context.DownstreamRequest); diff --git a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs index 50eb7776..fea51733 100644 --- a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs +++ b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs @@ -34,7 +34,7 @@ namespace Ocelot.RateLimit.Middleware // check if rate limiting is enabled if (!context.DownstreamReRoute.EnableEndpointEndpointRateLimiting) { - Logger.LogInformation($"EndpointRateLimiting is not enabled for {context.DownstreamReRoute.DownstreamPathTemplate.Value}"); + Logger.LogInformation($"EndpointRateLimiting is not enabled for {context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value}"); await _next.Invoke(context); return; } @@ -45,7 +45,7 @@ namespace Ocelot.RateLimit.Middleware // check white list if (IsWhitelisted(identity, options)) { - Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} is white listed from rate limiting"); + Logger.LogInformation($"{context.DownstreamReRoute.DownstreamDownstreamPathTemplate.Value} is white listed from rate limiting"); await _next.Invoke(context); return; } @@ -112,7 +112,7 @@ namespace Ocelot.RateLimit.Middleware public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule, DownstreamReRoute downstreamReRoute) { Logger.LogInformation( - $"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { downstreamReRoute.UpstreamPathTemplate.Value }, TraceIdentifier {httpContext.TraceIdentifier}."); + $"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { downstreamReRoute.UpstreamPathTemplate.OriginalValue }, TraceIdentifier {httpContext.TraceIdentifier}."); } public virtual Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitOptions option, string retryAfter) diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index ec44e375..651cb2d3 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -13,7 +13,7 @@ namespace Ocelot.Requester private readonly IDelegatingHandlerHandlerFactory _factory; private readonly IHttpClientCache _cacheHandlers; private readonly IOcelotLogger _logger; - private string _cacheKey; + private DownstreamReRoute _cacheKey; private HttpClient _httpClient; private IHttpClient _client; private readonly TimeSpan _defaultTimeout; @@ -34,7 +34,7 @@ namespace Ocelot.Requester public IHttpClient Create(DownstreamContext context) { - _cacheKey = GetCacheKey(context); + _cacheKey = context.DownstreamReRoute; var httpClient = _cacheHandlers.Get(_cacheKey); @@ -51,7 +51,7 @@ namespace Ocelot.Requester handler.ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) => true; _logger - .LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {context.DownstreamReRoute.DownstreamPathTemplate}"); + .LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamDownstreamPathTemplate: {context.DownstreamReRoute.DownstreamDownstreamPathTemplate}"); } var timeout = context.DownstreamReRoute.QosOptions.TimeoutValue == 0 @@ -127,14 +127,5 @@ namespace Ocelot.Requester }); return httpMessageHandler; } - - private string GetCacheKey(DownstreamContext request) - { - var cacheKey = $"{request.DownstreamRequest.Method}:{request.DownstreamRequest.OriginalString}"; - - _logger.LogDebug($"Cache key for request is {cacheKey}"); - - return cacheKey; - } } } diff --git a/src/Ocelot/Requester/IHttpClientCache.cs b/src/Ocelot/Requester/IHttpClientCache.cs index 8aaeeaab..c9e90f04 100644 --- a/src/Ocelot/Requester/IHttpClientCache.cs +++ b/src/Ocelot/Requester/IHttpClientCache.cs @@ -1,10 +1,11 @@ namespace Ocelot.Requester { using System; + using Configuration; public interface IHttpClientCache { - IHttpClient Get(string key); - void Set(string key, IHttpClient handler, TimeSpan expirationTime); + IHttpClient Get(DownstreamReRoute key); + void Set(DownstreamReRoute key, IHttpClient handler, TimeSpan expirationTime); } } diff --git a/src/Ocelot/Requester/MemoryHttpClientCache.cs b/src/Ocelot/Requester/MemoryHttpClientCache.cs index 58a6c166..b2fcc2a0 100644 --- a/src/Ocelot/Requester/MemoryHttpClientCache.cs +++ b/src/Ocelot/Requester/MemoryHttpClientCache.cs @@ -2,22 +2,23 @@ { using System; using System.Collections.Concurrent; + using Configuration; public class MemoryHttpClientCache : IHttpClientCache { - private readonly ConcurrentDictionary _httpClientsCache; + private readonly ConcurrentDictionary _httpClientsCache; public MemoryHttpClientCache() { - _httpClientsCache = new ConcurrentDictionary(); + _httpClientsCache = new ConcurrentDictionary(); } - public void Set(string key, IHttpClient client, TimeSpan expirationTime) + public void Set(DownstreamReRoute key, IHttpClient client, TimeSpan expirationTime) { _httpClientsCache.AddOrUpdate(key, client, (k, oldValue) => client); } - public IHttpClient Get(string key) + public IHttpClient Get(DownstreamReRoute key) { //todo handle error? return _httpClientsCache.TryGetValue(key, out var client) ? client : null; diff --git a/src/Ocelot/Requester/QoS/QosFactory.cs b/src/Ocelot/Requester/QoS/QosFactory.cs index 37211f4b..8916d6f2 100644 --- a/src/Ocelot/Requester/QoS/QosFactory.cs +++ b/src/Ocelot/Requester/QoS/QosFactory.cs @@ -27,7 +27,7 @@ namespace Ocelot.Requester.QoS return new OkResponse(handler(request, _ocelotLoggerFactory)); } - return new ErrorResponse(new UnableToFindQoSProviderError($"could not find qosProvider for {request.DownstreamScheme}{request.DownstreamAddresses}{request.DownstreamPathTemplate}")); + return new ErrorResponse(new UnableToFindQoSProviderError($"could not find qosProvider for {request.DownstreamScheme}{request.DownstreamAddresses}{request.DownstreamDownstreamPathTemplate}")); } } } diff --git a/src/Ocelot/Values/PathTemplate.cs b/src/Ocelot/Values/DownstreamPathTemplate.cs similarity index 58% rename from src/Ocelot/Values/PathTemplate.cs rename to src/Ocelot/Values/DownstreamPathTemplate.cs index 584f80ac..de2a30b8 100644 --- a/src/Ocelot/Values/PathTemplate.cs +++ b/src/Ocelot/Values/DownstreamPathTemplate.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Values -{ - public class PathTemplate - { - public PathTemplate(string value) - { - Value = value; - } - - public string Value { get; } - } -} +namespace Ocelot.Values +{ + public class DownstreamPathTemplate + { + public DownstreamPathTemplate(string value) + { + Value = value; + } + + public string Value { get; } + } +} diff --git a/src/Ocelot/Values/UpstreamPathTemplate.cs b/src/Ocelot/Values/UpstreamPathTemplate.cs index b42c887e..082b6a83 100644 --- a/src/Ocelot/Values/UpstreamPathTemplate.cs +++ b/src/Ocelot/Values/UpstreamPathTemplate.cs @@ -2,16 +2,20 @@ namespace Ocelot.Values { public class UpstreamPathTemplate { - public UpstreamPathTemplate(string template, int priority, bool containsQueryString) + public UpstreamPathTemplate(string template, int priority, bool containsQueryString, string originalValue) { Template = template; Priority = priority; ContainsQueryString = containsQueryString; + OriginalValue = originalValue; } public string Template { get; } - public int Priority { get; } + public int Priority { get; } + public bool ContainsQueryString { get; } + + public string OriginalValue { get; } } } diff --git a/test/Ocelot.AcceptanceTests/HttpClientCachingTests.cs b/test/Ocelot.AcceptanceTests/HttpClientCachingTests.cs new file mode 100644 index 00000000..85b17daf --- /dev/null +++ b/test/Ocelot.AcceptanceTests/HttpClientCachingTests.cs @@ -0,0 +1,163 @@ +namespace Ocelot.AcceptanceTests +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Net; + using Configuration; + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.File; + using Requester; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class HttpClientCachingTests : IDisposable + { + private readonly Steps _steps; + private string _downstreamPath; + private readonly ServiceHandler _serviceHandler; + + public HttpClientCachingTests() + { + _serviceHandler = new ServiceHandler(); + _steps = new Steps(); + } + + [Fact] + public void should_cache_one_http_client_same_re_route() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + var cache = new FakeHttpClientCache(); + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningWithFakeHttpClientCache(cache)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(x => cache.Count.ShouldBe(1)) + .BDDfy(); + } + + [Fact] + public void should_cache_two_http_client_different_re_route() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + }, + new FileReRoute + { + DownstreamPathTemplate = "/two", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/two", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + var cache = new FakeHttpClientCache(); + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningWithFakeHttpClientCache(cache)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/two")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/two")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/two")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(x => cache.Count.ShouldBe(2)) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, int statusCode, string responseBody) + { + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + } + + public void Dispose() + { + _serviceHandler.Dispose(); + _steps.Dispose(); + } + + public class FakeHttpClientCache : IHttpClientCache + { + private readonly ConcurrentDictionary _httpClientsCache; + + public FakeHttpClientCache() + { + _httpClientsCache = new ConcurrentDictionary(); + } + + public void Set(DownstreamReRoute key, IHttpClient client, TimeSpan expirationTime) + { + _httpClientsCache.AddOrUpdate(key, client, (k, oldValue) => client); + } + + public IHttpClient Get(DownstreamReRoute key) + { + //todo handle error? + return _httpClientsCache.TryGetValue(key, out var client) ? client : null; + } + + public int Count => _httpClientsCache.Count; + } + } +} diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 7841083b..274f1554 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -29,6 +29,7 @@ using static Ocelot.Infrastructure.Wait; using Configuration.Repository; using Ocelot.Configuration.Creator; + using Requester; using CookieHeaderValue = System.Net.Http.Headers.CookieHeaderValue; using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue; @@ -182,6 +183,35 @@ _ocelotClient = _ocelotServer.CreateClient(); } + public void GivenOcelotIsRunningWithFakeHttpClientCache(IHttpClientCache cache) + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", false, false); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(cache); + s.AddOcelot(); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + internal void GivenIWait(int wait) { Thread.Sleep(wait); diff --git a/test/Ocelot.Benchmarks/DictionaryBenchmarks.cs b/test/Ocelot.Benchmarks/DictionaryBenchmarks.cs new file mode 100644 index 00000000..05d5b6ba --- /dev/null +++ b/test/Ocelot.Benchmarks/DictionaryBenchmarks.cs @@ -0,0 +1,77 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using System.Net.Http; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Validators; + +namespace Ocelot.Benchmarks +{ + using System.Collections.Concurrent; + using Configuration; + using Configuration.Builder; + using Requester; + + [Config(typeof(DictionaryBenchmarks))] + public class DictionaryBenchmarks : ManualConfig + { + private ConcurrentDictionary _downstreamReRouteDictionary; + private ConcurrentDictionary _stringReRouteDictionary; + private HttpClientWrapper _client; + private string _stringKey; + private DownstreamReRoute _downstreamReRouteKey; + + public DictionaryBenchmarks() + { + Add(StatisticColumn.AllStatistics); + Add(MemoryDiagnoser.Default); + Add(BaselineValidator.FailOnError); + } + + [GlobalSetup] + public void SetUp() + { + _downstreamReRouteKey = new DownstreamReRouteBuilder().Build(); + _stringKey = "test"; + _client = new HttpClientWrapper(new HttpClient()); + _downstreamReRouteDictionary = new ConcurrentDictionary(); + + _downstreamReRouteDictionary.TryAdd(new DownstreamReRouteBuilder().Build(), new HttpClientWrapper(new HttpClient())); + _downstreamReRouteDictionary.TryAdd(new DownstreamReRouteBuilder().Build(), new HttpClientWrapper(new HttpClient())); + _downstreamReRouteDictionary.TryAdd(new DownstreamReRouteBuilder().Build(), new HttpClientWrapper(new HttpClient())); + _downstreamReRouteDictionary.TryAdd(new DownstreamReRouteBuilder().Build(), new HttpClientWrapper(new HttpClient())); + _downstreamReRouteDictionary.TryAdd(new DownstreamReRouteBuilder().Build(), new HttpClientWrapper(new HttpClient())); + _downstreamReRouteDictionary.TryAdd(new DownstreamReRouteBuilder().Build(), new HttpClientWrapper(new HttpClient())); + _downstreamReRouteDictionary.TryAdd(new DownstreamReRouteBuilder().Build(), new HttpClientWrapper(new HttpClient())); + _downstreamReRouteDictionary.TryAdd(new DownstreamReRouteBuilder().Build(), new HttpClientWrapper(new HttpClient())); + _downstreamReRouteDictionary.TryAdd(new DownstreamReRouteBuilder().Build(), new HttpClientWrapper(new HttpClient())); + _downstreamReRouteDictionary.TryAdd(new DownstreamReRouteBuilder().Build(), new HttpClientWrapper(new HttpClient())); + + _stringReRouteDictionary = new ConcurrentDictionary(); + _stringReRouteDictionary.TryAdd("1", new HttpClientWrapper(new HttpClient())); + _stringReRouteDictionary.TryAdd("2", new HttpClientWrapper(new HttpClient())); + _stringReRouteDictionary.TryAdd("3", new HttpClientWrapper(new HttpClient())); + _stringReRouteDictionary.TryAdd("4", new HttpClientWrapper(new HttpClient())); + _stringReRouteDictionary.TryAdd("5", new HttpClientWrapper(new HttpClient())); + _stringReRouteDictionary.TryAdd("6", new HttpClientWrapper(new HttpClient())); + _stringReRouteDictionary.TryAdd("7", new HttpClientWrapper(new HttpClient())); + _stringReRouteDictionary.TryAdd("8", new HttpClientWrapper(new HttpClient())); + _stringReRouteDictionary.TryAdd("9", new HttpClientWrapper(new HttpClient())); + _stringReRouteDictionary.TryAdd("10", new HttpClientWrapper(new HttpClient())); + } + + [Benchmark(Baseline = true)] + public IHttpClient StringKey() + { + _stringReRouteDictionary.AddOrUpdate(_stringKey, _client, (k, oldValue) => _client); + return _stringReRouteDictionary.TryGetValue(_stringKey, out var client) ? client : null; + } + + [Benchmark] + public IHttpClient DownstreamReRouteKey() + { + _downstreamReRouteDictionary.AddOrUpdate(_downstreamReRouteKey, _client, (k, oldValue) => _client); + return _downstreamReRouteDictionary.TryGetValue(_downstreamReRouteKey, out var client) ? client : null; + } + } +} diff --git a/test/Ocelot.Benchmarks/Program.cs b/test/Ocelot.Benchmarks/Program.cs index 8601eeaa..d830a46b 100644 --- a/test/Ocelot.Benchmarks/Program.cs +++ b/test/Ocelot.Benchmarks/Program.cs @@ -7,6 +7,7 @@ namespace Ocelot.Benchmarks public static void Main(string[] args) { var switcher = new BenchmarkSwitcher(new[] { + typeof(DictionaryBenchmarks), typeof(UrlPathToUrlPathTemplateMatcherBenchmarks), typeof(AllTheThingsBenchmarks), typeof(ExceptionHandlerMiddlewareBenchmarks) diff --git a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs index 472c8643..15e86230 100644 --- a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs @@ -16,6 +16,7 @@ namespace Ocelot.UnitTests.Authorization using Xunit; using Microsoft.AspNetCore.Http; using Ocelot.Configuration; + using Values; public class AuthorisationMiddlewareTests { @@ -44,6 +45,7 @@ namespace Ocelot.UnitTests.Authorization { this.Given(x => x.GivenTheDownStreamRouteIs(new List(), new DownstreamReRouteBuilder() + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().Build()) .WithIsAuthorised(true) .WithUpstreamHttpMethod(new List { "Get" }) .Build())) diff --git a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs index f545e04c..7f041d7b 100644 --- a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs @@ -13,7 +13,6 @@ using Shouldly; using TestStack.BDDfy; using Xunit; - using Ocelot.DependencyInjection; using Ocelot.Errors; using Ocelot.UnitTests.TestData; using Ocelot.Values; @@ -83,9 +82,9 @@ .Build(); var downstreamReRoute = new DownstreamReRouteBuilder() + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithDownstreamAddresses(new List() { new DownstreamHostAndPort("127.0.0.1", 80) }) .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List { "Get" }) .WithLoadBalancerKey("CookieStickySessions:sessionid") .Build(); @@ -116,6 +115,7 @@ }, })) .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheUpstreamTemplatePatternCreatorReturns("woop", "/api/products/{productId}")) .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) @@ -124,7 +124,7 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build() })) @@ -206,7 +206,7 @@ var lauraReRoute = new ReRouteBuilder() .WithUpstreamHttpMethod(new List() { "Get" }) .WithUpstreamHost("localhost") - .WithUpstreamPathTemplate("/laura") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/laura").Build()) .WithDownstreamReRoute(lauraDownstreamReRoute) .Build(); @@ -225,14 +225,14 @@ var tomReRoute = new ReRouteBuilder() .WithUpstreamHttpMethod(new List() { "Get" }) .WithUpstreamHost("localhost") - .WithUpstreamPathTemplate("/tom") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/tom").Build()) .WithDownstreamReRoute(tomDownstreamReRoute) .Build(); expected.Add(tomReRoute); var aggregateReReRoute = new ReRouteBuilder() - .WithUpstreamPathTemplate("/") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/").Build()) .WithUpstreamHost("localhost") .WithDownstreamReRoute(lauraDownstreamReRoute) .WithDownstreamReRoute(tomDownstreamReRoute) @@ -241,7 +241,18 @@ expected.Add(aggregateReReRoute); + var tupleList = new List<(string, string)> + { + ("woop", "/laura"), + ("woop", "/laura"), + ("woop", "/tom"), + ("woop", "/tom"), + ("woop", "/"), + ("woop", "/") + }; + this.Given(x => x.GivenTheConfigIs(configuration)) + .And(x => GivenTheUpstreamTemplatePatternCreatorReturns(tupleList.ToArray())) .And(x => x.GivenTheFollowingOptionsAreReturned(new ReRouteOptionsBuilder().Build())) .And(x => x.GivenTheFollowingIsReturned(serviceProviderConfig)) .And(x => GivenTheDownstreamAddresses()) @@ -249,7 +260,7 @@ .And(x => x.GivenTheConfigIsValid()) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheServiceProviderCreatorIsCalledCorrectly()) - .Then(x => x.ThenTheReRoutesAre(expected)) + .Then(x => x.ThenTheAggregateReRoutesAre(expected)) .BDDfy(); } @@ -406,9 +417,9 @@ .Build(); var downstreamReRoute = new DownstreamReRouteBuilder() + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithDownstreamAddresses(new List() {new DownstreamHostAndPort("127.0.0.1", 80)}) .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List {"Get"}) .WithLoadBalancerKey("/api/products/{productId}|Get|127.0.0.1:0") .Build(); @@ -433,6 +444,7 @@ }, })) .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheUpstreamTemplatePatternCreatorReturns("woop", "/api/products/{productId}")) .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) @@ -441,7 +453,7 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build() })) @@ -459,7 +471,7 @@ var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamScheme("https") .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List {"Get"}) .WithDelegatingHandlers(handlers) .WithLoadBalancerKey("/api/products/{productId}|Get|") @@ -480,6 +492,7 @@ }, })) .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheUpstreamTemplatePatternCreatorReturns("woop", "/api/products/{productId}")) .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) @@ -488,7 +501,7 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build() })) @@ -503,7 +516,7 @@ var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List {"Get"}) .WithUseServiceDiscovery(true) .WithServiceName("ProductService") @@ -532,6 +545,7 @@ } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheUpstreamTemplatePatternCreatorReturns("woop", "/api/products/{productId}")) .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) @@ -540,7 +554,7 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build() })) @@ -555,7 +569,7 @@ var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List {"Get"}) .WithUseServiceDiscovery(false) .WithLoadBalancerKey("/api/products/{productId}|Get|") @@ -575,6 +589,7 @@ } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheUpstreamTemplatePatternCreatorReturns("woop", "/api/products/{productId}")) .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) @@ -583,7 +598,7 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build() })) @@ -598,9 +613,9 @@ var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List {"Get"}) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1, false, "/api/products/{productId}")) .WithLoadBalancerKey("/api/products/{productId}|Get|") .Build(); @@ -621,15 +636,15 @@ .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) - .And(x => x.GivenTheUpstreamTemplatePatternCreatorReturns("(?i)/api/products/.*/$")) + .And(x => x.GivenTheUpstreamTemplatePatternCreatorReturns("(?i)/api/products/.*/$", "/api/products/{productId}")) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1, false, "/api/products/{productId}")) .Build() })) .BDDfy(); @@ -643,7 +658,7 @@ var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List {"Get"}) .WithRequestIdKey("blahhhh") .WithLoadBalancerKey("/api/products/{productId}|Get|") @@ -667,6 +682,7 @@ } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheUpstreamTemplatePatternCreatorReturns("woop", "/api/products/{productId}")) .And(x => GivenTheDownstreamAddresses()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) @@ -676,7 +692,7 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build() })) @@ -734,7 +750,7 @@ var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List {"Get"}) .WithAuthenticationOptions(authenticationOptions) .WithClaimsToHeaders(new List @@ -748,12 +764,13 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build() }; this.Given(x => x.GivenTheConfigIs(fileConfig)) + .And(x => GivenTheUpstreamTemplatePatternCreatorReturns("woop", "/api/products/{productId}")) .And(x => GivenTheDownstreamAddresses()) .And(x => x.GivenTheConfigIsValid()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) @@ -781,7 +798,7 @@ var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List {"Get"}) .WithAuthenticationOptions(authenticationOptions) .WithLoadBalancerKey("/api/products/{productId}|Get|") @@ -791,12 +808,13 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build() }; this.Given(x => x.GivenTheConfigIs(fileConfig)) + .And(x => GivenTheUpstreamTemplatePatternCreatorReturns("woop", "/api/products/{productId}")) .And(x => GivenTheDownstreamAddresses()) .And(x => x.GivenTheConfigIsValid()) .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) @@ -828,11 +846,6 @@ var reRouteOptions = new ReRouteOptionsBuilder() .Build(); - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithEnableRateLimiting(true) - .WithRateLimitOptions(new RateLimitOptionsBuilder().Build()) - .Build(); - var rateLimitOptions = new RateLimitOptionsBuilder() .WithRateLimitRule(new RateLimitRule("1s", 1, 1)) .Build(); @@ -923,6 +936,31 @@ dynamic.RateLimitOptions.RateLimitRule.PeriodTimespan.ShouldBe(rateLimitOptions.PeriodTimespan); } + private void ThenTheAggregateReRoutesAre(List expectedReRoutes) + { + for (int i = 0; i < _config.Data.ReRoutes.Count; i++) + { + var result = _config.Data.ReRoutes[i]; + var expected = expectedReRoutes[i]; + + result.DownstreamReRoute.Count.ShouldBe(expected.DownstreamReRoute.Count); + + result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); + result.UpstreamTemplatePattern.OriginalValue.ShouldBe(expected.UpstreamTemplatePattern.OriginalValue); + result.UpstreamTemplatePattern.Template.ShouldBe(expected.UpstreamTemplatePattern.Template); + + result.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe(expected.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value); + 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].LoadBalancerKey.ShouldBe(expected.DownstreamReRoute[0].LoadBalancerKey); + result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers); + result.DownstreamReRoute[0].AddHeadersToDownstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToDownstream); + result.DownstreamReRoute[0].AddHeadersToUpstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToUpstream, "AddHeadersToUpstream should be set"); + } + } + private void ThenTheReRoutesAre(List expectedReRoutes) { for (int i = 0; i < _config.Data.ReRoutes.Count; i++) @@ -932,10 +970,11 @@ result.DownstreamReRoute.Count.ShouldBe(expected.DownstreamReRoute.Count); - result.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamReRoute[0].DownstreamPathTemplate.Value); result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); - result.UpstreamPathTemplate.Value.ShouldBe(expected.UpstreamPathTemplate.Value); - result.UpstreamTemplatePattern?.Template.ShouldBe(expected.UpstreamTemplatePattern?.Template); + result.UpstreamTemplatePattern.OriginalValue.ShouldBe(expected.UpstreamTemplatePattern.OriginalValue); + result.UpstreamTemplatePattern.Template.ShouldBe(expected.UpstreamTemplatePattern.Template); + + result.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe(expected.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value); 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); @@ -977,11 +1016,22 @@ .Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once); } - private void GivenTheUpstreamTemplatePatternCreatorReturns(string pattern) + private void GivenTheUpstreamTemplatePatternCreatorReturns(string pattern, string original) { _upstreamTemplatePatternCreator .Setup(x => x.Create(It.IsAny())) - .Returns(new UpstreamPathTemplate(pattern, 1, false)); + .Returns(new UpstreamPathTemplate(pattern, 1, false, original)); + } + + private void GivenTheUpstreamTemplatePatternCreatorReturns(params (string pattern, string original)[] list) + { + var builder = _upstreamTemplatePatternCreator + .SetupSequence(x => x.Create(It.IsAny())); + + foreach (var p in list) + { + builder.Returns(new UpstreamPathTemplate(p.pattern, 1, false, p.original)); + } } private void ThenTheRequestIdKeyCreatorIsCalledCorrectly() diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index dfa6362f..9c802961 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -42,7 +42,7 @@ namespace Ocelot.UnitTests.Configuration private void ThenTheConfigurationIsReturned() { - _getResult.Data.ReRoutes[0].DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("initial"); + _getResult.Data.ReRoutes[0].DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe("initial"); } private void WhenIGetTheConfiguration() diff --git a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs index 43176e5a..112b7e0b 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs @@ -85,7 +85,7 @@ new FileReRoute { DownstreamScheme = "DownstreamScheme", - DownstreamPathTemplate = "DownstreamPathTemplate", + DownstreamPathTemplate = "DownstreamDownstreamPathTemplate", Key = "Key", UpstreamHost = "UpstreamHost", UpstreamHttpMethod = new List diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs index a1c4b799..d946046b 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs @@ -211,7 +211,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void ThenTheDownstreamRouteIsCreated() { - _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test"); + _result.Data.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe("/test"); _result.Data.ReRoute.UpstreamHttpMethod[0].ShouldBe(HttpMethod.Get); _result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth"); _result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET"); @@ -226,21 +226,21 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void ThenTheDownstreamPathIsForwardSlash() { - _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/"); + _result.Data.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe("/"); _result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth"); _result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/|GET"); } private void ThenThePathDoesNotHaveTrailingSlash() { - _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test"); + _result.Data.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe("/test"); _result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth"); _result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET"); } private void ThenTheQueryStringIsRemoved() { - _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test"); + _result.Data.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe("/test"); _result.Data.ReRoute.DownstreamReRoute[0].ServiceName.ShouldBe("auth"); _result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerKey.ShouldBe("/auth/test|GET"); } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 360d58f4..d640c959 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -47,24 +47,20 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig)) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) @@ -74,13 +70,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .Build()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .Build() ))) .BDDfy(); @@ -99,24 +93,20 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig)) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) @@ -126,10 +116,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .WithUpstreamHttpMethod(new List { "Post" }) .Build()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .WithUpstreamHttpMethod(new List { "Post" }) .Build() ))) @@ -150,13 +140,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig )) @@ -170,10 +158,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -194,13 +182,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig )) @@ -214,10 +200,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly("matchInUrlMatcher")) @@ -239,13 +225,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig )) @@ -258,10 +242,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() ))) .BDDfy(); @@ -282,24 +266,20 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPathForAPost") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig )) @@ -312,10 +292,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPathForAPost") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build() ))) .BDDfy(); @@ -332,13 +312,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("somPath") - .WithUpstreamPathTemplate("somePath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamPathTemplate("somePath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1, false, "someUpstreamPath")) .Build(), }, string.Empty, serviceProviderConfig )) @@ -366,13 +344,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig )) @@ -385,10 +361,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build() ))) .BDDfy(); @@ -409,13 +385,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig )) @@ -428,10 +402,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build() ))) .BDDfy(); @@ -452,13 +426,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig )) @@ -485,14 +457,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -507,10 +477,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -532,13 +502,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig )) @@ -552,10 +520,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -575,27 +543,23 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { }) // empty list of methods - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { }) // empty list of methods - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -621,14 +585,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -654,14 +616,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -688,25 +648,21 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("THENULLPATH") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build()) - .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -721,10 +677,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "test")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "test")) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(2)) @@ -756,25 +712,25 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void ThenTheUrlMatcherIsCalledCorrectly() { _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamPathTemplate.Value, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Once); + .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern.Template, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Once); } private void ThenTheUrlMatcherIsCalledCorrectly(int times) { _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamPathTemplate.Value, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Exactly(times)); + .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern.OriginalValue, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Exactly(times)); } private void ThenTheUrlMatcherIsCalledCorrectly(string expectedUpstreamUrlPath) { _mockMatcher - .Verify(x => x.Match(expectedUpstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamPathTemplate.Value, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Once); + .Verify(x => x.Match(expectedUpstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern.OriginalValue, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Once); } private void ThenTheUrlMatcherIsNotCalled() { _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamPathTemplate.Value, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Never); + .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern.OriginalValue, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Never); } private void GivenTheUrlMatcherReturns(Response match) @@ -803,7 +759,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void ThenTheFollowingIsReturned(DownstreamRoute expected) { - _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe(expected.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value); + _result.Data.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value.ShouldBe(expected.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate.Value); _result.Data.ReRoute.UpstreamTemplatePattern.Priority.ShouldBe(expected.ReRoute.UpstreamTemplatePattern.Priority); for (int i = 0; i < _result.Data.TemplatePlaceholderNameAndValues.Count; i++) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs index 0380f2fc..7f59c208 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs @@ -5,11 +5,8 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Responses; - using Ocelot.Values; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -30,8 +27,8 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); var provider = services.BuildServiceProvider(); _logger = new Mock(); _loggerFactory = new Mock(); @@ -49,7 +46,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder this.Given(_ => GivenTheReRoutes(reRoutes)) .When(_ => WhenIGet()) - .Then(_ => ThenTheResultShouldBe()) + .Then(_ => ThenTheResultShouldBe()) .BDDfy(); } @@ -59,12 +56,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder var spConfig = new ServiceProviderConfigurationBuilder().WithHost("test").WithPort(50).WithType("test").Build(); var reRoutes = new List { - new ReRouteBuilder().WithUpstreamPathTemplate("woot").Build() + new ReRouteBuilder().WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("woot").Build()).Build() }; this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) .When(_ => WhenIGet()) - .Then(_ => ThenTheResultShouldBe()) + .Then(_ => ThenTheResultShouldBe()) .BDDfy(); } @@ -76,7 +73,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) .When(_ => WhenIGet()) - .Then(_ => ThenTheResultShouldBe()) + .Then(_ => ThenTheResultShouldBe()) .BDDfy(); } @@ -88,7 +85,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) .When(_ => WhenIGet()) - .Then(_ => ThenTheResultShouldBe()) + .Then(_ => ThenTheResultShouldBe()) .BDDfy(); } @@ -100,7 +97,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) .When(_ => WhenIGet()) - .Then(_ => ThenTheResultShouldBe()) + .Then(_ => ThenTheResultShouldBe()) .BDDfy(); } @@ -112,7 +109,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) .When(_ => WhenIGet()) - .Then(_ => ThenTheResultShouldBe()) + .Then(_ => ThenTheResultShouldBe()) .BDDfy(); } @@ -127,7 +124,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) .When(_ => WhenIGet()) - .Then(_ => ThenTheResultShouldBe()) + .Then(_ => ThenTheResultShouldBe()) .BDDfy(); } diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index ca190240..a022efe2 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -296,7 +296,7 @@ .WithDownstreamPathTemplate("/Authorized/{action}?server={server}") .WithUpstreamHttpMethod(new List { "Post", "Get" }) .WithDownstreamScheme("http") - .WithUpstreamPathTemplate("/uc/Authorized/{server}/{action}") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("/uc/Authorized/{server}/{action}").Build()) .Build(); var config = new ServiceProviderConfigurationBuilder() @@ -350,7 +350,7 @@ { _downstreamPath = new OkResponse(new DownstreamPath(path)); _downstreamUrlTemplateVariableReplacer - .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) + .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) .Returns(_downstreamPath); } diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs index cd8cf3c8..3b72db1b 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs @@ -199,7 +199,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer private void WhenIReplaceTheTemplateVariables() { - _result = _downstreamPathReplacer.Replace(_downstreamRoute.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate, _downstreamRoute.TemplatePlaceholderNameAndValues); + _result = _downstreamPathReplacer.Replace(_downstreamRoute.ReRoute.DownstreamReRoute[0].DownstreamDownstreamPathTemplate, _downstreamRoute.TemplatePlaceholderNameAndValues); } private void ThenTheDownstreamUrlPathIsReturned(string expected) diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index 7d3cac1a..a8737a52 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -49,15 +49,22 @@ namespace Ocelot.UnitTests.RateLimit [Fact] public void should_call_middleware_and_ratelimiting() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(new DownstreamReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( - new RateLimitOptions(true, "ClientId", new List(), false, "", "", new RateLimitRule("1s", 100, 3), 429)) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); + { + var upstreamTemplate = new UpstreamPathTemplateBuilder().Build(); + + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithEnableRateLimiting(true) + .WithRateLimitOptions(new RateLimitOptions(true, "ClientId", new List(), false, "", "", new RateLimitRule("1s", 100, 3), 429)) + .WithUpstreamHttpMethod(new List {"Get"}) + .WithUpstreamTemplatePattern(upstreamTemplate) + .Build(); + + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List {"Get"}) + .Build(); + + var downstreamRoute = new DownstreamRoute(new List(), reRoute); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) .When(x => x.WhenICallTheMiddlewareMultipleTime(2)) diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index 1b0d17eb..4c671653 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -54,6 +54,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); @@ -74,6 +75,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); @@ -89,6 +91,68 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } + [Fact] + public void should_get_from_cache_with_different_query_string() + { + var qosOptions = new QoSOptionsBuilder() + .Build(); + + var reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) + .WithLoadBalancerKey("") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) + .WithQosOptions(new QoSOptionsBuilder().Build()) + .Build(); + + this.Given(x => GivenARealCache()) + .And(x => GivenTheFactoryReturns()) + .And(x => GivenARequest(reRoute, "http://wwww.someawesomewebsite.com/woot?badman=1")) + .And(x => WhenIBuildTheFirstTime()) + .And(x => WhenISave()) + .And(x => WhenIBuildAgain()) + .And(x => GivenARequest(reRoute, "http://wwww.someawesomewebsite.com/woot?badman=2")) + .And(x => WhenISave()) + .When(x => WhenIBuildAgain()) + .Then(x => ThenTheHttpClientIsFromTheCache()) + .BDDfy(); + } + + [Fact] + public void should_not_get_from_cache_with_different_query_string() + { + var qosOptions = new QoSOptionsBuilder() + .Build(); + + var reRouteA = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) + .WithLoadBalancerKey("") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithContainsQueryString(true).WithOriginalValue("").Build()) + .WithQosOptions(new QoSOptionsBuilder().Build()) + .Build(); + + var reRouteB = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) + .WithLoadBalancerKey("") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithContainsQueryString(true).WithOriginalValue("").Build()) + .WithQosOptions(new QoSOptionsBuilder().Build()) + .Build(); + + this.Given(x => GivenARealCache()) + .And(x => GivenTheFactoryReturns()) + .And(x => GivenARequest(reRouteA, "http://wwww.someawesomewebsite.com/woot?badman=1")) + .And(x => WhenIBuildTheFirstTime()) + .And(x => WhenISave()) + .And(x => WhenIBuildAgain()) + .And(x => GivenARequest(reRouteB, "http://wwww.someawesomewebsite.com/woot?badman=2")) + .And(x => WhenISave()) + .When(x => WhenIBuildAgain()) + .Then(x => ThenTheHttpClientIsNotFromTheCache()) + .BDDfy(); + } + [Fact] public void should_log_if_ignoring_ssl_errors() { @@ -99,6 +163,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) .WithQosOptions(new QoSOptionsBuilder().Build()) .WithDangerousAcceptAnyServerCertificateValidator(true) .Build(); @@ -121,6 +186,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); @@ -152,6 +218,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, true, false, true)) .WithLoadBalancerKey("") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); @@ -176,9 +243,9 @@ namespace Ocelot.UnitTests.Requester [InlineData("PATCH")] public void should_add_verb_to_cache_key(string verb) { - var client = "http://localhost:5012"; + var downstreamUrl = "http://localhost:5012/"; - HttpMethod method = new HttpMethod(verb); + var method = new HttpMethod(verb); var qosOptions = new QoSOptionsBuilder() .Build(); @@ -187,14 +254,15 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") + .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); this.Given(_ => GivenADownstreamService()) - .And(_ => GivenARequestWithAUrlAndMethod(reRoute, client, method)) + .And(_ => GivenARequestWithAUrlAndMethod(reRoute, downstreamUrl, method)) .And(_ => GivenTheFactoryReturnsNothing()) .And(_ => WhenIBuild()) - .And(_ => GivenCacheIsCalledWithExpectedKey($"{method.ToString()}:{client}")) + .And(_ => GivenCacheIsCalledWithExpectedKey($"{method.ToString()}:{downstreamUrl}")) .BDDfy(); } @@ -209,6 +277,11 @@ namespace Ocelot.UnitTests.Requester _againHttpClient.ShouldBe(_firstHttpClient); } + private void ThenTheHttpClientIsNotFromTheCache() + { + _againHttpClient.ShouldNotBe(_firstHttpClient); + } + private void WhenISave() { _builder.Save(); @@ -216,17 +289,17 @@ namespace Ocelot.UnitTests.Requester private void GivenCacheIsCalledWithExpectedKey(string expectedKey) { - _cacheHandlers.Verify(x => x.Get(It.Is(p => p.Equals(expectedKey, StringComparison.OrdinalIgnoreCase))), Times.Once); + _cacheHandlers.Verify(x => x.Get(It.IsAny()), Times.Once); } private void ThenTheDangerousAcceptAnyServerCertificateValidatorWarningIsLogged() { - _logger.Verify(x => x.LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {_context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {_context.DownstreamReRoute.DownstreamPathTemplate}"), Times.Once); + _logger.Verify(x => x.LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {_context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamDownstreamPathTemplate: {_context.DownstreamReRoute.DownstreamDownstreamPathTemplate}"), Times.Once); } private void GivenTheClientIsCached() { - _cacheHandlers.Setup(x => x.Get(It.IsAny())).Returns(_httpClient); + _cacheHandlers.Setup(x => x.Get(It.IsAny())).Returns(_httpClient); } private void ThenTheCookieIsSet() @@ -287,7 +360,12 @@ namespace Ocelot.UnitTests.Requester private void GivenARequest(DownstreamReRoute downstream) { - GivenARequestWithAUrlAndMethod(downstream, "http://localhost:5003", HttpMethod.Get); + GivenARequest(downstream, "http://localhost:5003"); + } + + private void GivenARequest(DownstreamReRoute downstream, string downstreamUrl) + { + GivenARequestWithAUrlAndMethod(downstream, downstreamUrl, HttpMethod.Get); } private void GivenARequestWithAUrlAndMethod(DownstreamReRoute downstream, string url, HttpMethod method) diff --git a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs index a6701035..7099b10f 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs @@ -50,6 +50,8 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_call_request_correctly() { + var upstreamTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue("").Build(); + var qosOptions = new QoSOptionsBuilder() .Build(); @@ -57,6 +59,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") + .WithUpstreamTemplatePattern(upstreamTemplate) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); @@ -76,6 +79,8 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_call_request_unable_to_complete_request() { + var upstreamTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue("").Build(); + var qosOptions = new QoSOptionsBuilder() .Build(); @@ -83,6 +88,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") + .WithUpstreamTemplatePattern(upstreamTemplate) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); @@ -101,6 +107,8 @@ namespace Ocelot.UnitTests.Requester [Fact] public void http_client_request_times_out() { + var upstreamTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue("").Build(); + var qosOptions = new QoSOptionsBuilder() .Build(); @@ -108,6 +116,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") + .WithUpstreamTemplatePattern(upstreamTemplate) .WithQosOptions(new QoSOptionsBuilder().WithTimeoutValue(1).Build()) .Build(); @@ -132,7 +141,7 @@ namespace Ocelot.UnitTests.Requester private void WhenIGetResponse() { - _response = _httpClientRequester.GetResponse(_request).Result; + _response = _httpClientRequester.GetResponse(_request).GetAwaiter().GetResult(); } private void ThenTheResponseIsCalledCorrectly() From 6198404697cbdb69a9a4464518ccdc5674a89114 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Mon, 3 Sep 2018 07:58:22 +0100 Subject: [PATCH 49/50] ##591 added addanddelete method back to cache as Ocelot.Provider.Consul uses it...sigh at me (#592) --- src/Ocelot/Cache/IOcelotCache.cs | 23 ++++++++++--------- src/Ocelot/Cache/InMemoryCache.cs | 10 ++++++++ .../Cache/InMemoryCacheTests.cs | 12 ++++++++++ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/Ocelot/Cache/IOcelotCache.cs b/src/Ocelot/Cache/IOcelotCache.cs index c2054f6e..04ac2b51 100644 --- a/src/Ocelot/Cache/IOcelotCache.cs +++ b/src/Ocelot/Cache/IOcelotCache.cs @@ -1,11 +1,12 @@ -using System; - -namespace Ocelot.Cache -{ - public interface IOcelotCache - { - void Add(string key, T value, TimeSpan ttl, string region); - T Get(string key, string region); - void ClearRegion(string region); - } -} +using System; + +namespace Ocelot.Cache +{ + public interface IOcelotCache + { + void Add(string key, T value, TimeSpan ttl, string region); + T Get(string key, string region); + void ClearRegion(string region); + void AddAndDelete(string key, T value, TimeSpan ttl, string region); + } +} diff --git a/src/Ocelot/Cache/InMemoryCache.cs b/src/Ocelot/Cache/InMemoryCache.cs index fe1c8791..64c1dbc5 100644 --- a/src/Ocelot/Cache/InMemoryCache.cs +++ b/src/Ocelot/Cache/InMemoryCache.cs @@ -39,6 +39,16 @@ } } + public void AddAndDelete(string key, T value, TimeSpan ttl, string region) + { + if (_cache.ContainsKey(key)) + { + _cache.Remove(key); + } + + Add(key, value, ttl, region); + } + public void ClearRegion(string region) { if (_regions.ContainsKey(region)) diff --git a/test/Ocelot.UnitTests/Cache/InMemoryCacheTests.cs b/test/Ocelot.UnitTests/Cache/InMemoryCacheTests.cs index 8b2e58b9..9c17ba3c 100644 --- a/test/Ocelot.UnitTests/Cache/InMemoryCacheTests.cs +++ b/test/Ocelot.UnitTests/Cache/InMemoryCacheTests.cs @@ -25,6 +25,18 @@ fake.Value.ShouldBe(1); } + [Fact] + public void should_add_and_delete() + { + var fake = new Fake(1); + _cache.Add("1", fake, TimeSpan.FromSeconds(100), "region"); + var newFake = new Fake(1); + _cache.AddAndDelete("1", newFake, TimeSpan.FromSeconds(100), "region"); + var result = _cache.Get("1", "region"); + result.ShouldBe(newFake); + newFake.Value.ShouldBe(1); + } + [Fact] public void should_clear_region() { From ef6db657b75150d97a715a536f13bc3186f9e056 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Mon, 3 Sep 2018 21:31:57 +0100 Subject: [PATCH 50/50] #593 instanciate upstream path template when dynamic routing for logging (#594) --- .../Builder/DownstreamReRouteBuilder.cs | 2 +- .../Configuration/Builder/ReRouteBuilder.cs | 2 +- .../FileInternalConfigurationCreator.cs | 6 +- .../Finder/DownstreamRouteCreator.cs | 6 +- .../AuthorisationMiddlewareTests.cs | 2 +- .../FileInternalConfigurationCreatorTests.cs | 46 +++---- .../DownstreamRouteCreatorTests.cs | 3 + .../DownstreamRouteFinderTests.cs | 128 +++++++++--------- .../DownstreamRouteProviderFactoryTests.cs | 2 +- .../DownstreamUrlCreatorMiddlewareTests.cs | 2 +- .../ClientRateLimitMiddlewareTests.cs | 2 +- .../Requester/HttpClientBuilderTests.cs | 18 +-- .../Requester/HttpClientHttpRequesterTest.cs | 6 +- 13 files changed, 116 insertions(+), 109 deletions(-) diff --git a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs index 2ed2ef44..cbe2e910 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs @@ -78,7 +78,7 @@ namespace Ocelot.Configuration.Builder return this; } - public DownstreamReRouteBuilder WithUpstreamTemplatePattern(UpstreamPathTemplate input) + public DownstreamReRouteBuilder WithUpstreamPathTemplate(UpstreamPathTemplate input) { _upstreamTemplatePattern = input; return this; diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index b31a14ff..824fe09d 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -36,7 +36,7 @@ return this; } - public ReRouteBuilder WithUpstreamTemplatePattern(UpstreamPathTemplate input) + public ReRouteBuilder WithUpstreamPathTemplate(UpstreamPathTemplate input) { _upstreamTemplatePattern = input; return this; diff --git a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs index 51dad93d..1acbe0fb 100644 --- a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs @@ -167,7 +167,7 @@ namespace Ocelot.Configuration.Creator var reRoute = new ReRouteBuilder() .WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod) - .WithUpstreamTemplatePattern(upstreamTemplatePattern) + .WithUpstreamPathTemplate(upstreamTemplatePattern) .WithDownstreamReRoutes(applicableReRoutes) .WithUpstreamHost(aggregateReRoute.UpstreamHost) .WithAggregator(aggregateReRoute.Aggregator) @@ -182,7 +182,7 @@ namespace Ocelot.Configuration.Creator var reRoute = new ReRouteBuilder() .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) - .WithUpstreamTemplatePattern(upstreamTemplatePattern) + .WithUpstreamPathTemplate(upstreamTemplatePattern) .WithDownstreamReRoute(downstreamReRoutes) .WithUpstreamHost(fileReRoute.UpstreamHost) .Build(); @@ -228,7 +228,7 @@ namespace Ocelot.Configuration.Creator .WithKey(fileReRoute.Key) .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) - .WithUpstreamTemplatePattern(upstreamTemplatePattern) + .WithUpstreamPathTemplate(upstreamTemplatePattern) .WithIsAuthenticated(fileReRouteOptions.IsAuthenticated) .WithAuthenticationOptions(authOptionsForRoute) .WithClaimsToHeaders(claimsToHeaders) diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs index f7dea902..f2c8628b 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs @@ -44,6 +44,8 @@ var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new []{ upstreamHttpMethod }); + var upstreamPathTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue(upstreamUrlPath).Build(); + var downstreamReRouteBuilder = new DownstreamReRouteBuilder() .WithServiceName(serviceName) .WithLoadBalancerKey(loadBalancerKey) @@ -52,7 +54,8 @@ .WithHttpHandlerOptions(configuration.HttpHandlerOptions) .WithQosOptions(qosOptions) .WithDownstreamScheme(configuration.DownstreamScheme) - .WithLoadBalancerOptions(configuration.LoadBalancerOptions); + .WithLoadBalancerOptions(configuration.LoadBalancerOptions) + .WithUpstreamPathTemplate(upstreamPathTemplate); var rateLimitOptions = configuration.ReRoutes != null ? configuration.ReRoutes @@ -72,6 +75,7 @@ var reRoute = new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) .WithUpstreamHttpMethod(new List(){ upstreamHttpMethod }) + .WithUpstreamPathTemplate(upstreamPathTemplate) .Build(); downstreamRoute = new OkResponse(new DownstreamRoute(new List(), reRoute)); diff --git a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs index 15e86230..a2c2fadd 100644 --- a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs @@ -45,7 +45,7 @@ namespace Ocelot.UnitTests.Authorization { this.Given(x => x.GivenTheDownStreamRouteIs(new List(), new DownstreamReRouteBuilder() - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().Build()) .WithIsAuthorised(true) .WithUpstreamHttpMethod(new List { "Get" }) .Build())) diff --git a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs index 7f041d7b..ba4e1497 100644 --- a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs @@ -82,7 +82,7 @@ .Build(); var downstreamReRoute = new DownstreamReRouteBuilder() - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithDownstreamAddresses(new List() { new DownstreamHostAndPort("127.0.0.1", 80) }) .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamHttpMethod(new List { "Get" }) @@ -124,7 +124,7 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build() })) @@ -206,7 +206,7 @@ var lauraReRoute = new ReRouteBuilder() .WithUpstreamHttpMethod(new List() { "Get" }) .WithUpstreamHost("localhost") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/laura").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/laura").Build()) .WithDownstreamReRoute(lauraDownstreamReRoute) .Build(); @@ -225,14 +225,14 @@ var tomReRoute = new ReRouteBuilder() .WithUpstreamHttpMethod(new List() { "Get" }) .WithUpstreamHost("localhost") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/tom").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/tom").Build()) .WithDownstreamReRoute(tomDownstreamReRoute) .Build(); expected.Add(tomReRoute); var aggregateReReRoute = new ReRouteBuilder() - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/").Build()) .WithUpstreamHost("localhost") .WithDownstreamReRoute(lauraDownstreamReRoute) .WithDownstreamReRoute(tomDownstreamReRoute) @@ -417,7 +417,7 @@ .Build(); var downstreamReRoute = new DownstreamReRouteBuilder() - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithDownstreamAddresses(new List() {new DownstreamHostAndPort("127.0.0.1", 80)}) .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamHttpMethod(new List {"Get"}) @@ -453,7 +453,7 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build() })) @@ -471,7 +471,7 @@ var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamScheme("https") .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List {"Get"}) .WithDelegatingHandlers(handlers) .WithLoadBalancerKey("/api/products/{productId}|Get|") @@ -501,7 +501,7 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build() })) @@ -516,7 +516,7 @@ var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List {"Get"}) .WithUseServiceDiscovery(true) .WithServiceName("ProductService") @@ -554,7 +554,7 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build() })) @@ -569,7 +569,7 @@ var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List {"Get"}) .WithUseServiceDiscovery(false) .WithLoadBalancerKey("/api/products/{productId}|Get|") @@ -598,7 +598,7 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build() })) @@ -613,9 +613,9 @@ var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List {"Get"}) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1, false, "/api/products/{productId}")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1, false, "/api/products/{productId}")) .WithLoadBalancerKey("/api/products/{productId}|Get|") .Build(); @@ -642,9 +642,9 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1, false, "/api/products/{productId}")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1, false, "/api/products/{productId}")) .Build() })) .BDDfy(); @@ -658,7 +658,7 @@ var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List {"Get"}) .WithRequestIdKey("blahhhh") .WithLoadBalancerKey("/api/products/{productId}|Get|") @@ -692,7 +692,7 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build() })) @@ -750,7 +750,7 @@ var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List {"Get"}) .WithAuthenticationOptions(authenticationOptions) .WithClaimsToHeaders(new List @@ -764,7 +764,7 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build() }; @@ -798,7 +798,7 @@ var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List {"Get"}) .WithAuthenticationOptions(authenticationOptions) .WithLoadBalancerKey("/api/products/{productId}|Get|") @@ -808,7 +808,7 @@ { new ReRouteBuilder() .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithTemplate("woop").WithOriginalValue("/api/products/{productId}").Build()) .WithUpstreamHttpMethod(new List { "Get" }) .Build() }; diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs index d946046b..336bc108 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs @@ -222,6 +222,9 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _result.Data.ReRoute.DownstreamReRoute[0].LoadBalancerOptions.Type.ShouldBe(nameof(NoLoadBalancer)); _result.Data.ReRoute.DownstreamReRoute[0].HttpHandlerOptions.ShouldBe(_handlerOptions); _result.Data.ReRoute.DownstreamReRoute[0].QosOptions.ShouldBe(_qoSOptions); + _result.Data.ReRoute.UpstreamTemplatePattern.ShouldNotBeNull(); + _result.Data.ReRoute.DownstreamReRoute[0].UpstreamPathTemplate.ShouldNotBeNull(); + } private void ThenTheDownstreamPathIsForwardSlash() diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index d640c959..5f15b136 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -48,19 +48,19 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig)) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) @@ -70,10 +70,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .WithUpstreamHttpMethod(new List { "Post" }) .Build()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .WithUpstreamHttpMethod(new List { "Post" }) .Build() ))) @@ -94,19 +94,19 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 0, false, "someUpstreamPath")) .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig)) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) @@ -116,10 +116,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .WithUpstreamHttpMethod(new List { "Post" }) .Build()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("test", 1, false, "someUpstreamPath")) .WithUpstreamHttpMethod(new List { "Post" }) .Build() ))) @@ -141,10 +141,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig )) @@ -158,10 +158,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -183,10 +183,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig )) @@ -200,10 +200,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly("matchInUrlMatcher")) @@ -226,10 +226,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig )) @@ -242,10 +242,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() ))) .BDDfy(); @@ -267,19 +267,19 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPathForAPost") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig )) @@ -292,10 +292,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPathForAPost") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build() ))) .BDDfy(); @@ -313,10 +313,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("somPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("somePath", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("somePath", 1, false, "someUpstreamPath")) .Build(), }, string.Empty, serviceProviderConfig )) @@ -345,10 +345,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get", "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig )) @@ -361,10 +361,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build() ))) .BDDfy(); @@ -386,10 +386,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig )) @@ -402,10 +402,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build() ))) .BDDfy(); @@ -427,10 +427,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig )) @@ -458,11 +458,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -477,10 +477,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -503,10 +503,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() }, string.Empty, serviceProviderConfig )) @@ -520,10 +520,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -544,22 +544,22 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { }) // empty list of methods - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamHttpMethod(new List { }) // empty list of methods - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -586,11 +586,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -617,11 +617,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -649,20 +649,20 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("THENULLPATH") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "someUpstreamPath")) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -677,10 +677,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "test")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "test")) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false, "test")) + .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "test")) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(2)) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs index 7f59c208..6b00c280 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs @@ -56,7 +56,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder var spConfig = new ServiceProviderConfigurationBuilder().WithHost("test").WithPort(50).WithType("test").Build(); var reRoutes = new List { - new ReRouteBuilder().WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("woot").Build()).Build() + new ReRouteBuilder().WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("woot").Build()).Build() }; this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index a022efe2..014326f4 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -296,7 +296,7 @@ .WithDownstreamPathTemplate("/Authorized/{action}?server={server}") .WithUpstreamHttpMethod(new List { "Post", "Get" }) .WithDownstreamScheme("http") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("/uc/Authorized/{server}/{action}").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("/uc/Authorized/{server}/{action}").Build()) .Build(); var config = new ServiceProviderConfigurationBuilder() diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index a8737a52..dd2d3c91 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -56,7 +56,7 @@ namespace Ocelot.UnitTests.RateLimit .WithEnableRateLimiting(true) .WithRateLimitOptions(new RateLimitOptions(true, "ClientId", new List(), false, "", "", new RateLimitRule("1s", 100, 3), 429)) .WithUpstreamHttpMethod(new List {"Get"}) - .WithUpstreamTemplatePattern(upstreamTemplate) + .WithUpstreamPathTemplate(upstreamTemplate) .Build(); var reRoute = new ReRouteBuilder() diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index 4c671653..9ff6ef10 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -54,7 +54,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); @@ -75,7 +75,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); @@ -101,7 +101,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); @@ -128,7 +128,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithContainsQueryString(true).WithOriginalValue("").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithContainsQueryString(true).WithOriginalValue("").Build()) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); @@ -136,7 +136,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithContainsQueryString(true).WithOriginalValue("").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithContainsQueryString(true).WithOriginalValue("").Build()) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); @@ -163,7 +163,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) .WithQosOptions(new QoSOptionsBuilder().Build()) .WithDangerousAcceptAnyServerCertificateValidator(true) .Build(); @@ -186,7 +186,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); @@ -218,7 +218,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, true, false, true)) .WithLoadBalancerKey("") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); @@ -254,7 +254,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") - .WithUpstreamTemplatePattern(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); diff --git a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs index 7099b10f..17b7f93a 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs @@ -59,7 +59,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") - .WithUpstreamTemplatePattern(upstreamTemplate) + .WithUpstreamPathTemplate(upstreamTemplate) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); @@ -88,7 +88,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") - .WithUpstreamTemplatePattern(upstreamTemplate) + .WithUpstreamPathTemplate(upstreamTemplate) .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); @@ -116,7 +116,7 @@ namespace Ocelot.UnitTests.Requester .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) .WithLoadBalancerKey("") - .WithUpstreamTemplatePattern(upstreamTemplate) + .WithUpstreamPathTemplate(upstreamTemplate) .WithQosOptions(new QoSOptionsBuilder().WithTimeoutValue(1).Build()) .Build();