From 0f7aaa097d027aeda2998f5be39ead7c227621b4 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Wed, 27 Jun 2018 18:15:04 +0100 Subject: [PATCH 01/16] dont use dynamic routing unless service discovery provider explictly set and log that we are going to use dynamic info (#437) * #428 dont use dynamic routing unless service discovery provider explictly set and log that we are going to use dynamic info * #428 fixed tests that were failing due to not maintaining backwards compat with config for service discovery --- docs/features/servicediscovery.rst | 7 +- .../ServiceProviderConfigurationCreator.cs | 7 +- .../Finder/DownstreamRouteProviderFactory.cs | 8 +- test/Ocelot.ManualTest/Program.cs | 124 +++++++++--------- .../DownstreamRouteProviderFactoryTests.cs | 74 +++++++---- 5 files changed, 128 insertions(+), 92 deletions(-) diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index be519013..4fff6a3b 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -18,7 +18,8 @@ will be used. "ServiceDiscoveryProvider": { "Host": "localhost", - "Port": 8500 + "Port": 8500, + "Type": "Consul" } In the future we can add a feature that allows ReRoute specfic configuration. @@ -136,8 +137,8 @@ The config might look something like "RequestIdKey": null, "ServiceDiscoveryProvider": { "Host": "localhost", - "Port": 8510, - "Type": null, + "Port": 8500, + "Type": "Consul", "Token": null, "ConfigurationKey": null }, diff --git a/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs index 3fd07454..a236fb22 100644 --- a/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs @@ -8,13 +8,16 @@ namespace Ocelot.Configuration.Creator public ServiceProviderConfiguration Create(FileGlobalConfiguration globalConfiguration) { var port = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; - var host = globalConfiguration?.ServiceDiscoveryProvider?.Host ?? "consul"; + var host = globalConfiguration?.ServiceDiscoveryProvider?.Host ?? "localhost"; + var type = !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Type) + ? globalConfiguration?.ServiceDiscoveryProvider?.Type + : "consul"; var pollingInterval = globalConfiguration?.ServiceDiscoveryProvider?.PollingInterval ?? 0; return new ServiceProviderConfigurationBuilder() .WithHost(host) .WithPort(port) - .WithType(globalConfiguration?.ServiceDiscoveryProvider?.Type) + .WithType(type) .WithToken(globalConfiguration?.ServiceDiscoveryProvider?.Token) .WithConfigurationKey(globalConfiguration?.ServiceDiscoveryProvider?.ConfigurationKey) .WithPollingInterval(pollingInterval) diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs index b615b613..fc74f679 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs @@ -5,13 +5,16 @@ using System.Linq; using Configuration; using Microsoft.Extensions.DependencyInjection; + using Ocelot.Logging; public class DownstreamRouteProviderFactory : IDownstreamRouteProviderFactory { private readonly Dictionary _providers; + private IOcelotLogger _logger; - public DownstreamRouteProviderFactory(IServiceProvider provider) + public DownstreamRouteProviderFactory(IServiceProvider provider, IOcelotLoggerFactory factory) { + _logger = factory.CreateLogger(); _providers = provider.GetServices().ToDictionary(x => x.GetType().Name); } @@ -19,6 +22,7 @@ { if(!config.ReRoutes.Any() && IsServiceDiscovery(config.ServiceProviderConfiguration)) { + _logger.LogInformation($"Selected {nameof(DownstreamRouteCreator)} as DownstreamRouteProvider for this request"); return _providers[nameof(DownstreamRouteCreator)]; } @@ -27,7 +31,7 @@ private bool IsServiceDiscovery(ServiceProviderConfiguration config) { - if(!string.IsNullOrEmpty(config?.Host) || config?.Port > 0) + if(!string.IsNullOrEmpty(config?.Host) && config?.Port > 0 && !string.IsNullOrEmpty(config.Type)) { return true; } diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index dcc7676d..a33395c1 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -1,61 +1,63 @@ -namespace Ocelot.ManualTest -{ - using System.IO; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.DependencyInjection; - using Ocelot.DependencyInjection; - using Ocelot.Middleware; - - public class Program - { - public static void Main(string[] args) - { - new WebHostBuilder() - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config - .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) - .AddJsonFile("appsettings.json", true, true) - .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("ocelot.json") - .AddEnvironmentVariables(); - }) - .ConfigureServices(s => { - s.AddAuthentication() - .AddJwtBearer("TestKey", x => - { - x.Authority = "test"; - x.Audience = "test"; - }); - - s.AddOcelot() - .AddCacheManager(x => - { - x.WithDictionaryHandle(); - }) - /*.AddOpenTracing(option => - { - option.CollectorUrl = "http://localhost:9618"; - option.Service = "Ocelot.ManualTest"; - })*/ - .AddAdministration("/administration", "secret"); - }) - .ConfigureLogging((hostingContext, logging) => - { - logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - logging.AddConsole(); - }) - .UseIISIntegration() - .Configure(app => - { - app.UseOcelot().Wait(); - }) - .Build() - .Run(); - } - } -} +namespace Ocelot.ManualTest +{ + using System.IO; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.DependencyInjection; + using Ocelot.Middleware; + using System; + using IdentityServer4.AccessTokenValidation; + + public class Program + { + public static void Main(string[] args) + { + new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddJsonFile("ocelot.json") + .AddEnvironmentVariables(); + }) + .ConfigureServices(s => { + s.AddAuthentication() + .AddJwtBearer("TestKey", x => + { + x.Authority = "test"; + x.Audience = "test"; + }); + + s.AddOcelot() + .AddCacheManager(x => + { + x.WithDictionaryHandle(); + }) + /*.AddOpenTracing(option => + { + option.CollectorUrl = "http://localhost:9618"; + option.Service = "Ocelot.ManualTest"; + })*/ + .AddAdministration("/administration", "secret"); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + }) + .UseIISIntegration() + .Configure(app => + { + app.UseOcelot().Wait(); + }) + .Build() + .Run(); + } + } +} diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs index 65ed4737..2b595660 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs @@ -1,26 +1,28 @@ -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -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; - namespace Ocelot.UnitTests.DownstreamRouteFinder { + using System.Collections.Generic; + using Microsoft.Extensions.DependencyInjection; + 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; using Ocelot.Configuration.Creator; + using Ocelot.Logging; public class DownstreamRouteProviderFactoryTests { private readonly DownstreamRouteProviderFactory _factory; private IInternalConfiguration _config; private IDownstreamRouteProvider _result; + private Mock _logger; + private Mock _loggerFactory; public DownstreamRouteProviderFactoryTests() { @@ -31,7 +33,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder services.AddSingleton(); services.AddSingleton(); var provider = services.BuildServiceProvider(); - _factory = new DownstreamRouteProviderFactory(provider); + _logger = new Mock(); + _loggerFactory = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _factory = new DownstreamRouteProviderFactory(provider, _loggerFactory.Object); } [Fact] @@ -49,12 +54,34 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder } [Fact] - public void should_return_downstream_route_finder_as_no_service_discovery() + public void should_return_downstream_route_finder_as_no_service_discovery_given_no_host() { - var spConfig = new ServiceProviderConfigurationBuilder().Build(); - var reRoutes = new List - { - }; + var spConfig = new ServiceProviderConfigurationBuilder().WithHost("").WithPort(50).Build(); + var reRoutes = new List(); + + this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheResultShouldBe()) + .BDDfy(); + } + + [Fact] + public void should_return_downstream_route_finder_given_no_service_discovery_port() + { + var spConfig = new ServiceProviderConfigurationBuilder().WithHost("localhost").WithPort(0).Build(); + var reRoutes = new List(); + + this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheResultShouldBe()) + .BDDfy(); + } + + [Fact] + public void should_return_downstream_route_finder_given_no_service_discovery_type() + { + var spConfig = new ServiceProviderConfigurationBuilder().WithHost("localhost").WithPort(50).WithType("").Build(); + var reRoutes = new List(); this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) .When(_ => WhenIGet()) @@ -65,10 +92,9 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder [Fact] public void should_return_downstream_route_creator() { - var spConfig = new ServiceProviderConfigurationBuilder().WithHost("test").WithPort(50).Build(); - var reRoutes = new List - { - }; + var spConfig = new ServiceProviderConfigurationBuilder().WithHost("test").WithPort(50).WithType("test").Build(); + var reRoutes = new List(); + this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) .When(_ => WhenIGet()) .Then(_ => ThenTheResultShouldBe()) From ddbfd44125041192aa99d47a8b3e79dc94955233 Mon Sep 17 00:00:00 2001 From: liuyuedeyv <286758941@qq.com> Date: Sat, 30 Jun 2018 02:02:35 +0800 Subject: [PATCH 02/16] Update HttpClientBuilder.cs (#442) if get the httpclient from cache,when the save method called ,it can not to save the right httpclient; --- src/Ocelot/Requester/HttpClientBuilder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index 0cd810cf..748c60c4 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -40,6 +40,7 @@ namespace Ocelot.Requester if (httpClient != null) { + _client = httpClient; return httpClient; } From a87bc92c60dd941bca6bdb41ec7e80f8562eb1f2 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 1 Jul 2018 08:56:06 +0100 Subject: [PATCH 03/16] Feature/test for #441 (#444) * #441 added a test for this change * #441 bit of tidying up and fixing of erros --- .../Finder/DownstreamRouteCreator.cs | 4 +- .../LoadBalancers/NoLoadBalancer.cs | 2 +- src/Ocelot/Raft/SqlLiteLog.cs | 15 ++++- src/Ocelot/Requester/IHttpClientCache.cs | 12 +--- src/Ocelot/Requester/MemoryHttpClientCache.cs | 40 ++++--------- .../OcelotPipelineExtensionsTests.cs | 1 - .../Requester/HttpClientBuilderTests.cs | 59 ++++++++++++++++++- ...lingConsulServiceDiscoveryProviderTests.cs | 39 +++++------- 8 files changed, 105 insertions(+), 67 deletions(-) diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs index 71940d18..b654d32d 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs @@ -61,8 +61,8 @@ downstreamRoute = new OkResponse(new DownstreamRoute(new List(), reRoute)); - _cache.AddOrUpdate(loadBalancerKey, downstreamRoute, (x, y) => downstreamRoute); - + _cache.AddOrUpdate(loadBalancerKey, downstreamRoute, (x, y) => downstreamRoute); + return downstreamRoute; } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs index 3f69aded..c24a50d2 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs @@ -20,7 +20,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers public async Task> Lease(DownstreamContext downstreamContext) { var services = await _services(); - //todo first or default could be null.. + if (services == null || services.Count == 0) { return new ErrorResponse(new ServicesAreEmptyError("There were no services in NoLoadBalancer")); diff --git a/src/Ocelot/Raft/SqlLiteLog.cs b/src/Ocelot/Raft/SqlLiteLog.cs index f4dfed49..99cd0308 100644 --- a/src/Ocelot/Raft/SqlLiteLog.cs +++ b/src/Ocelot/Raft/SqlLiteLog.cs @@ -99,6 +99,7 @@ namespace Ocelot.Raft } } } + _sempaphore.Release(); return result; } @@ -120,6 +121,7 @@ namespace Ocelot.Raft } } } + _sempaphore.Release(); return result; } @@ -135,6 +137,7 @@ namespace Ocelot.Raft 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}"); @@ -162,6 +165,7 @@ namespace Ocelot.Raft 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}"); @@ -188,6 +192,7 @@ namespace Ocelot.Raft } } } + _sempaphore.Release(); } @@ -197,6 +202,7 @@ namespace Ocelot.Raft 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)) @@ -227,6 +233,7 @@ namespace Ocelot.Raft 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)) @@ -251,6 +258,7 @@ namespace Ocelot.Raft 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)) @@ -267,10 +275,10 @@ namespace Ocelot.Raft }; var log = JsonConvert.DeserializeObject(data, jsonSerializerSettings); logsToReturn.Add((id, log)); - } } } + _sempaphore.Release(); return logsToReturn; } @@ -283,6 +291,7 @@ namespace Ocelot.Raft 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)) @@ -299,15 +308,18 @@ namespace Ocelot.Raft } } } + _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}"); @@ -316,6 +328,7 @@ namespace Ocelot.Raft var result = await deleteCommand.ExecuteNonQueryAsync(); } } + _sempaphore.Release(); } } diff --git a/src/Ocelot/Requester/IHttpClientCache.cs b/src/Ocelot/Requester/IHttpClientCache.cs index ce80dde3..2c4571ce 100644 --- a/src/Ocelot/Requester/IHttpClientCache.cs +++ b/src/Ocelot/Requester/IHttpClientCache.cs @@ -1,16 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Ocelot.Requester +namespace Ocelot.Requester { + using System; + public interface IHttpClientCache { - bool Exists(string id); IHttpClient Get(string id); - void Remove(string id); void Set(string id, IHttpClient handler, TimeSpan expirationTime); } } diff --git a/src/Ocelot/Requester/MemoryHttpClientCache.cs b/src/Ocelot/Requester/MemoryHttpClientCache.cs index e37a46a3..9e4059e5 100644 --- a/src/Ocelot/Requester/MemoryHttpClientCache.cs +++ b/src/Ocelot/Requester/MemoryHttpClientCache.cs @@ -1,21 +1,20 @@ -using Microsoft.Extensions.Caching.Memory; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Ocelot.Requester +namespace Ocelot.Requester { + using System; + using System.Collections.Concurrent; + public class MemoryHttpClientCache : IHttpClientCache { - private readonly ConcurrentDictionary> _httpClientsCache = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> _httpClientsCache; + + public MemoryHttpClientCache() + { + _httpClientsCache = new ConcurrentDictionary>(); + } public void Set(string id, IHttpClient client, TimeSpan expirationTime) { - ConcurrentQueue connectionQueue; - if (_httpClientsCache.TryGetValue(id, out connectionQueue)) + if (_httpClientsCache.TryGetValue(id, out var connectionQueue)) { connectionQueue.Enqueue(client); } @@ -27,28 +26,15 @@ namespace Ocelot.Requester } } - public bool Exists(string id) - { - ConcurrentQueue connectionQueue; - return _httpClientsCache.TryGetValue(id, out connectionQueue); - } - public IHttpClient Get(string id) { IHttpClient client= null; - ConcurrentQueue connectionQueue; - if (_httpClientsCache.TryGetValue(id, out connectionQueue)) + if (_httpClientsCache.TryGetValue(id, out var connectionQueue)) { connectionQueue.TryDequeue(out client); } return client; - } - - public void Remove(string id) - { - ConcurrentQueue connectionQueue; - _httpClientsCache.TryRemove(id, out connectionQueue); - } + } } } diff --git a/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs index 717144b0..74f6c787 100644 --- a/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs +++ b/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs @@ -45,7 +45,6 @@ namespace Ocelot.UnitTests.Middleware services.AddDiscoveryClient(new DiscoveryOptions { ClientType = DiscoveryClientType.EUREKA, - //options can not be null ClientOptions = new EurekaClientOptions() { ShouldFetchRegistry = false, diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index 1dd70591..429c5a90 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -23,15 +23,18 @@ namespace Ocelot.UnitTests.Requester { public class HttpClientBuilderTests : IDisposable { - private readonly HttpClientBuilder _builder; + private HttpClientBuilder _builder; private readonly Mock _factory; private IHttpClient _httpClient; private HttpResponseMessage _response; private DownstreamContext _context; private readonly Mock _cacheHandlers; - private Mock _logger; + private readonly Mock _logger; private int _count; private IWebHost _host; + private IHttpClient _againHttpClient; + private IHttpClient _firstHttpClient; + private MemoryHttpClientCache _realCache; public HttpClientBuilderTests() { @@ -61,6 +64,47 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } + [Fact] + public void should_get_from_cache() + { + var qosOptions = new QoSOptionsBuilder() + .Build(); + + var reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true)) + .WithLoadBalancerKey("") + .WithQosOptions(new QoSOptionsBuilder().Build()) + .Build(); + + this.Given(x => GivenARealCache()) + .And(x => GivenTheFactoryReturns()) + .And(x => GivenARequest(reRoute)) + .And(x => WhenIBuildTheFirstTime()) + .And(x => WhenISave()) + .And(x => WhenIBuildAgain()) + .And(x => WhenISave()) + .When(x => WhenIBuildAgain()) + .Then(x => ThenTheHttpClientIsFromTheCache()) + .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() { @@ -302,6 +346,17 @@ namespace Ocelot.UnitTests.Requester _httpClient = _builder.Create(_context); } + private void WhenIBuildTheFirstTime() + { + _firstHttpClient = _builder.Create(_context); + } + + private void WhenIBuildAgain() + { + _builder = new HttpClientBuilder(_factory.Object, _realCache, _logger.Object); + _againHttpClient = _builder.Create(_context); + } + private void ThenTheHttpClientShouldNotBeNull() { _httpClient.ShouldNotBeNull(); diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/PollingConsulServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/PollingConsulServiceDiscoveryProviderTests.cs index e75bfbca..86d523e2 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/PollingConsulServiceDiscoveryProviderTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/PollingConsulServiceDiscoveryProviderTests.cs @@ -1,33 +1,24 @@ -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; -using static Ocelot.Infrastructure.Wait; - -namespace Ocelot.UnitTests.ServiceDiscovery +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 string _serviceName; - private List _services; + private readonly List _services; private readonly Mock _factory; private readonly Mock _logger; - private Mock _consulServiceDiscoveryProvider; + private readonly Mock _consulServiceDiscoveryProvider; private List _result; public PollingConsulServiceDiscoveryProviderTests() @@ -64,7 +55,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery private void WhenIGetTheServices(int expected) { - _provider = new PollingConsulServiceDiscoveryProvider(_delay, _serviceName, _factory.Object, _consulServiceDiscoveryProvider.Object); + _provider = new PollingConsulServiceDiscoveryProvider(_delay, "", _factory.Object, _consulServiceDiscoveryProvider.Object); var result = WaitFor(3000).Until(() => { try From 8029d07d1d969baf596ca8da210e37f98c4ba47c Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 2 Jul 2018 20:48:05 +0100 Subject: [PATCH 04/16] point to test api in azure for performance profilin --- test/Ocelot.ManualTest/ocelot.json | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/test/Ocelot.ManualTest/ocelot.json b/test/Ocelot.ManualTest/ocelot.json index 6f531414..51d387c4 100644 --- a/test/Ocelot.ManualTest/ocelot.json +++ b/test/Ocelot.ManualTest/ocelot.json @@ -17,20 +17,15 @@ }, { "DownstreamPathTemplate": "/api/values", - "DownstreamScheme": "http", + "DownstreamScheme": "https", "UpstreamPathTemplate": "/api/values", "UpstreamHttpMethod": [ "Get" ], "DownstreamHostAndPorts": [ { - "Host": "localhost", - "Port": 5007 + "Host": "testapivalues.azurewebsites.net", + "Port": 443 } - ], - "HttpHandlerOptions": { - "AllowAutoRedirect": true, - "UseCookieContainer": true, - "UseTracing": true - } + ] }, { "DownstreamPathTemplate": "/", From 85efcf0787f9100b1f112701287bf74381f94686 Mon Sep 17 00:00:00 2001 From: Lee Kah Wai Date: Thu, 5 Jul 2018 13:57:08 +0800 Subject: [PATCH 05/16] Update authentication.rst (#449) Cannot assign lambda expression to an implicity-typed variable --- docs/features/authentication.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/authentication.rst b/docs/features/authentication.rst index 745440ea..2ab0b11d 100644 --- a/docs/features/authentication.rst +++ b/docs/features/authentication.rst @@ -100,7 +100,7 @@ In order to use IdentityServer bearer tokens register your IdentityServer servic public void ConfigureServices(IServiceCollection services) { var authenticationProviderKey = "TestKey"; - var options = o => + Action options = o => { o.Authority = "https://whereyouridentityserverlives.com"; o.ApiName = "api"; From d604badd49c08c94febc71a9640b76112e95c378 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 8 Jul 2018 15:02:35 +0100 Subject: [PATCH 06/16] test to show returning 304 works (#450) --- .../HttpHeadersTransformationMiddleware.cs | 5 ++ .../ResponseCodeTests.cs | 84 +++++++++++++++++++ test/Ocelot.ManualTest/ocelot.json | 16 ++++ 3 files changed, 105 insertions(+) create mode 100644 test/Ocelot.AcceptanceTests/ResponseCodeTests.cs diff --git a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs index a870cc0f..bead85c5 100644 --- a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs @@ -38,6 +38,11 @@ namespace Ocelot.Headers.Middleware await _next.Invoke(context); + if(context.IsError) + { + return; + } + var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace; _postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest); diff --git a/test/Ocelot.AcceptanceTests/ResponseCodeTests.cs b/test/Ocelot.AcceptanceTests/ResponseCodeTests.cs new file mode 100644 index 00000000..e4a361e4 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/ResponseCodeTests.cs @@ -0,0 +1,84 @@ +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; + +namespace Ocelot.AcceptanceTests +{ + public class ResponseCodeTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + private string _downstreamPath; + + public ResponseCodeTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_return_response_304_when_service_returns_304() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/{everything}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/{everything}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/inline.132.bundle.js", 304)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/inline.132.bundle.js")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotModified)) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + context.Response.StatusCode = statusCode; + }); + }) + .Build(); + + _builder.Start(); + } + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.ManualTest/ocelot.json b/test/Ocelot.ManualTest/ocelot.json index 51d387c4..dddb438f 100644 --- a/test/Ocelot.ManualTest/ocelot.json +++ b/test/Ocelot.ManualTest/ocelot.json @@ -15,6 +15,22 @@ "TimeoutValue": 360000 } }, + { + "DownstreamPathTemplate": "/api/v1/todo/", + "DownstreamScheme": "http", + "UpstreamPathTemplate": "/api/v1/todo/", + "UpstreamHttpMethod": [ "Get", "Post" ], + "DownstreamHostAndPorts": [ + { + "Host": "lxtodo.azurewebsites.net", + "Port": 80 + } + + ], + "DownstreamHeaderTransform": { + "Location": "{DownstreamBaseUrl}, {BaseUrl}" + } + }, { "DownstreamPathTemplate": "/api/values", "DownstreamScheme": "https", From a419ed68dc90d76efa5dc7a8ac6b06ac10f641b6 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 8 Jul 2018 15:09:16 +0100 Subject: [PATCH 07/16] #398 refactored MemoryHttpClientCache to stop it holding onto references (#448) --- global.json | 2 +- src/Ocelot/Requester/IHttpClientCache.cs | 4 +- src/Ocelot/Requester/MemoryHttpClientCache.cs | 62 +++++++------------ 3 files changed, 27 insertions(+), 41 deletions(-) diff --git a/global.json b/global.json index 7281f931..ceb6f979 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "projects": [ "src", "test" ], "sdk": { - "version": "2.1.4" + "version": "2.1.300" } } \ No newline at end of file diff --git a/src/Ocelot/Requester/IHttpClientCache.cs b/src/Ocelot/Requester/IHttpClientCache.cs index 2c4571ce..8aaeeaab 100644 --- a/src/Ocelot/Requester/IHttpClientCache.cs +++ b/src/Ocelot/Requester/IHttpClientCache.cs @@ -4,7 +4,7 @@ public interface IHttpClientCache { - IHttpClient Get(string id); - void Set(string id, IHttpClient handler, TimeSpan expirationTime); + IHttpClient Get(string key); + void Set(string key, IHttpClient handler, TimeSpan expirationTime); } } diff --git a/src/Ocelot/Requester/MemoryHttpClientCache.cs b/src/Ocelot/Requester/MemoryHttpClientCache.cs index 9e4059e5..58a6c166 100644 --- a/src/Ocelot/Requester/MemoryHttpClientCache.cs +++ b/src/Ocelot/Requester/MemoryHttpClientCache.cs @@ -1,40 +1,26 @@ -namespace Ocelot.Requester -{ - using System; +namespace Ocelot.Requester +{ + using System; using System.Collections.Concurrent; - - public class MemoryHttpClientCache : IHttpClientCache - { - private readonly ConcurrentDictionary> _httpClientsCache; - - public MemoryHttpClientCache() - { - _httpClientsCache = new ConcurrentDictionary>(); + + public class MemoryHttpClientCache : IHttpClientCache + { + private readonly ConcurrentDictionary _httpClientsCache; + + public MemoryHttpClientCache() + { + _httpClientsCache = new ConcurrentDictionary(); } - - public void Set(string id, IHttpClient client, TimeSpan expirationTime) - { - if (_httpClientsCache.TryGetValue(id, out var connectionQueue)) - { - connectionQueue.Enqueue(client); - } - else - { - connectionQueue = new ConcurrentQueue(); - connectionQueue.Enqueue(client); - _httpClientsCache.TryAdd(id, connectionQueue); - } - } - - public IHttpClient Get(string id) - { - IHttpClient client= null; - if (_httpClientsCache.TryGetValue(id, out var connectionQueue)) - { - connectionQueue.TryDequeue(out client); - } - - return client; - } - } -} + + public void Set(string key, IHttpClient client, TimeSpan expirationTime) + { + _httpClientsCache.AddOrUpdate(key, client, (k, oldValue) => client); + } + + public IHttpClient Get(string key) + { + //todo handle error? + return _httpClientsCache.TryGetValue(key, out var client) ? client : null; + } + } +} From c8b72f31b5d06c30a1927ac4d9c1b055e38c156a Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Mon, 9 Jul 2018 08:08:39 +0100 Subject: [PATCH 08/16] #438 removed singleton delegating handlers as you cannot have these (#456) --- docs/features/delegatinghandlers.rst | 145 ++- global.json | 12 +- .../DependencyInjection/IOcelotBuilder.cs | 5 +- .../DependencyInjection/OcelotBuilder.cs | 21 +- .../HttpDelegatingHandlersTests.cs | 62 ++ test/Ocelot.AcceptanceTests/Steps.cs | 41 +- test/Ocelot.ManualTest/Program.cs | 23 +- .../DependencyInjection/OcelotBuilderTests.cs | 935 +++++++++--------- 8 files changed, 643 insertions(+), 601 deletions(-) diff --git a/docs/features/delegatinghandlers.rst b/docs/features/delegatinghandlers.rst index 445a46fb..7c354760 100644 --- a/docs/features/delegatinghandlers.rst +++ b/docs/features/delegatinghandlers.rst @@ -1,79 +1,66 @@ -Delegating Handers -================== - -Ocelot allows the user to add delegating handlers to the HttpClient transport. This feature was requested `GitHub #208 `_ -and I decided that it was going to be useful in various ways. Since then we extended it in `GitHub #264 `_. - -Usage -^^^^^ - -In order to add delegating handlers to the HttpClient transport you need to do two main things. - -First in order to create a class that can be used a delegating handler it must look as follows. We are going to register these handlers in the -asp.net core container so you can inject any other services you have registered into the constructor of your handler. - -.. code-block:: csharp - - public class FakeHandler : DelegatingHandler - { - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - //do stuff and optionally call the base handler.. - return await base.SendAsync(request, cancellationToken); - } - } - -Next you must add the handlers to Ocelot's container either as singleton like follows.. - -.. code-block:: csharp - - services.AddOcelot() - .AddSingletonDelegatingHandler() - .AddSingletonDelegatingHandler() - -Or transient as below... - -.. code-block:: csharp - - services.AddOcelot() - .AddTransientDelegatingHandler() - .AddTransientDelegatingHandler() - -Both of these Add methods have a default parameter called global which is set to false. If it is false then the intent of -the DelegatingHandler is to be applied to specific ReRoutes via ocelot.json (more on that later). If it is set to true -then it becomes a global handler and will be applied to all ReRoutes. - -e.g. - -.. code-block:: csharp - - services.AddOcelot() - .AddSingletonDelegatingHandler(true) - -Or transient as below... - -.. code-block:: csharp - - services.AddOcelot() - .AddTransientDelegatingHandler(true) - -Finally if you want ReRoute specific DelegatingHandlers or to order your specific and / or global (more on this later) DelegatingHandlers -then you must add the following json to the specific ReRoute in ocelot.json. The names in the array must match the class names of your -DelegatingHandlers for Ocelot to match them together. - -.. code-block:: json - - "DelegatingHandlers": [ - "FakeHandlerTwo", - "FakeHandler" - ] - -You can have as many DelegatingHandlers as you want and they are run in the following order: - -1. Any globals that are left in the order they were added to services and are not in the DelegatingHandlers array from ocelot.json. -2. Any non global DelegatingHandlers plus any globals that were in the DelegatingHandlers array from ocelot.json ordered as they are in the DelegatingHandlers array. -3. Tracing DelegatingHandler if enabled (see tracing docs). -4. QoS DelegatingHandler if enabled (see QoS docs). -5. The HttpClient sends the HttpRequestMessage. - -Hopefully other people will find this feature useful! +Delegating Handlers +=================== + +Ocelot allows the user to add delegating handlers to the HttpClient transport. This feature was requested `GitHub #208 `_ +and I decided that it was going to be useful in various ways. Since then we extended it in `GitHub #264 `_. + +Usage +^^^^^ + +In order to add delegating handlers to the HttpClient transport you need to do two main things. + +First in order to create a class that can be used a delegating handler it must look as follows. We are going to register these handlers in the +asp.net core container so you can inject any other services you have registered into the constructor of your handler. + +.. code-block:: csharp + + public class FakeHandler : DelegatingHandler + { + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + //do stuff and optionally call the base handler.. + return await base.SendAsync(request, cancellationToken); + } + } + +Next you must add the handlers to Ocelot's container like below... + +.. code-block:: csharp + + services.AddOcelot() + .AddDelegatingHandler() + .AddDelegatingHandler() + +Both of these Add methods have a default parameter called global which is set to false. If it is false then the intent of +the DelegatingHandler is to be applied to specific ReRoutes via ocelot.json (more on that later). If it is set to true +then it becomes a global handler and will be applied to all ReRoutes. + +e.g. + +As below... + +.. code-block:: csharp + + services.AddOcelot() + .AddDelegatingHandler(true) + +Finally if you want ReRoute specific DelegatingHandlers or to order your specific and / or global (more on this later) DelegatingHandlers +then you must add the following json to the specific ReRoute in ocelot.json. The names in the array must match the class names of your +DelegatingHandlers for Ocelot to match them together. + +.. code-block:: json + + "DelegatingHandlers": [ + "FakeHandlerTwo", + "FakeHandler" + ] + +You can have as many DelegatingHandlers as you want and they are run in the following order: + +1. Any globals that are left in the order they were added to services and are not in the DelegatingHandlers array from ocelot.json. +2. Any non global DelegatingHandlers plus any globals that were in the DelegatingHandlers array from ocelot.json ordered as they are in the DelegatingHandlers array. +3. Tracing DelegatingHandler if enabled (see tracing docs). +4. QoS DelegatingHandler if enabled (see QoS docs). +5. The HttpClient sends the HttpRequestMessage. + +Hopefully other people will find this feature useful! diff --git a/global.json b/global.json index ceb6f979..1494d180 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ -{ - "projects": [ "src", "test" ], - "sdk": { - "version": "2.1.300" - } -} \ No newline at end of file +{ + "projects": [ "src", "test" ], + "sdk": { + "version": "2.1.301" + } +} diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index 7cf514cb..20eafd36 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -19,10 +19,7 @@ namespace Ocelot.DependencyInjection IOcelotAdministrationBuilder AddAdministration(string path, Action configOptions); - IOcelotBuilder AddSingletonDelegatingHandler(bool global = false) - where T : DelegatingHandler; - - IOcelotBuilder AddTransientDelegatingHandler(bool global = false) + IOcelotBuilder AddDelegatingHandler(bool global = false) where T : DelegatingHandler; IOcelotBuilder AddSingletonDefinedAggregator() diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 7132c0b2..1b93ddbb 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -212,26 +212,7 @@ namespace Ocelot.DependencyInjection return this; } - public IOcelotBuilder AddSingletonDelegatingHandler(bool global = false) - where THandler : DelegatingHandler - { - if(global) - { - _services.AddSingleton(); - _services.AddSingleton(s => { - var service = s.GetService(); - return new GlobalDelegatingHandler(service); - }); - } - else - { - _services.AddSingleton(); - } - - return this; - } - - public IOcelotBuilder AddTransientDelegatingHandler(bool global = false) + public IOcelotBuilder AddDelegatingHandler(bool global = false) where THandler : DelegatingHandler { if(global) diff --git a/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs b/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs index 740ee6e7..a6a9a9ce 100644 --- a/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs +++ b/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs @@ -101,6 +101,57 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_call_global_di_handlers_multiple_times() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 9187, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:9187", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningWithGlobalHandlerRegisteredInDi()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .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")) + .BDDfy(); + } + [Fact] public void should_call_global_di_handlers_with_dependency() { @@ -198,6 +249,17 @@ namespace Ocelot.AcceptanceTests return base.SendAsync(request, cancellationToken); } } + // ReSharper disable once ClassNeverInstantiated.Local + private class FakeHandlerAgain : DelegatingHandler + { + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + Console.WriteLine(request.RequestUri); + + //do stuff and optionally call the base handler.. + return await base.SendAsync(request, cancellationToken); + } + } private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) { diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 5dd95831..62d1c816 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -237,8 +237,8 @@ namespace Ocelot.AcceptanceTests { s.AddSingleton(_webHostBuilder); s.AddOcelot() - .AddSingletonDelegatingHandler() - .AddSingletonDelegatingHandler(); + .AddDelegatingHandler() + .AddDelegatingHandler(); }) .Configure(a => { @@ -303,8 +303,39 @@ namespace Ocelot.AcceptanceTests { s.AddSingleton(_webHostBuilder); s.AddOcelot() - .AddSingletonDelegatingHandler(true) - .AddSingletonDelegatingHandler(true); + .AddDelegatingHandler(true) + .AddDelegatingHandler(true); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningWithGlobalHandlerRegisteredInDi() + where TOne : DelegatingHandler + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("ocelot.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddOcelot() + .AddDelegatingHandler(true); }) .Configure(a => { @@ -336,7 +367,7 @@ namespace Ocelot.AcceptanceTests s.AddSingleton(_webHostBuilder); s.AddSingleton(dependency); s.AddOcelot() - .AddSingletonDelegatingHandler(true); + .AddDelegatingHandler(true); }) .Configure(a => { diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index a33395c1..fede584f 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -9,6 +9,9 @@ using Ocelot.Middleware; using System; using IdentityServer4.AccessTokenValidation; + using System.Net.Http; + using System.Threading.Tasks; + using System.Threading; public class Program { @@ -35,10 +38,11 @@ }); s.AddOcelot() - .AddCacheManager(x => - { - x.WithDictionaryHandle(); - }) + .AddDelegatingHandler(true) + // .AddCacheManager(x => + // { + // x.WithDictionaryHandle(); + // }) /*.AddOpenTracing(option => { option.CollectorUrl = "http://localhost:9618"; @@ -60,4 +64,15 @@ .Run(); } } + + public class FakeHandler : DelegatingHandler + { + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + Console.WriteLine(request.RequestUri); + + //do stuff and optionally call the base handler.. + return await base.SendAsync(request, cancellationToken); + } + } } diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index b295a564..358dab18 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -1,483 +1,452 @@ -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; -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 -{ - public class OcelotBuilderTests - { - private readonly IServiceCollection _services; - private IServiceProvider _serviceProvider; - private readonly IConfiguration _configRoot; - private IOcelotBuilder _ocelotBuilder; - private readonly int _maxRetries; - private Exception _ex; - - public OcelotBuilderTests() - { - _configRoot = new ConfigurationRoot(new List()); - _services = new ServiceCollection(); - _services.AddSingleton(); - _services.AddSingleton(_configRoot); - _maxRetries = 100; - } - - [Fact] - public void should_add_specific_delegating_handlers_transient() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddSpecificTransientDelegatingHandler()) - .And(x => AddSpecificTransientDelegatingHandler()) - .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificHandlers()) - .And(x => ThenTheSpecificHandlersAreTransient()) - .BDDfy(); - } - - [Fact] - public void should_add_specific_delegating_handler_singleton() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddSpecificDelegatingHandler()) - .And(x => AddSpecificDelegatingHandler()) - .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificHandlers()) - .And(x => ThenTheSpecificHandlersAreSingleton()) - .BDDfy(); - } - - [Fact] - public void should_add_global_delegating_handlers_transient() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddTransientGlobalDelegatingHandler()) - .And(x => AddTransientGlobalDelegatingHandler()) - .Then(x => ThenTheProviderIsRegisteredAndReturnsHandlers()) - .And(x => ThenTheGlobalHandlersAreTransient()) - .BDDfy(); - } - - [Fact] - public void should_add_global_delegating_handlers_singleton() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddGlobalDelegatingHandler()) - .And(x => AddGlobalDelegatingHandler()) - .Then(x => ThenTheProviderIsRegisteredAndReturnsHandlers()) - .And(x => ThenTheGlobalHandlersAreSingleton()) - .BDDfy(); - } - - [Fact] - public void should_set_up_services() - { - this.When(x => WhenISetUpOcelotServices()) - .Then(x => ThenAnExceptionIsntThrown()) - .BDDfy(); - } - - [Fact] - public void should_return_ocelot_builder() - { - this.When(x => WhenISetUpOcelotServices()) - .Then(x => ThenAnOcelotBuilderIsReturned()) - .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() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpConsul()) - .Then(x => ThenAnExceptionIsntThrown()) - .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() - { - 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() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenIValidateScopes()) - .When(x => WhenIAccessLoggerFactory()) - .Then(x => ThenAnExceptionIsntThrown()) - .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() - { - this.When(x => WhenISetUpOcelotServicesWithoutConfig()) - .Then(x => ThenAnExceptionIsntThrown()) - .BDDfy(); - } - - [Fact] - public void should_add_singleton_defined_aggregators() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddSingletonDefinedAggregator()) - .When(x => AddSingletonDefinedAggregator()) - .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificAggregators()) - .And(x => ThenTheAggregatorsAreSingleton()) - .BDDfy(); - } - - [Fact] - public void should_add_transient_defined_aggregators() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddTransientDefinedAggregator()) - .When(x => AddTransientDefinedAggregator()) - .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificAggregators()) - .And(x => ThenTheAggregatorsAreTransient()) - .BDDfy(); - } - - private void AddSingletonDefinedAggregator() - where T : class, IDefinedAggregator - { - _ocelotBuilder.AddSingletonDefinedAggregator(); - } - - private void AddTransientDefinedAggregator() - where T : class, IDefinedAggregator - { - _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(); - var first = handlers[0]; - handlers = _serviceProvider.GetServices().ToList(); - var second = handlers[0]; - first.ShouldNotBe(second); - } - - private void ThenTheGlobalHandlersAreSingleton() - { - var handlers = _serviceProvider.GetServices().ToList(); - var first = handlers[0].DelegatingHandler; - handlers = _serviceProvider.GetServices().ToList(); - var second = handlers[0].DelegatingHandler; - first.ShouldBe(second); - } - - private void ThenTheGlobalHandlersAreTransient() - { - var handlers = _serviceProvider.GetServices().ToList(); - var first = handlers[0].DelegatingHandler; - handlers = _serviceProvider.GetServices().ToList(); - var second = handlers[0].DelegatingHandler; - first.ShouldNotBe(second); - } - - private void WhenISetUpAdministration() - { - _ocelotBuilder.AddAdministration("/administration", "secret"); - } - - private void WhenISetUpAdministration(Action options) - { - _ocelotBuilder.AddAdministration("/administration", options); - } - - private void AddTransientGlobalDelegatingHandler() - where T : DelegatingHandler - { - _ocelotBuilder.AddTransientDelegatingHandler(true); - } - - private void AddSpecificTransientDelegatingHandler() - where T : DelegatingHandler - { - _ocelotBuilder.AddTransientDelegatingHandler(); - } - - private void ThenTheCorrectAdminPathIsRegitered() - { - _serviceProvider = _services.BuildServiceProvider(); - var path = _serviceProvider.GetService(); - path.Path.ShouldBe("/administration"); - } - - private void ThenTheProviderIsRegisteredAndReturnsHandlers() - { - _serviceProvider = _services.BuildServiceProvider(); - var handlers = _serviceProvider.GetServices().ToList(); - handlers[0].DelegatingHandler.ShouldBeOfType(); - handlers[1].DelegatingHandler.ShouldBeOfType(); - } - - private void ThenTheProviderIsRegisteredAndReturnsSpecificHandlers() - { - _serviceProvider = _services.BuildServiceProvider(); - var handlers = _serviceProvider.GetServices().ToList(); - handlers[0].ShouldBeOfType(); - handlers[1].ShouldBeOfType(); - } - - private void ThenTheProviderIsRegisteredAndReturnsSpecificAggregators() - { - _serviceProvider = _services.BuildServiceProvider(); - var handlers = _serviceProvider.GetServices().ToList(); - handlers[0].ShouldBeOfType(); - handlers[1].ShouldBeOfType(); - } - - private void ThenTheAggregatorsAreTransient() - { - var aggregators = _serviceProvider.GetServices().ToList(); - var first = aggregators[0]; - aggregators = _serviceProvider.GetServices().ToList(); - var second = aggregators[0]; - first.ShouldNotBe(second); - } - - private void ThenTheAggregatorsAreSingleton() - { - var aggregators = _serviceProvider.GetServices().ToList(); - var first = aggregators[0]; - aggregators = _serviceProvider.GetServices().ToList(); - var second = aggregators[0]; - 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 - { - _ocelotBuilder.AddStoreOcelotConfigurationInConsul(); - } - catch (Exception e) - { - _ex = e; - } - } - - private void WhenISetUpRafty() - { - try - { - _ocelotBuilder.AddAdministration("/administration", "secret").AddRafty(); - } - catch (Exception e) - { - _ex = e; - } - } - - private void AddGlobalDelegatingHandler() - where T : DelegatingHandler - { - _ocelotBuilder.AddSingletonDelegatingHandler(true); - } - - private void AddSpecificDelegatingHandler() - where T : DelegatingHandler - { - _ocelotBuilder.AddSingletonDelegatingHandler(); - } - - private void ThenAnOcelotBuilderIsReturned() - { - _ocelotBuilder.ShouldBeOfType(); - } - - private void WhenISetUpOcelotServices() - { - try - { - _ocelotBuilder = _services.AddOcelot(_configRoot); - } - catch (Exception e) - { - _ex = e; - } - } - - private void WhenISetUpOcelotServicesWithoutConfig() - { - try - { - _ocelotBuilder = _services.AddOcelot(); - } - catch (Exception e) - { - _ex = e; - } - } - - private void WhenISetUpCacheManager() - { - try - { - _ocelotBuilder.AddCacheManager(x => { - x.WithMaxRetries(_maxRetries); - x.WithDictionaryHandle(); - }); - } - catch (Exception e) - { - _ex = e; - } - } - - 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 - { - _serviceProvider = _services.BuildServiceProvider(); - var logger = _serviceProvider.GetService(); - logger.ShouldNotBeNull(); - } - catch (Exception e) - { - _ex = e; - } - } - - private void WhenIAccessOcelotHttpTracingHandler() - { - try - { - var tracingHandler = _serviceProvider.GetService(); - tracingHandler.ShouldNotBeNull(); - } - catch (Exception e) - { - _ex = e; - } - } - - private void WhenIValidateScopes() - { - try - { - _serviceProvider = _services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = true }); - } - catch (Exception e) - { - _ex = e; - } - } - - private void ThenAnExceptionIsntThrown() - { - _ex.ShouldBeNull(); - } - } -} +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; +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 +{ + public class OcelotBuilderTests + { + private readonly IServiceCollection _services; + private IServiceProvider _serviceProvider; + private readonly IConfiguration _configRoot; + private IOcelotBuilder _ocelotBuilder; + private readonly int _maxRetries; + private Exception _ex; + + public OcelotBuilderTests() + { + _configRoot = new ConfigurationRoot(new List()); + _services = new ServiceCollection(); + _services.AddSingleton(); + _services.AddSingleton(_configRoot); + _maxRetries = 100; + } + + [Fact] + public void should_add_specific_delegating_handlers_transient() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddSpecificTransientDelegatingHandler()) + .And(x => AddSpecificTransientDelegatingHandler()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificHandlers()) + .And(x => ThenTheSpecificHandlersAreTransient()) + .BDDfy(); + } + + [Fact] + public void should_add_global_delegating_handlers_transient() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddTransientGlobalDelegatingHandler()) + .And(x => AddTransientGlobalDelegatingHandler()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsHandlers()) + .And(x => ThenTheGlobalHandlersAreTransient()) + .BDDfy(); + } + + [Fact] + public void should_set_up_services() + { + this.When(x => WhenISetUpOcelotServices()) + .Then(x => ThenAnExceptionIsntThrown()) + .BDDfy(); + } + + [Fact] + public void should_return_ocelot_builder() + { + this.When(x => WhenISetUpOcelotServices()) + .Then(x => ThenAnOcelotBuilderIsReturned()) + .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() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => WhenISetUpConsul()) + .Then(x => ThenAnExceptionIsntThrown()) + .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() + { + 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() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => WhenIValidateScopes()) + .When(x => WhenIAccessLoggerFactory()) + .Then(x => ThenAnExceptionIsntThrown()) + .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() + { + this.When(x => WhenISetUpOcelotServicesWithoutConfig()) + .Then(x => ThenAnExceptionIsntThrown()) + .BDDfy(); + } + + [Fact] + public void should_add_singleton_defined_aggregators() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddSingletonDefinedAggregator()) + .When(x => AddSingletonDefinedAggregator()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificAggregators()) + .And(x => ThenTheAggregatorsAreSingleton()) + .BDDfy(); + } + + [Fact] + public void should_add_transient_defined_aggregators() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddTransientDefinedAggregator()) + .When(x => AddTransientDefinedAggregator()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificAggregators()) + .And(x => ThenTheAggregatorsAreTransient()) + .BDDfy(); + } + + private void AddSingletonDefinedAggregator() + where T : class, IDefinedAggregator + { + _ocelotBuilder.AddSingletonDefinedAggregator(); + } + + private void AddTransientDefinedAggregator() + where T : class, IDefinedAggregator + { + _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(); + var first = handlers[0]; + handlers = _serviceProvider.GetServices().ToList(); + var second = handlers[0]; + first.ShouldNotBe(second); + } + + private void ThenTheGlobalHandlersAreTransient() + { + var handlers = _serviceProvider.GetServices().ToList(); + var first = handlers[0].DelegatingHandler; + handlers = _serviceProvider.GetServices().ToList(); + var second = handlers[0].DelegatingHandler; + first.ShouldNotBe(second); + } + + private void WhenISetUpAdministration() + { + _ocelotBuilder.AddAdministration("/administration", "secret"); + } + + private void WhenISetUpAdministration(Action options) + { + _ocelotBuilder.AddAdministration("/administration", options); + } + + private void AddTransientGlobalDelegatingHandler() + where T : DelegatingHandler + { + _ocelotBuilder.AddDelegatingHandler(true); + } + + private void AddSpecificTransientDelegatingHandler() + where T : DelegatingHandler + { + _ocelotBuilder.AddDelegatingHandler(); + } + + private void ThenTheCorrectAdminPathIsRegitered() + { + _serviceProvider = _services.BuildServiceProvider(); + var path = _serviceProvider.GetService(); + path.Path.ShouldBe("/administration"); + } + + private void ThenTheProviderIsRegisteredAndReturnsHandlers() + { + _serviceProvider = _services.BuildServiceProvider(); + var handlers = _serviceProvider.GetServices().ToList(); + handlers[0].DelegatingHandler.ShouldBeOfType(); + handlers[1].DelegatingHandler.ShouldBeOfType(); + } + + private void ThenTheProviderIsRegisteredAndReturnsSpecificHandlers() + { + _serviceProvider = _services.BuildServiceProvider(); + var handlers = _serviceProvider.GetServices().ToList(); + handlers[0].ShouldBeOfType(); + handlers[1].ShouldBeOfType(); + } + + private void ThenTheProviderIsRegisteredAndReturnsSpecificAggregators() + { + _serviceProvider = _services.BuildServiceProvider(); + var handlers = _serviceProvider.GetServices().ToList(); + handlers[0].ShouldBeOfType(); + handlers[1].ShouldBeOfType(); + } + + private void ThenTheAggregatorsAreTransient() + { + var aggregators = _serviceProvider.GetServices().ToList(); + var first = aggregators[0]; + aggregators = _serviceProvider.GetServices().ToList(); + var second = aggregators[0]; + first.ShouldNotBe(second); + } + + private void ThenTheAggregatorsAreSingleton() + { + var aggregators = _serviceProvider.GetServices().ToList(); + var first = aggregators[0]; + aggregators = _serviceProvider.GetServices().ToList(); + var second = aggregators[0]; + 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 + { + _ocelotBuilder.AddStoreOcelotConfigurationInConsul(); + } + catch (Exception e) + { + _ex = e; + } + } + + private void WhenISetUpRafty() + { + try + { + _ocelotBuilder.AddAdministration("/administration", "secret").AddRafty(); + } + catch (Exception e) + { + _ex = e; + } + } + + private void AddGlobalDelegatingHandler() + where T : DelegatingHandler + { + _ocelotBuilder.AddDelegatingHandler(true); + } + + private void AddSpecificDelegatingHandler() + where T : DelegatingHandler + { + _ocelotBuilder.AddDelegatingHandler(); + } + + private void ThenAnOcelotBuilderIsReturned() + { + _ocelotBuilder.ShouldBeOfType(); + } + + private void WhenISetUpOcelotServices() + { + try + { + _ocelotBuilder = _services.AddOcelot(_configRoot); + } + catch (Exception e) + { + _ex = e; + } + } + + private void WhenISetUpOcelotServicesWithoutConfig() + { + try + { + _ocelotBuilder = _services.AddOcelot(); + } + catch (Exception e) + { + _ex = e; + } + } + + private void WhenISetUpCacheManager() + { + try + { + _ocelotBuilder.AddCacheManager(x => { + x.WithMaxRetries(_maxRetries); + x.WithDictionaryHandle(); + }); + } + catch (Exception e) + { + _ex = e; + } + } + + 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 + { + _serviceProvider = _services.BuildServiceProvider(); + var logger = _serviceProvider.GetService(); + logger.ShouldNotBeNull(); + } + catch (Exception e) + { + _ex = e; + } + } + + private void WhenIAccessOcelotHttpTracingHandler() + { + try + { + var tracingHandler = _serviceProvider.GetService(); + tracingHandler.ShouldNotBeNull(); + } + catch (Exception e) + { + _ex = e; + } + } + + private void WhenIValidateScopes() + { + try + { + _serviceProvider = _services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = true }); + } + catch (Exception e) + { + _ex = e; + } + } + + private void ThenAnExceptionIsntThrown() + { + _ex.ShouldBeNull(); + } + } +} From 89c3887d3642faa8983af2896e8359d79024d023 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Mon, 9 Jul 2018 18:35:01 +0100 Subject: [PATCH 09/16] #453 made caching docs a bit better (#457) --- docs/features/caching.rst | 55 ++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/docs/features/caching.rst b/docs/features/caching.rst index 8e70bc3a..fd780b1e 100644 --- a/docs/features/caching.rst +++ b/docs/features/caching.rst @@ -1,21 +1,34 @@ -Caching -======= - -Ocelot supports some very rudimentary caching at the moment provider by -the `CacheManager `_ project. This is an amazing project -that is solving a lot of caching problems. I would reccomend using this package to -cache with Ocelot. If you look at the example `here `_ you can see how the cache manager is setup and then passed into the Ocelot -AddOcelotOutputCaching configuration method. You can use any settings supported by -the CacheManager package and just pass them in. - -Anyway Ocelot currently supports caching on the URL of the downstream service -and setting a TTL in seconds to expire the cache. You can also clear the cache for a region -by calling Ocelot's administration API. - -In order to use caching on a route in your ReRoute configuration add this setting. - -.. code-block:: json - - "FileCacheOptions": { "TtlSeconds": 15, "Region": "somename" } - -In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds. +Caching +======= + +Ocelot supports some very rudimentary caching at the moment provider by +the `CacheManager `_ project. This is an amazing project +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.. + +.. code-block:: csharp + + s.AddOcelot() + .AddCacheManager(x => + { + x.WithDictionaryHandle(); + }) + +In order to use caching on a route in your ReRoute configuration add this setting. + +.. code-block:: json + + "FileCacheOptions": { "TtlSeconds": 15, "Region": "somename" } + +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 +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. + From 75f9a8f9be29122b6b857fd750dda62475f3aad7 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Tue, 10 Jul 2018 18:00:17 +0100 Subject: [PATCH 10/16] #451 started implementing querystring support in templates (#459) * #451 started implementing querystring support in templates * #451 ocelot.json back to normal and specified in docs query string wont work in upstream template * Revert "#451 ocelot.json back to normal and specified in docs query string wont work in upstream template" This reverts commit 563193f7b2f78bad6109484fe77f3c87de831005. * #451 ocelot.json back to normal and specified in docs query string wont work in upstream template --- docs/features/routing.rst | 405 ++++++----- .../DownstreamUrlCreatorMiddleware.cs | 196 ++--- test/Ocelot.AcceptanceTests/RoutingTests.cs | 2 +- .../RoutingWithQueryStringTests.cs | 104 +++ test/Ocelot.ManualTest/ocelot.json | 668 +++++++++--------- .../DownstreamUrlCreatorMiddlewareTests.cs | 466 ++++++------ 6 files changed, 1005 insertions(+), 836 deletions(-) create mode 100644 test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs diff --git a/docs/features/routing.rst b/docs/features/routing.rst index 06928371..abcfc7d2 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -1,187 +1,218 @@ -Routing -======= - -Ocelot's primary functionality is to take incomeing http requests and forward them on -to a downstream service. At the moment in the form of another http request (in the future -this could be any transport mechanism). - -Ocelot's describes the routing of one request to another as a ReRoute. In order to get -anything working in Ocelot you need to set up a ReRoute in the configuration. - -.. code-block:: json - - { - "ReRoutes": [ - ] - } - -In order to set up a ReRoute you need to add one to the json array called ReRoutes like -the following. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Put", "Delete" ] - } - -The DownstreamPathTemplate, Scheme and DownstreamHostAndPorts make the URL that this request will be forwarded to. - -DownstreamHostAndPorts is an array that contains the host and port of any downstream services that you wish to forward requests to. Usually this will just contain one entry but sometimes you might want to load balance -requests to your downstream services and Ocelot let's you add more than one entry and then select a load balancer. - -The UpstreamPathTemplate is the URL that Ocelot will use to identity which DownstreamPathTemplate to use for a given request. Finally the UpstreamHttpMethod is used so -Ocelot can distinguish between requests to the same URL and is obviously needed to work :) - -You can set a specific list of HTTP Methods or set an empty list to allow any of them. In Ocelot you can add placeholders for variables to your Templates in the form of {something}. -The placeholder needs to be in both the DownstreamPathTemplate and UpstreamPathTemplate. If it is Ocelot will attempt to replace the placeholder with the correct variable value from the Upstream URL when the request comes in. - -You can also do a catch all type of ReRoute e.g. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/{everything}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/{everything}", - "UpstreamHttpMethod": [ "Get", "Post" ] - } - -This will forward any path + query string combinations to the downstream service after the path /api. - -At the moment without any configuration Ocelot will default to all ReRoutes being case insensitive. -In order to change this you can specify on a per ReRoute basis the following setting. - -.. code-block:: json - - "ReRouteIsCaseSensitive": true - -This means that when Ocelot tries to match the incoming upstream url with an upstream template the -evaluation will be case sensitive. This setting defaults to false so only set it if you want -the ReRoute to be case sensitive is my advice! - -Catch All -^^^^^^^^^ - -Ocelot's routing also supports a catch all style routing where the user can specify that they want to match all traffic if you set up your config like below the request will be proxied straight through (it doesnt have to be url any placeholder name will work). - -.. code-block:: json - - { - "DownstreamPathTemplate": "/{url}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/{url}", - "UpstreamHttpMethod": [ "Get" ] - } - -The catch all has a lower priority than any other ReRoute. If you also have the ReRoute below in your config then Ocelot would match it before the catch all. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.10.1", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": [ "Get" ] - } - -Upstream Host -^^^^^^^^^^^^^ - -This feature allows you to have ReRoutes based on the upstream host. This works by looking at the host header the client has used and then using this as part of the information we use to identify a ReRoute. - -In order to use this feature please add the following to your config. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.10.1", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": [ "Get" ], - "UpstreamHost": "somedomain.com" - } - -The ReRoute above will only be matched when the host header value is somedomain.com. - -If you do not set UpstreamHost on a ReRoue then any host header can match it. This is basically a catch all and -preservers existing functionality at the time of building the feature. This means that if you have two ReRoutes that are the same apart from the UpstreamHost where one is null and the other set. Ocelot will favour the one that has been set. - -This feature was requested as part of `Issue 216 `_ . - -Priority -^^^^^^^^ - -In `Issue 270 `_ I finally decided to expose the ReRoute priority in -ocelot.json. This means you can decide in what order you want your ReRoutes to match the Upstream HttpRequest. - -In order to get this working add the following to a ReRoute in ocelot.json, 0 is just an example value here but will explain below. - -.. code-block:: json - - { - "Priority": 0 - } - -0 is the lowest priority, Ocelot will always use 0 for /{catchAll} ReRoutes and this is still hardcoded. After that you are free -to set any priority you wish. - -e.g. you could have - -.. code-block:: json - - { - "UpstreamPathTemplate": "/goods/{catchAll}" - "Priority": 0 - } - -and - -.. code-block:: json - - { - "UpstreamPathTemplate": "/goods/delete" - "Priority": 1 - } - -In the example above if you make a request into Ocelot on /goods/delete Ocelot will match /goods/delete ReRoute. Previously it would have -matched /goods/{catchAll} (because this is the first ReRoute in the list!). - -Dynamic Routing -^^^^^^^^^^^^^^^ - -This feature was requested in `issue 340 `_. The idea is to enable dynamic routing -when using a service discovery provider so you don't have to provide the ReRoute config. See the docs :ref:`service-discovery` if -this sounds interesting to you. +Routing +======= + +Ocelot's primary functionality is to take incomeing http requests and forward them on +to a downstream service. At the moment in the form of another http request (in the future +this could be any transport mechanism). + +Ocelot's describes the routing of one request to another as a ReRoute. In order to get +anything working in Ocelot you need to set up a ReRoute in the configuration. + +.. code-block:: json + + { + "ReRoutes": [ + ] + } + +In order to set up a ReRoute you need to add one to the json array called ReRoutes like +the following. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + +The DownstreamPathTemplate, Scheme and DownstreamHostAndPorts make the URL that this request will be forwarded to. + +DownstreamHostAndPorts is an array that contains the host and port of any downstream services that you wish to forward requests to. Usually this will just contain one entry but sometimes you might want to load balance +requests to your downstream services and Ocelot let's you add more than one entry and then select a load balancer. + +The UpstreamPathTemplate is the URL that Ocelot will use to identity which DownstreamPathTemplate to use for a given request. Finally the UpstreamHttpMethod is used so +Ocelot can distinguish between requests to the same URL and is obviously needed to work :) + +You can set a specific list of HTTP Methods or set an empty list to allow any of them. In Ocelot you can add placeholders for variables to your Templates in the form of {something}. +The placeholder needs to be in both the DownstreamPathTemplate and UpstreamPathTemplate. If it is Ocelot will attempt to replace the placeholder with the correct variable value from the Upstream URL when the request comes in. + +You can also do a catch all type of ReRoute e.g. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/{everything}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/{everything}", + "UpstreamHttpMethod": [ "Get", "Post" ] + } + +This will forward any path + query string combinations to the downstream service after the path /api. + +At the moment without any configuration Ocelot will default to all ReRoutes being case insensitive. +In order to change this you can specify on a per ReRoute basis the following setting. + +.. code-block:: json + + "ReRouteIsCaseSensitive": true + +This means that when Ocelot tries to match the incoming upstream url with an upstream template the +evaluation will be case sensitive. This setting defaults to false so only set it if you want +the ReRoute to be case sensitive is my advice! + +Catch All +^^^^^^^^^ + +Ocelot's routing also supports a catch all style routing where the user can specify that they want to match all traffic if you set up your config like below the request will be proxied straight through (it doesnt have to be url any placeholder name will work). + +.. code-block:: json + + { + "DownstreamPathTemplate": "/{url}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/{url}", + "UpstreamHttpMethod": [ "Get" ] + } + +The catch all has a lower priority than any other ReRoute. If you also have the ReRoute below in your config then Ocelot would match it before the catch all. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.10.1", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": [ "Get" ] + } + +Upstream Host +^^^^^^^^^^^^^ + +This feature allows you to have ReRoutes based on the upstream host. This works by looking at the host header the client has used and then using this as part of the information we use to identify a ReRoute. + +In order to use this feature please add the following to your config. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.10.1", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": [ "Get" ], + "UpstreamHost": "somedomain.com" + } + +The ReRoute above will only be matched when the host header value is somedomain.com. + +If you do not set UpstreamHost on a ReRoue then any host header can match it. This is basically a catch all and +preservers existing functionality at the time of building the feature. This means that if you have two ReRoutes that are the same apart from the UpstreamHost where one is null and the other set. Ocelot will favour the one that has been set. + +This feature was requested as part of `Issue 216 `_ . + +Priority +^^^^^^^^ + +In `Issue 270 `_ I finally decided to expose the ReRoute priority in +ocelot.json. This means you can decide in what order you want your ReRoutes to match the Upstream HttpRequest. + +In order to get this working add the following to a ReRoute in ocelot.json, 0 is just an example value here but will explain below. + +.. code-block:: json + + { + "Priority": 0 + } + +0 is the lowest priority, Ocelot will always use 0 for /{catchAll} ReRoutes and this is still hardcoded. After that you are free +to set any priority you wish. + +e.g. you could have + +.. code-block:: json + + { + "UpstreamPathTemplate": "/goods/{catchAll}" + "Priority": 0 + } + +and + +.. code-block:: json + + { + "UpstreamPathTemplate": "/goods/delete" + "Priority": 1 + } + +In the example above if you make a request into Ocelot on /goods/delete Ocelot will match /goods/delete ReRoute. Previously it would have +matched /goods/{catchAll} (because this is the first ReRoute in the list!). + +Dynamic Routing +^^^^^^^^^^^^^^^ + +This feature was requested in `issue 340 `_. The idea is to enable dynamic routing +when using a service discovery provider so you don't have to provide the ReRoute config. See the docs :ref:`service-discovery` if +this sounds interesting to you. + +Query Strings +^^^^^^^^^^^^^ + +Ocelot allow's you to specify a querystring as part of the DownstreamPathTemplate like the example below. + +.. code-block:: json + + { + "ReRoutes": [ + { + "DownstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + "UpstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 50110 + } + ] + } + ], + "GlobalConfiguration": { + "UseServiceDiscovery": false + } + } + +In this example Ocelot will use the value from the {unitId} in the upstream path template and add it to the downstream request as a query string parameter called unitId! Please note you cannot use query string parameters to match routes in the UpstreamPathTemplate. diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index defe20a9..11ee9574 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -1,84 +1,112 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; -using System; -using System.Linq; -using Ocelot.DownstreamRouteFinder.Middleware; -using Ocelot.Responses; -using Ocelot.Values; - -namespace Ocelot.DownstreamUrlCreator.Middleware -{ - public class DownstreamUrlCreatorMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IDownstreamPathPlaceholderReplacer _replacer; - - public DownstreamUrlCreatorMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IDownstreamPathPlaceholderReplacer replacer) - :base(loggerFactory.CreateLogger()) - { - _next = next; - _replacer = replacer; - } - - public async Task Invoke(DownstreamContext context) - { - var dsPath = _replacer - .Replace(context.DownstreamReRoute.DownstreamPathTemplate, context.TemplatePlaceholderNameAndValues); - - if (dsPath.IsError) - { - Logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error"); - - SetPipelineError(context, dsPath.Errors); - return; - } - - context.DownstreamRequest.Scheme = context.DownstreamReRoute.DownstreamScheme; - - if (ServiceFabricRequest(context)) - { - var pathAndQuery = CreateServiceFabricUri(context, dsPath); - context.DownstreamRequest.AbsolutePath = pathAndQuery.path; - context.DownstreamRequest.Query = pathAndQuery.query; - } - else - { - context.DownstreamRequest.AbsolutePath = dsPath.Data.Value; - } - - Logger.LogDebug($"Downstream url is {context.DownstreamRequest}"); - - await _next.Invoke(context); - } - - private (string path, string query) CreateServiceFabricUri(DownstreamContext context, Response dsPath) - { - 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"); - } - - 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"); - } - } -} +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Middleware; +using System; +using System.Linq; +using Ocelot.DownstreamRouteFinder.Middleware; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.DownstreamUrlCreator.Middleware +{ + public class DownstreamUrlCreatorMiddleware : OcelotMiddleware + { + private readonly OcelotRequestDelegate _next; + private readonly IDownstreamPathPlaceholderReplacer _replacer; + + public DownstreamUrlCreatorMiddleware(OcelotRequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IDownstreamPathPlaceholderReplacer replacer) + :base(loggerFactory.CreateLogger()) + { + _next = next; + _replacer = replacer; + } + + public async Task Invoke(DownstreamContext context) + { + var response = _replacer + .Replace(context.DownstreamReRoute.DownstreamPathTemplate, context.TemplatePlaceholderNameAndValues); + + if (response.IsError) + { + Logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error"); + + SetPipelineError(context, response.Errors); + return; + } + + context.DownstreamRequest.Scheme = context.DownstreamReRoute.DownstreamScheme; + + if (ServiceFabricRequest(context)) + { + var pathAndQuery = CreateServiceFabricUri(context, response); + context.DownstreamRequest.AbsolutePath = pathAndQuery.path; + context.DownstreamRequest.Query = pathAndQuery.query; + } + else + { + var dsPath = response.Data; + + if(ContainsQueryString(dsPath)) + { + context.DownstreamRequest.AbsolutePath = GetPath(dsPath); + context.DownstreamRequest.Query = GetQueryString(dsPath); + + // todo - do we need to add anything from the request query string onto the query from the + // templae? + } + else + { + context.DownstreamRequest.AbsolutePath = dsPath.Value; + } + } + + Logger.LogDebug($"Downstream url is {context.DownstreamRequest}"); + + await _next.Invoke(context); + } + + private string GetPath(DownstreamPath dsPath) + { + return dsPath.Value.Substring(0, dsPath.Value.IndexOf("?")); + } + + private string GetQueryString(DownstreamPath dsPath) + { + return dsPath.Value.Substring(dsPath.Value.IndexOf("?")); + } + + private bool ContainsQueryString(DownstreamPath dsPath) + { + return dsPath.Value.Contains("?"); + } + + private (string path, string query) CreateServiceFabricUri(DownstreamContext context, Response dsPath) + { + 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"); + } + + 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/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 3ddb4f87..38ebe18e 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -984,7 +984,7 @@ namespace Ocelot.AcceptanceTests app.Run(async context => { _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - + if(_downstreamPath != basePath) { context.Response.StatusCode = statusCode; diff --git a/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs b/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs new file mode 100644 index 00000000..bc0bac23 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs @@ -0,0 +1,104 @@ +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; + +namespace Ocelot.AcceptanceTests +{ + public class RoutingWithQueryStringTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + private string _downstreamPath; + + public RoutingWithQueryStringTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_return_response_200_with_query_string_template() + { + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 61879, + } + }, + UpstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:61879", $"/api/subscriptions/{subscriptionId}/updates", $"?unitId={unitId}", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/units/{subscriptionId}/{unitId}/updates")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, string queryString, int statusCode, string responseBody) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + + if(context.Request.PathBase.Value != basePath || context.Request.QueryString.Value != queryString) + { + context.Response.StatusCode = 404; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); + }) + .Build(); + + _builder.Start(); + } + + internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) + { + _downstreamPath.ShouldBe(expectedDownstreamPath); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.ManualTest/ocelot.json b/test/Ocelot.ManualTest/ocelot.json index dddb438f..f53ef507 100644 --- a/test/Ocelot.ManualTest/ocelot.json +++ b/test/Ocelot.ManualTest/ocelot.json @@ -1,345 +1,345 @@ { - "ReRoutes": [ - { - "DownstreamPathTemplate": "/profile", - "DownstreamScheme": "http", - "UpstreamPathTemplate": "/profile", - "UpstreamHttpMethod": [ "Get" ], - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 3000 + "ReRoutes": [ + { + "DownstreamPathTemplate": "/profile", + "DownstreamScheme": "http", + "UpstreamPathTemplate": "/profile", + "UpstreamHttpMethod": [ "Get" ], + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 3000 + } + ], + "QoSOptions": { + "TimeoutValue": 360000 } - ], - "QoSOptions": { - "TimeoutValue": 360000 - } - }, - { - "DownstreamPathTemplate": "/api/v1/todo/", - "DownstreamScheme": "http", - "UpstreamPathTemplate": "/api/v1/todo/", - "UpstreamHttpMethod": [ "Get", "Post" ], - "DownstreamHostAndPorts": [ - { - "Host": "lxtodo.azurewebsites.net", - "Port": 80 - } - - ], - "DownstreamHeaderTransform": { - "Location": "{DownstreamBaseUrl}, {BaseUrl}" - } - }, - { - "DownstreamPathTemplate": "/api/values", - "DownstreamScheme": "https", - "UpstreamPathTemplate": "/api/values", - "UpstreamHttpMethod": [ "Get" ], - "DownstreamHostAndPorts": [ - { - "Host": "testapivalues.azurewebsites.net", - "Port": 443 - } - ] - }, - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 52876 - } - ], - "UpstreamPathTemplate": "/identityserverexample", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 }, - "AuthenticationOptions": { - "AuthenticationProviderKey": "TestKey", - "AllowedScopes": [ - "openid", - "offline_access" + { + "DownstreamPathTemplate": "/api/v1/todo/", + "DownstreamScheme": "http", + "UpstreamPathTemplate": "/api/v1/todo/", + "UpstreamHttpMethod": [ "Get", "Post" ], + "DownstreamHostAndPorts": [ + { + "Host": "lxtodo.azurewebsites.net", + "Port": 80 + } + + ], + "DownstreamHeaderTransform": { + "Location": "{DownstreamBaseUrl}, {BaseUrl}" + } + }, + { + "DownstreamPathTemplate": "/api/values", + "DownstreamScheme": "https", + "UpstreamPathTemplate": "/api/values", + "UpstreamHttpMethod": [ "Get" ], + "DownstreamHostAndPorts": [ + { + "Host": "testapivalues.azurewebsites.net", + "Port": 443 + } ] }, - "AddHeadersToRequest": { - "CustomerId": "Claims[CustomerId] > value", - "LocationId": "Claims[LocationId] > value", - "UserType": "Claims[sub] > value[0] > |", - "UserId": "Claims[sub] > value[1] > |" + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 52876 + } + ], + "UpstreamPathTemplate": "/identityserverexample", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "AuthenticationOptions": { + "AuthenticationProviderKey": "TestKey", + "AllowedScopes": [ + "openid", + "offline_access" + ] + }, + "AddHeadersToRequest": { + "CustomerId": "Claims[CustomerId] > value", + "LocationId": "Claims[LocationId] > value", + "UserType": "Claims[sub] > value[0] > |", + "UserId": "Claims[sub] > value[1] > |" + }, + "AddClaimsToRequest": { + "CustomerId": "Claims[CustomerId] > value", + "LocationId": "Claims[LocationId] > value", + "UserType": "Claims[sub] > value[0] > |", + "UserId": "Claims[sub] > value[1] > |" + }, + "AddQueriesToRequest": { + "CustomerId": "Claims[CustomerId] > value", + "LocationId": "Claims[LocationId] > value", + "UserType": "Claims[sub] > value[0] > |", + "UserId": "Claims[sub] > value[1] > |" + }, + "RouteClaimsRequirement": { + "UserType": "registered" + }, + "RequestIdKey": "OcRequestId" }, - "AddClaimsToRequest": { - "CustomerId": "Claims[CustomerId] > value", - "LocationId": "Claims[LocationId] > value", - "UserType": "Claims[sub] > value[0] > |", - "UserId": "Claims[sub] > value[1] > |" - }, - "AddQueriesToRequest": { - "CustomerId": "Claims[CustomerId] > value", - "LocationId": "Claims[LocationId] > value", - "UserType": "Claims[sub] > value[0] > |", - "UserId": "Claims[sub] > value[1] > |" - }, - "RouteClaimsRequirement": { - "UserType": "registered" - }, - "RequestIdKey": "OcRequestId" - }, - { - "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 443 + { + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 443 + } + ], + "UpstreamPathTemplate": "/posts", + "UpstreamHttpMethod": [ "Get" ], + "HttpHandlerOptions": { + "AllowAutoRedirect": true, + "UseCookieContainer": true + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 } - ], - "UpstreamPathTemplate": "/posts", - "UpstreamHttpMethod": [ "Get" ], - "HttpHandlerOptions": { - "AllowAutoRedirect": true, - "UseCookieContainer": true }, - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Get" ], + "RequestIdKey": "ReRouteRequestId", + "HttpHandlerOptions": { + "AllowAutoRedirect": true, + "UseCookieContainer": true, + "UseTracing": true, + "UseProxy": true + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}/comments", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/posts/{postId}/comments", + "UpstreamHttpMethod": [ "Get" ], + "HttpHandlerOptions": { + "AllowAutoRedirect": true, + "UseCookieContainer": true, + "UseTracing": false + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/comments", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/comments", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/posts", + "UpstreamHttpMethod": [ "Post" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Patch" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Delete" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/products", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/products/{productId}", + "UpstreamHttpMethod": [ "Get" ], + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/products", + "UpstreamHttpMethod": [ "Post" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/products/{productId}", + "UpstreamHttpMethod": [ "Put" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/posts/", + "UpstreamHttpMethod": [ "Get" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "www.bbc.co.uk", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/bbc/", + "UpstreamHttpMethod": [ "Get" ] } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Get" ], - "RequestIdKey": "ReRouteRequestId", - "HttpHandlerOptions": { - "AllowAutoRedirect": true, - "UseCookieContainer": true, - "UseTracing": true, - "UseProxy": true - }, - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}/comments", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/posts/{postId}/comments", - "UpstreamHttpMethod": [ "Get" ], - "HttpHandlerOptions": { - "AllowAutoRedirect": true, - "UseCookieContainer": true, - "UseTracing": false - }, - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/comments", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/comments", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/posts", - "UpstreamHttpMethod": [ "Post" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Put" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Patch" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Delete" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/api/products", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/products", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/products/{productId}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/products/{productId}", - "UpstreamHttpMethod": [ "Get" ], - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/products", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/products", - "UpstreamHttpMethod": [ "Post" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/api/products/{productId}", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/products/{productId}", - "UpstreamHttpMethod": [ "Put" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "jsonplaceholder.typicode.com", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/posts/", - "UpstreamHttpMethod": [ "Get" ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "www.bbc.co.uk", - "Port": 80 - } - ], - "UpstreamPathTemplate": "/bbc/", - "UpstreamHttpMethod": [ "Get" ] + ], + + "GlobalConfiguration": { + "RequestIdKey": "ot-traceid" } - ], - - "GlobalConfiguration": { - "RequestIdKey": "ot-traceid" } -} diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index 4e81e59f..98fc7c11 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -1,231 +1,237 @@ -namespace Ocelot.UnitTests.DownstreamUrlCreator -{ - using System; - using System.Collections.Generic; - using System.Net.Http; - using System.Threading.Tasks; - using Moq; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.DownstreamUrlCreator.Middleware; - using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; - using Ocelot.Logging; - using Ocelot.Responses; - using Ocelot.Values; - using TestStack.BDDfy; - using Xunit; - using Shouldly; - using Microsoft.AspNetCore.Http; - using Ocelot.Request.Middleware; - using Ocelot.Configuration; - using Ocelot.Middleware; - - public class DownstreamUrlCreatorMiddlewareTests - { - private readonly Mock _downstreamUrlTemplateVariableReplacer; - private OkResponse _downstreamPath; - private readonly Mock _loggerFactory; - private Mock _logger; - private DownstreamUrlCreatorMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private readonly OcelotRequestDelegate _next; - private readonly HttpRequestMessage _request; - - public DownstreamUrlCreatorMiddlewareTests() - { - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _downstreamUrlTemplateVariableReplacer = new Mock(); - _request = new HttpRequestMessage(HttpMethod.Get, "https://my.url/abc/?q=123"); - _downstreamContext.DownstreamRequest = new DownstreamRequest(_request); - _next = context => Task.CompletedTask; - } - - [Fact] - public void should_replace_scheme_and_path() - { - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithUpstreamHttpMethod(new List {"Get"}) - .WithDownstreamScheme("https") - .Build(); - - var config = new ServiceProviderConfigurationBuilder() - .Build(); - - this.Given(x => x.GivenTheDownStreamRouteIs( - new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123")) - .And(x => GivenTheServiceProviderConfigIs(config)) - .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamRequestUriIs("https://my.url:80/api/products/1?q=123")) - .BDDfy(); - } - - [Fact] - public void should_not_create_service_fabric_url() - { - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithDownstreamScheme("https") - .Build(); - - var config = new ServiceProviderConfigurationBuilder() - .WithType("ServiceFabric") - .WithHost("localhost") - .WithPort(19081) - .Build(); - - this.Given(x => x.GivenTheDownStreamRouteIs( - new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123")) - .And(x => GivenTheServiceProviderConfigIs(config)) - .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamRequestUriIs("https://my.url:80/api/products/1?q=123")) - .BDDfy(); - } - - [Fact] - public void should_create_service_fabric_url() - { - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamScheme("http") - .WithServiceName("Ocelot/OcelotApp") - .WithUseServiceDiscovery(true) - .Build(); - - var downstreamRoute = new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .Build()); - - var config = new ServiceProviderConfigurationBuilder() - .WithType("ServiceFabric") - .WithHost("localhost") - .WithPort(19081) - .Build(); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenTheServiceProviderConfigIs(config)) - .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")) - .BDDfy(); - } - - [Fact] - public void should_create_service_fabric_url_with_query_string_for_stateless_service() - { - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamScheme("http") - .WithServiceName("Ocelot/OcelotApp") - .WithUseServiceDiscovery(true) - .Build(); - - var downstreamRoute = new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .Build()); - - var config = new ServiceProviderConfigurationBuilder() - .WithType("ServiceFabric") - .WithHost("localhost") - .WithPort(19081) - .Build(); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenTheServiceProviderConfigIs(config)) - .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")) - .BDDfy(); - } - - [Fact] - public void should_create_service_fabric_url_with_query_string_for_stateful_service() - { - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamScheme("http") - .WithServiceName("Ocelot/OcelotApp") - .WithUseServiceDiscovery(true) - .Build(); - - var downstreamRoute = new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .Build()); - - var config = new ServiceProviderConfigurationBuilder() - .WithType("ServiceFabric") - .WithHost("localhost") - .WithPort(19081) - .Build(); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenTheServiceProviderConfigIs(config)) - .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081?PartitionKind=test&PartitionKey=1")) - .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1?PartitionKind=test&PartitionKey=1")) - .BDDfy(); - } - - private void GivenTheServiceProviderConfigIs(ServiceProviderConfiguration config) +namespace Ocelot.UnitTests.DownstreamUrlCreator +{ + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Threading.Tasks; + using Moq; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.DownstreamUrlCreator.Middleware; + using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; + using Ocelot.Logging; + using Ocelot.Responses; + using Ocelot.Values; + using TestStack.BDDfy; + using Xunit; + using Shouldly; + using Microsoft.AspNetCore.Http; + using Ocelot.Request.Middleware; + using Ocelot.Configuration; + using Ocelot.Middleware; + + public class DownstreamUrlCreatorMiddlewareTests + { + private readonly Mock _downstreamUrlTemplateVariableReplacer; + private OkResponse _downstreamPath; + private readonly Mock _loggerFactory; + private Mock _logger; + private DownstreamUrlCreatorMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; + private readonly OcelotRequestDelegate _next; + private readonly HttpRequestMessage _request; + + public DownstreamUrlCreatorMiddlewareTests() { - var configuration = new InternalConfiguration(null, null, config, null, null, null, null, null); - _downstreamContext.Configuration = configuration; - } - - private void WhenICallTheMiddleware() - { - _middleware = new DownstreamUrlCreatorMiddleware(_next, _loggerFactory.Object, _downstreamUrlTemplateVariableReplacer.Object); - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; - _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; - } - - private void GivenTheDownstreamRequestUriIs(string uri) - { - _request.RequestUri = new Uri(uri); - _downstreamContext.DownstreamRequest = new DownstreamRequest(_request); - } - - private void GivenTheUrlReplacerWillReturn(string path) - { - _downstreamPath = new OkResponse(new DownstreamPath(path)); - _downstreamUrlTemplateVariableReplacer - .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) - .Returns(_downstreamPath); - } - - private void ThenTheDownstreamRequestUriIs(string expectedUri) - { - _downstreamContext.DownstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri); - } - } -} + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _downstreamUrlTemplateVariableReplacer = new Mock(); + _request = new HttpRequestMessage(HttpMethod.Get, "https://my.url/abc/?q=123"); + _downstreamContext.DownstreamRequest = new DownstreamRequest(_request); + _next = context => Task.CompletedTask; + } + + [Fact] + public void should_replace_scheme_and_path() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithUpstreamHttpMethod(new List {"Get"}) + .WithDownstreamScheme("https") + .Build(); + + var config = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123")) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("https://my.url:80/api/products/1?q=123")) + .And(x => ThenTheQueryStringIs("?q=123")) + .BDDfy(); + } + + [Fact] + public void should_not_create_service_fabric_url() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithDownstreamScheme("https") + .Build(); + + var config = new ServiceProviderConfigurationBuilder() + .WithType("ServiceFabric") + .WithHost("localhost") + .WithPort(19081) + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123")) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("https://my.url:80/api/products/1?q=123")) + .BDDfy(); + } + + [Fact] + public void should_create_service_fabric_url() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamScheme("http") + .WithServiceName("Ocelot/OcelotApp") + .WithUseServiceDiscovery(true) + .Build(); + + var downstreamRoute = new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .Build()); + + var config = new ServiceProviderConfigurationBuilder() + .WithType("ServiceFabric") + .WithHost("localhost") + .WithPort(19081) + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenTheServiceProviderConfigIs(config)) + .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")) + .BDDfy(); + } + + [Fact] + public void should_create_service_fabric_url_with_query_string_for_stateless_service() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamScheme("http") + .WithServiceName("Ocelot/OcelotApp") + .WithUseServiceDiscovery(true) + .Build(); + + var downstreamRoute = new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .Build()); + + var config = new ServiceProviderConfigurationBuilder() + .WithType("ServiceFabric") + .WithHost("localhost") + .WithPort(19081) + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenTheServiceProviderConfigIs(config)) + .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")) + .BDDfy(); + } + + [Fact] + public void should_create_service_fabric_url_with_query_string_for_stateful_service() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamScheme("http") + .WithServiceName("Ocelot/OcelotApp") + .WithUseServiceDiscovery(true) + .Build(); + + var downstreamRoute = new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .Build()); + + var config = new ServiceProviderConfigurationBuilder() + .WithType("ServiceFabric") + .WithHost("localhost") + .WithPort(19081) + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081?PartitionKind=test&PartitionKey=1")) + .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1?PartitionKind=test&PartitionKey=1")) + .BDDfy(); + } + + private void GivenTheServiceProviderConfigIs(ServiceProviderConfiguration config) + { + var configuration = new InternalConfiguration(null, null, config, null, null, null, null, null); + _downstreamContext.Configuration = configuration; + } + + private void WhenICallTheMiddleware() + { + _middleware = new DownstreamUrlCreatorMiddleware(_next, _loggerFactory.Object, _downstreamUrlTemplateVariableReplacer.Object); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; + _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; + } + + private void GivenTheDownstreamRequestUriIs(string uri) + { + _request.RequestUri = new Uri(uri); + _downstreamContext.DownstreamRequest = new DownstreamRequest(_request); + } + + private void GivenTheUrlReplacerWillReturn(string path) + { + _downstreamPath = new OkResponse(new DownstreamPath(path)); + _downstreamUrlTemplateVariableReplacer + .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) + .Returns(_downstreamPath); + } + + private void ThenTheDownstreamRequestUriIs(string expectedUri) + { + _downstreamContext.DownstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri); + } + + private void ThenTheQueryStringIs(string queryString) + { + _downstreamContext.DownstreamRequest.Query.ShouldBe(queryString); + } + } +} From 69d766b01df5a95ed1a3929a1ac314f9533be2dd Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Tue, 10 Jul 2018 18:06:28 +0100 Subject: [PATCH 11/16] #446 link to issue that might help people with okta integration --- docs/features/authentication.rst | 293 ++++++++++++++++--------------- 1 file changed, 149 insertions(+), 144 deletions(-) diff --git a/docs/features/authentication.rst b/docs/features/authentication.rst index 2ab0b11d..a7599c32 100644 --- a/docs/features/authentication.rst +++ b/docs/features/authentication.rst @@ -1,144 +1,149 @@ -Authentication -============== - -In order to authenticate ReRoutes and subsequently use any of Ocelot's claims based features such as authorisation or modifying the request with values from the token. Users must register authentication services in their Startup.cs as usual but they provide a scheme (authentication provider key) with each registration e.g. - -.. code-block:: csharp - - public void ConfigureServices(IServiceCollection services) - { - var authenticationProviderKey = "TestKey"; - - services.AddAuthentication() - .AddJwtBearer(authenticationProviderKey, x => - { - }); - } - - -In this example TestKey is the scheme that this provider has been registered with. -We then map this to a ReRoute in the configuration e.g. - -.. code-block:: json - - "ReRoutes": [{ - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51876, - } - ], - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": ["Post"], - "ReRouteIsCaseSensitive": false, - "DownstreamScheme": "http", - "AuthenticationOptions": { - "AuthenticationProviderKey": "TestKey", - "AllowedScopes": [] - } - }] - -When Ocelot runs it will look at this ReRoutes AuthenticationOptions.AuthenticationProviderKey -and check that there is an Authentication provider registered with the given key. If there isn't then Ocelot -will not start up, if there is then the ReRoute will use that provider when it executes. - -If a ReRoute is authenticated Ocelot will invoke whatever scheme is associated with it while executing the authentication middleware. If the request fails authentication Ocelot returns a http status code 401. - -JWT Tokens -^^^^^^^^^^ - -If you want to authenticate using JWT tokens maybe from a provider like Auth0 you can register your authentication middleware as normal e.g. - -.. code-block:: csharp - - public void ConfigureServices(IServiceCollection services) - { - var authenticationProviderKey = "TestKey"; - - services.AddAuthentication() - .AddJwtBearer(authenticationProviderKey, x => - { - x.Authority = "test"; - x.Audience = "test"; - }); - - services.AddOcelot(); - } - -Then map the authentication provider key to a ReRoute in your configuration e.g. - -.. code-block:: json - - "ReRoutes": [{ - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51876, - } - ], - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": ["Post"], - "ReRouteIsCaseSensitive": false, - "DownstreamScheme": "http", - "AuthenticationOptions": { - "AuthenticationProviderKey": "TestKey", - "AllowedScopes": [] - } - }] - - - -Identity Server Bearer Tokens -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -In order to use IdentityServer bearer tokens register your IdentityServer services as usual in ConfigureServices with a scheme (key). If you don't understand how to do this please consult the IdentityServer documentation. - -.. code-block:: csharp - - public void ConfigureServices(IServiceCollection services) - { - var authenticationProviderKey = "TestKey"; - Action options = o => - { - o.Authority = "https://whereyouridentityserverlives.com"; - o.ApiName = "api"; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = "secret"; - }; - - services.AddAuthentication() - .AddIdentityServerAuthentication(authenticationProviderKey, options); - - services.AddOcelot(); - } - -Then map the authentication provider key to a ReRoute in your configuration e.g. - -.. code-block:: json - - "ReRoutes": [{ - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51876, - } - ], - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": ["Post"], - "ReRouteIsCaseSensitive": false, - "DownstreamScheme": "http", - "AuthenticationOptions": { - "AuthenticationProviderKey": "TestKey", - "AllowedScopes": [] - } - }] - -Allowed Scopes -^^^^^^^^^^^^^ - -If you add scopes to AllowedScopes Ocelot will get all the user claims (from the token) of the type scope and make sure that the user has all of the scopes in the list. - -This is a way to restrict access to a ReRoute on a per scope basis. +Authentication +============== + +In order to authenticate ReRoutes and subsequently use any of Ocelot's claims based features such as authorisation or modifying the request with values from the token. Users must register authentication services in their Startup.cs as usual but they provide a scheme (authentication provider key) with each registration e.g. + +.. code-block:: csharp + + public void ConfigureServices(IServiceCollection services) + { + var authenticationProviderKey = "TestKey"; + + services.AddAuthentication() + .AddJwtBearer(authenticationProviderKey, x => + { + }); + } + + +In this example TestKey is the scheme that this provider has been registered with. +We then map this to a ReRoute in the configuration e.g. + +.. code-block:: json + + "ReRoutes": [{ + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51876, + } + ], + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": ["Post"], + "ReRouteIsCaseSensitive": false, + "DownstreamScheme": "http", + "AuthenticationOptions": { + "AuthenticationProviderKey": "TestKey", + "AllowedScopes": [] + } + }] + +When Ocelot runs it will look at this ReRoutes AuthenticationOptions.AuthenticationProviderKey +and check that there is an Authentication provider registered with the given key. If there isn't then Ocelot +will not start up, if there is then the ReRoute will use that provider when it executes. + +If a ReRoute is authenticated Ocelot will invoke whatever scheme is associated with it while executing the authentication middleware. If the request fails authentication Ocelot returns a http status code 401. + +JWT Tokens +^^^^^^^^^^ + +If you want to authenticate using JWT tokens maybe from a provider like Auth0 you can register your authentication middleware as normal e.g. + +.. code-block:: csharp + + public void ConfigureServices(IServiceCollection services) + { + var authenticationProviderKey = "TestKey"; + + services.AddAuthentication() + .AddJwtBearer(authenticationProviderKey, x => + { + x.Authority = "test"; + x.Audience = "test"; + }); + + services.AddOcelot(); + } + +Then map the authentication provider key to a ReRoute in your configuration e.g. + +.. code-block:: json + + "ReRoutes": [{ + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51876, + } + ], + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": ["Post"], + "ReRouteIsCaseSensitive": false, + "DownstreamScheme": "http", + "AuthenticationOptions": { + "AuthenticationProviderKey": "TestKey", + "AllowedScopes": [] + } + }] + + + +Identity Server Bearer Tokens +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In order to use IdentityServer bearer tokens register your IdentityServer services as usual in ConfigureServices with a scheme (key). If you don't understand how to do this please consult the IdentityServer documentation. + +.. code-block:: csharp + + public void ConfigureServices(IServiceCollection services) + { + var authenticationProviderKey = "TestKey"; + Action options = o => + { + o.Authority = "https://whereyouridentityserverlives.com"; + o.ApiName = "api"; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }; + + services.AddAuthentication() + .AddIdentityServerAuthentication(authenticationProviderKey, options); + + services.AddOcelot(); + } + +Then map the authentication provider key to a ReRoute in your configuration e.g. + +.. code-block:: json + + "ReRoutes": [{ + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51876, + } + ], + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": ["Post"], + "ReRouteIsCaseSensitive": false, + "DownstreamScheme": "http", + "AuthenticationOptions": { + "AuthenticationProviderKey": "TestKey", + "AllowedScopes": [] + } + }] + +Okta +^^^^ + +I have not had time to write this up but we have `Issue 446 `_ that contains some code and examples that might help with Okta integration. + +Allowed Scopes +^^^^^^^^^^^^^ + +If you add scopes to AllowedScopes Ocelot will get all the user claims (from the token) of the type scope and make sure that the user has all of the scopes in the list. + +This is a way to restrict access to a ReRoute on a per scope basis. From 26ef3faa11a23cb9ebc962fd87d382fdbd3a8ed9 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Wed, 11 Jul 2018 06:28:08 +0100 Subject: [PATCH 12/16] upgraded packages to netcoreapp2.1 Ocelot is still netstandard2.0 (#455) * upgraded packages to netcoreapp2.1 Ocelot is still netstandard2.0 * try get latest sdk travis * try get specific sdk travis --- .travis.yml | 2 +- src/Ocelot/Ocelot.csproj | 48 +++++++++---------- .../Ocelot.AcceptanceTests.csproj | 27 +++++------ .../Ocelot.Benchmarks.csproj | 3 +- .../Ocelot.IntegrationTests.csproj | 27 +++++------ .../Ocelot.ManualTest.csproj | 28 ++++++----- test/Ocelot.UnitTests/Ocelot.UnitTests.csproj | 25 +++++----- 7 files changed, 80 insertions(+), 80 deletions(-) diff --git a/.travis.yml b/.travis.yml index ef0701aa..192d14b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ osx_image: xcode9.2 mono: - 4.4.2 -dotnet: 2.1.4 +dotnet: 2.1.301 before_install: - git fetch --unshallow # Travis always does a shallow clone, but GitVersion needs the full history including branches and tags diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index 8224be2c..4d13aeda 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -25,37 +25,37 @@ True - + NU1701 - - - - - - - - - - + + + + + + + + + + NU1701 - - - - + + + + all - - - - - - - - - + + + + + + + + + diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index dd9d855c..c4e902fd 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -1,8 +1,7 @@  0.0.0-dev - netcoreapp2.0 - 2.0.0 + netcoreapp2.1 Ocelot.AcceptanceTests Exe Ocelot.AcceptanceTests @@ -34,25 +33,25 @@ - - - + + + all - - - - - - - - + + + + + + + + - + diff --git a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj index e97450d3..e7d255c9 100644 --- a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj +++ b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj @@ -2,8 +2,7 @@ 0.0.0-dev - netcoreapp2.0 - 2.0.0 + netcoreapp2.1 Ocelot.Benchmarks Exe Ocelot.Benchmarks diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj index 5b2cac21..19bf854f 100644 --- a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj @@ -1,8 +1,7 @@ - + 0.0.0-dev - netcoreapp2.0 - 2.0.0 + netcoreapp2.1 Ocelot.IntegrationTests Exe Ocelot.IntegrationTests @@ -25,26 +24,26 @@ - - + + all - - - - - - - + + + + + + + - + - + diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj index b7c3ac18..d9ddff9c 100644 --- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj +++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj @@ -1,8 +1,7 @@ - + 0.0.0-dev - netcoreapp2.0 - 2.0.0 + netcoreapp2.1 true Ocelot.ManualTest Exe @@ -15,6 +14,11 @@ PreserveNewest + + + PreserveNewest + + PreserveNewest @@ -24,15 +28,15 @@ - - - - - - - - - + + + + + + + + + all diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index a1908e7a..cb7110b4 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -2,8 +2,7 @@ 0.0.0-dev - netcoreapp2.0 - 2.0.0 + netcoreapp2.1 Ocelot.UnitTests Ocelot.UnitTests Exe @@ -38,22 +37,22 @@ - - - + + + all - - - - - - - - + + + + + + + + From 19ea93d10ee637ede45c2f91c6df55c65d0dd53e Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Wed, 11 Jul 2018 06:34:35 +0100 Subject: [PATCH 13/16] +semver: breaking and updated docs, this is breaking because i missed it with last release that remove delegating handlers and there are a lot of package upgrades --- README.md | 2 +- docs/introduction/gettingstarted.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 84455545..e3128609 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio ## How to install -Ocelot is designed to work with ASP.NET Core only and it targets `netstandard2.0`. This means it can be used anywhere `.NET Standard 2.0` is supported, including `.NET Core 2.0` and `.NET Framework 4.6.1` and up. [This](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) documentation may prove helpful when working out if Ocelot would be suitable for you. +Ocelot is designed to work with ASP.NET Core only and it targets `netstandard2.0`. This means it can be used anywhere `.NET Standard 2.0` is supported, including `.NET Core 2.1` and `.NET Framework 4.7.2` and up. [This](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) documentation may prove helpful when working out if Ocelot would be suitable for you. Install Ocelot and it's dependencies using NuGet. diff --git a/docs/introduction/gettingstarted.rst b/docs/introduction/gettingstarted.rst index f523ea1b..0bb0d5d1 100644 --- a/docs/introduction/gettingstarted.rst +++ b/docs/introduction/gettingstarted.rst @@ -4,7 +4,7 @@ Getting Started Ocelot is designed to work with .NET Core only and is currently built to netstandard2.0 `this `_ documentation may prove helpful when working out if Ocelot would be suitable for you. -.NET Core 2.0 +.NET Core 2.1 ^^^^^^^^^^^^^ **Install NuGet package** From 8f4ae03290ce4621821396c0baf4d9c227fc8c38 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Thu, 12 Jul 2018 19:26:23 +0100 Subject: [PATCH 14/16] Added support for query string parameters in upstream path template (#467) --- docs/features/routing.rst | 31 +++- .../Creator/UpstreamTemplatePatternCreator.cs | 17 +- .../Finder/DownstreamRouteCreator.cs | 2 +- .../Finder/DownstreamRouteFinder.cs | 12 +- .../Finder/IDownstreamRouteProvider.cs | 2 +- .../DownstreamRouteFinderMiddleware.cs | 4 +- .../IPlaceholderNameAndValueFinder.cs | 2 +- .../IUrlPathToUrlTemplateMatcher.cs | 4 +- .../UrlMatcher/RegExUrlMatcher.cs | 11 +- .../UrlPathPlaceholderNameAndValueFinder.cs | 50 +++++- .../DownstreamUrlCreatorMiddleware.cs | 40 +++-- .../Creator/DownstreamRequestCreator.cs | 6 +- .../DownstreamRequestInitialiserMiddleware.cs | 1 + src/Ocelot/Values/UpstreamPathTemplate.cs | 6 +- .../RoutingWithQueryStringTests.cs | 149 +++++++++++++++++- ...lPathToUrlPathTemplateMatcherBenchmarks.cs | 3 +- .../FileInternalConfigurationCreatorTests.cs | 6 +- .../UpstreamTemplatePatternCreatorTests.cs | 32 +++- .../DownstreamRouteCreatorTests.cs | 5 +- .../DownstreamRouteFinderMiddlewareTests.cs | 2 +- .../DownstreamRouteFinderTests.cs | 147 ++++++++--------- .../UrlMatcher/RegExUrlMatcherTests.cs | 85 ++++++++-- ...lPathPlaceholderNameAndValueFinderTests.cs | 93 ++++++++++- .../DownstreamUrlCreatorMiddlewareTests.cs | 97 ++++++++++++ 24 files changed, 664 insertions(+), 143 deletions(-) diff --git a/docs/features/routing.rst b/docs/features/routing.rst index abcfc7d2..e72b5ef8 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -215,4 +215,33 @@ Ocelot allow's you to specify a querystring as part of the DownstreamPathTemplat } } -In this example Ocelot will use the value from the {unitId} in the upstream path template and add it to the downstream request as a query string parameter called unitId! Please note you cannot use query string parameters to match routes in the UpstreamPathTemplate. +In this example Ocelot will use the value from the {unitId} in the upstream path template and add it to the downstream request as a query string parameter called unitId! + +Ocelot will also allow you to put query string parametrs in the UpstreamPathTemplate so you can match certain queries to certain services. + +.. code-block:: json + + { + "ReRoutes": [ + { + "DownstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates", + "UpstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 50110 + } + ] + } + ], + "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 in this example Ocelot will swap the unitId param from the query string and use it in the downstream request path. \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs index 27328213..90ad721d 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs @@ -14,7 +14,8 @@ namespace Ocelot.Configuration.Creator public UpstreamPathTemplate Create(IReRoute reRoute) { - var upstreamTemplate = reRoute.UpstreamPathTemplate; + var upstreamTemplate = reRoute.UpstreamPathTemplate; + var placeholders = new List(); @@ -30,9 +31,17 @@ namespace Ocelot.Configuration.Creator //hack to handle /{url} case if(ForwardSlashAndOnePlaceHolder(upstreamTemplate, placeholders, postitionOfPlaceHolderClosingBracket)) { - return new UpstreamPathTemplate(RegExForwardSlashAndOnePlaceHolder, 0); + return new UpstreamPathTemplate(RegExForwardSlashAndOnePlaceHolder, 0, false); } } + } + + var containsQueryString = false; + + if (upstreamTemplate.Contains("?")) + { + containsQueryString = true; + upstreamTemplate = upstreamTemplate.Replace("?", "\\?"); } foreach (var placeholder in placeholders) @@ -42,7 +51,7 @@ namespace Ocelot.Configuration.Creator if (upstreamTemplate == "/") { - return new UpstreamPathTemplate(RegExForwardSlashOnly, reRoute.Priority); + return new UpstreamPathTemplate(RegExForwardSlashOnly, reRoute.Priority, containsQueryString); } if(upstreamTemplate.EndsWith("/")) @@ -54,7 +63,7 @@ namespace Ocelot.Configuration.Creator ? $"^{upstreamTemplate}{RegExMatchEndString}" : $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; - return new UpstreamPathTemplate(route, reRoute.Priority); + return new UpstreamPathTemplate(route, reRoute.Priority, containsQueryString); } private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List placeholders, int postitionOfPlaceHolderClosingBracket) diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs index b654d32d..9a90edaf 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs @@ -21,7 +21,7 @@ _cache = new ConcurrentDictionary>(); } - public Response Get(string upstreamUrlPath, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost) + public Response Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost) { var serviceName = GetServiceName(upstreamUrlPath); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index b236c291..7c7264de 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -18,7 +18,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder _placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; } - public Response Get(string path, string httpMethod, IInternalConfiguration configuration, string upstreamHost) + public Response Get(string upstreamUrlPath, string upstreamQueryString, string httpMethod, IInternalConfiguration configuration, string upstreamHost) { var downstreamRoutes = new List(); @@ -28,11 +28,11 @@ namespace Ocelot.DownstreamRouteFinder.Finder foreach (var reRoute in applicableReRoutes) { - var urlMatch = _urlMatcher.Match(path, reRoute.UpstreamTemplatePattern.Template); + var urlMatch = _urlMatcher.Match(upstreamUrlPath, upstreamQueryString, reRoute.UpstreamTemplatePattern.Template, reRoute.UpstreamTemplatePattern.ContainsQueryString); if (urlMatch.Data.Match) { - downstreamRoutes.Add(GetPlaceholderNamesAndValues(path, reRoute)); + downstreamRoutes.Add(GetPlaceholderNamesAndValues(upstreamUrlPath, upstreamQueryString, reRoute)); } } @@ -44,7 +44,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder return notNullOption != null ? new OkResponse(notNullOption) : new OkResponse(nullOption); } - return new ErrorResponse(new UnableToFindDownstreamRouteError(path, httpMethod)); + return new ErrorResponse(new UnableToFindDownstreamRouteError(upstreamUrlPath, httpMethod)); } private bool RouteIsApplicableToThisRequest(ReRoute reRoute, string httpMethod, string upstreamHost) @@ -53,9 +53,9 @@ namespace Ocelot.DownstreamRouteFinder.Finder (string.IsNullOrEmpty(reRoute.UpstreamHost) || reRoute.UpstreamHost == upstreamHost); } - private DownstreamRoute GetPlaceholderNamesAndValues(string path, ReRoute reRoute) + private DownstreamRoute GetPlaceholderNamesAndValues(string path, string query, ReRoute reRoute) { - var templatePlaceholderNameAndValues = _placeholderNameAndValueFinder.Find(path, reRoute.UpstreamPathTemplate.Value); + var templatePlaceholderNameAndValues = _placeholderNameAndValueFinder.Find(path, query, reRoute.UpstreamPathTemplate.Value); return new DownstreamRoute(templatePlaceholderNameAndValues.Data, reRoute); } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs index df14065d..0a84f8f4 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteProvider.cs @@ -6,6 +6,6 @@ namespace Ocelot.DownstreamRouteFinder.Finder { public interface IDownstreamRouteProvider { - Response Get(string upstreamUrlPath, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost); + Response Get(string upstreamUrlPath, string upstreamQueryString, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost); } } diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index 5c40228d..b6c4bcf6 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -33,13 +33,15 @@ namespace Ocelot.DownstreamRouteFinder.Middleware { var upstreamUrlPath = context.HttpContext.Request.Path.ToString(); + var upstreamQueryString = context.HttpContext.Request.QueryString.ToString(); + var upstreamHost = context.HttpContext.Request.Headers["Host"]; Logger.LogDebug($"Upstream url path is {upstreamUrlPath}"); var provider = _factory.Get(context.Configuration); - var downstreamRoute = provider.Get(upstreamUrlPath, context.HttpContext.Request.Method, context.Configuration, upstreamHost); + var downstreamRoute = provider.Get(upstreamUrlPath, upstreamQueryString, context.HttpContext.Request.Method, context.Configuration, upstreamHost); if (downstreamRoute.IsError) { diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IPlaceholderNameAndValueFinder.cs index 678b1081..986d7b84 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IPlaceholderNameAndValueFinder.cs @@ -5,6 +5,6 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher { public interface IPlaceholderNameAndValueFinder { - Response> Find(string path, string pathTemplate); + Response> Find(string path, string query, string pathTemplate); } } diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs index 1a9f147a..bac96d53 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs @@ -4,6 +4,6 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher { public interface IUrlPathToUrlTemplateMatcher { - Response Match(string upstreamUrlPath, string upstreamUrlPathTemplate); + Response Match(string upstreamUrlPath, string upstreamQueryString, string upstreamUrlPathTemplate, bool containsQueryString); } -} \ No newline at end of file +} diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs index 793d33cf..d1706118 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs @@ -5,11 +5,18 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher { public class RegExUrlMatcher : IUrlPathToUrlTemplateMatcher { - public Response Match(string upstreamUrlPath, string upstreamUrlPathTemplate) + public Response Match(string upstreamUrlPath, string upstreamQueryString, string upstreamUrlPathTemplate, bool containsQueryString) { var regex = new Regex(upstreamUrlPathTemplate); - return regex.IsMatch(upstreamUrlPath) + if (!containsQueryString) + { + return regex.IsMatch(upstreamUrlPath) + ? new OkResponse(new UrlMatch(true)) + : new OkResponse(new UrlMatch(false)); + } + + return regex.IsMatch($"{upstreamUrlPath}{upstreamQueryString}") ? new OkResponse(new UrlMatch(true)) : new OkResponse(new UrlMatch(false)); } diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs index 4471618f..6f4fec00 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs @@ -5,27 +5,46 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher { public class UrlPathPlaceholderNameAndValueFinder : IPlaceholderNameAndValueFinder { - public Response> Find(string path, string pathTemplate) + 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, placeholderName, path, counterForPath); + var placeholderValue = GetPlaceholderValue(pathTemplate, query, placeholderName, path, counterForPath, delimiter); placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); counterForTemplate = GetNextCounterPosition(pathTemplate, counterForTemplate, '}'); - counterForPath = GetNextCounterPosition(path, counterForPath, '/'); + counterForPath = GetNextCounterPosition(path, counterForPath, nextDelimiter); continue; } @@ -44,7 +63,7 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher } else { - var placeholderValue = GetPlaceholderValue(pathTemplate, placeholderName, path, counterForPath + 1); + var placeholderValue = GetPlaceholderValue(pathTemplate, query, placeholderName, path, counterForPath + 1, '/'); placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); } @@ -57,6 +76,21 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher 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 @@ -69,11 +103,11 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher return path.Length == 1 || path.Length == 0; } - private string GetPlaceholderValue(string urlPathTemplate, string variableName, string urlPath, int counterForUrl) + private string GetPlaceholderValue(string urlPathTemplate, string query, string variableName, string urlPath, int counterForUrl, char delimiter) { - var positionOfNextSlash = urlPath.IndexOf('/', counterForUrl); + var positionOfNextSlash = urlPath.IndexOf(delimiter, counterForUrl); - if (positionOfNextSlash == -1 || urlPathTemplate.Trim('/').EndsWith(variableName)) + if (positionOfNextSlash == -1 || (urlPathTemplate.Trim(delimiter).EndsWith(variableName) && string.IsNullOrEmpty(query))) { positionOfNextSlash = urlPath.Length; } diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 11ee9574..6057e58a 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -1,17 +1,15 @@ using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; -using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; using System; -using System.Linq; -using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Responses; using Ocelot.Values; namespace Ocelot.DownstreamUrlCreator.Middleware { + using System.Text.RegularExpressions; + public class DownstreamUrlCreatorMiddleware : OcelotMiddleware { private readonly OcelotRequestDelegate _next; @@ -55,12 +53,11 @@ namespace Ocelot.DownstreamUrlCreator.Middleware { context.DownstreamRequest.AbsolutePath = GetPath(dsPath); context.DownstreamRequest.Query = GetQueryString(dsPath); - - // todo - do we need to add anything from the request query string onto the query from the - // templae? } - else + else { + RemoveQueryStringParametersThatHaveBeenUsedInTemplate(context); + context.DownstreamRequest.AbsolutePath = dsPath.Value; } } @@ -70,14 +67,37 @@ namespace Ocelot.DownstreamUrlCreator.Middleware await _next.Invoke(context); } + private static void RemoveQueryStringParametersThatHaveBeenUsedInTemplate(DownstreamContext context) + { + foreach (var nAndV in context.TemplatePlaceholderNameAndValues) + { + var name = nAndV.Name.Replace("{", "").Replace("}", ""); + + if (context.DownstreamRequest.Query.Contains(name) && + context.DownstreamRequest.Query.Contains(nAndV.Value)) + { + var questionMarkOrAmpersand = context.DownstreamRequest.Query.IndexOf(name, StringComparison.Ordinal); + context.DownstreamRequest.Query = context.DownstreamRequest.Query.Remove(questionMarkOrAmpersand - 1, 1); + + var rgx = new Regex($@"\b{name}={nAndV.Value}\b"); + context.DownstreamRequest.Query = rgx.Replace(context.DownstreamRequest.Query, ""); + + if (!string.IsNullOrEmpty(context.DownstreamRequest.Query)) + { + context.DownstreamRequest.Query = '?' + context.DownstreamRequest.Query.Substring(1); + } + } + } + } + private string GetPath(DownstreamPath dsPath) { - return dsPath.Value.Substring(0, dsPath.Value.IndexOf("?")); + return dsPath.Value.Substring(0, dsPath.Value.IndexOf("?", StringComparison.Ordinal)); } private string GetQueryString(DownstreamPath dsPath) { - return dsPath.Value.Substring(dsPath.Value.IndexOf("?")); + return dsPath.Value.Substring(dsPath.Value.IndexOf("?", StringComparison.Ordinal)); } private bool ContainsQueryString(DownstreamPath dsPath) diff --git a/src/Ocelot/Request/Creator/DownstreamRequestCreator.cs b/src/Ocelot/Request/Creator/DownstreamRequestCreator.cs index e4bdb1fc..352e70f3 100644 --- a/src/Ocelot/Request/Creator/DownstreamRequestCreator.cs +++ b/src/Ocelot/Request/Creator/DownstreamRequestCreator.cs @@ -2,13 +2,12 @@ namespace Ocelot.Request.Creator { using System.Net.Http; using Ocelot.Request.Middleware; - using System.Runtime.InteropServices; using Ocelot.Infrastructure; public class DownstreamRequestCreator : IDownstreamRequestCreator { private readonly IFrameworkDescription _framework; - private const string dotNetFramework = ".NET Framework"; + private const string DotNetFramework = ".NET Framework"; public DownstreamRequestCreator(IFrameworkDescription framework) { @@ -24,8 +23,7 @@ namespace Ocelot.Request.Creator * And MS HttpClient in Full Framework actually rejects it. * see #366 issue **/ - - if(_framework.Get().Contains(dotNetFramework)) + if(_framework.Get().Contains(DotNetFramework)) { if (request.Method == HttpMethod.Get || request.Method == HttpMethod.Head || diff --git a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs index ce226d98..4f3151d9 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs @@ -28,6 +28,7 @@ namespace Ocelot.Request.Middleware public async Task Invoke(DownstreamContext context) { var downstreamRequest = await _requestMapper.Map(context.HttpContext.Request); + if (downstreamRequest.IsError) { SetPipelineError(context, downstreamRequest.Errors); diff --git a/src/Ocelot/Values/UpstreamPathTemplate.cs b/src/Ocelot/Values/UpstreamPathTemplate.cs index 4b33a230..b42c887e 100644 --- a/src/Ocelot/Values/UpstreamPathTemplate.cs +++ b/src/Ocelot/Values/UpstreamPathTemplate.cs @@ -2,14 +2,16 @@ namespace Ocelot.Values { public class UpstreamPathTemplate { - public UpstreamPathTemplate(string template, int priority) + public UpstreamPathTemplate(string template, int priority, bool containsQueryString) { Template = template; Priority = priority; + ContainsQueryString = containsQueryString; } public string Template { get; } - public int Priority { get; } + public int Priority { get; } + public bool ContainsQueryString { get; } } } diff --git a/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs b/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs index bc0bac23..f97340af 100644 --- a/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs @@ -60,6 +60,152 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_return_response_200_with_query_string_upstream_template() + { + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 64879, + } + }, + UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:64879", $"/api/units/{subscriptionId}/{unitId}/updates", "", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/subscriptions/{subscriptionId}/updates?unitId={unitId}")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_404_with_query_string_upstream_template_no_query_string() + { + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 64879, + } + }, + UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:64879", $"/api/units/{subscriptionId}/{unitId}/updates", "", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/subscriptions/{subscriptionId}/updates")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + [Fact] + public void should_return_response_404_with_query_string_upstream_template_different_query_string() + { + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 64879, + } + }, + UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:64879", $"/api/units/{subscriptionId}/{unitId}/updates", "", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/subscriptions/{subscriptionId}/updates?test=1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_query_string_upstream_template_multiple_params() + { + var subscriptionId = Guid.NewGuid().ToString(); + var unitId = Guid.NewGuid().ToString(); + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/units/{subscriptionId}/{unitId}/updates", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 64879, + } + }, + UpstreamPathTemplate = "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:64879", $"/api/units/{subscriptionId}/{unitId}/updates", "?productId=1", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway($"/api/subscriptions/{subscriptionId}/updates?unitId={unitId}&productId=1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, string queryString, int statusCode, string responseBody) { _builder = new WebHostBuilder() @@ -72,10 +218,9 @@ namespace Ocelot.AcceptanceTests app.UsePathBase(basePath); app.Run(async context => { - if(context.Request.PathBase.Value != basePath || context.Request.QueryString.Value != queryString) { - context.Response.StatusCode = 404; + context.Response.StatusCode = 500; await context.Response.WriteAsync("downstream path didnt match base path"); } else diff --git a/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs b/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs index 678c1934..6ff703bd 100644 --- a/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs +++ b/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs @@ -14,6 +14,7 @@ namespace Ocelot.Benchmarks private RegExUrlMatcher _urlPathMatcher; private string _downstreamUrlPath; private string _downstreamUrlPathTemplate; + private string _upstreamQuery; public UrlPathToUrlPathTemplateMatcherBenchmarks() { @@ -33,7 +34,7 @@ namespace Ocelot.Benchmarks [Benchmark(Baseline = true)] public void Baseline() { - _urlPathMatcher.Match(_downstreamUrlPath, _downstreamUrlPathTemplate); + _urlPathMatcher.Match(_downstreamUrlPath, _upstreamQuery, _downstreamUrlPathTemplate, false); } // * Summary * diff --git a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs index 79111e2e..af7dfed7 100644 --- a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs @@ -599,7 +599,7 @@ .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List {"Get"}) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1, false)) .WithLoadBalancerKey("/api/products/{productId}|Get|") .Build(); @@ -628,7 +628,7 @@ .WithDownstreamReRoute(downstreamReRoute) .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1, false)) .Build() })) .BDDfy(); @@ -922,7 +922,7 @@ { _upstreamTemplatePatternCreator .Setup(x => x.Create(It.IsAny())) - .Returns(new UpstreamPathTemplate(pattern, 1)); + .Returns(new UpstreamPathTemplate(pattern, 1, false)); } private void ThenTheRequestIdKeyCreatorIsCalledCorrectly() diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs index 36986932..d924586d 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs @@ -10,7 +10,7 @@ namespace Ocelot.UnitTests.Configuration public class UpstreamTemplatePatternCreatorTests { private FileReRoute _fileReRoute; - private UpstreamTemplatePatternCreator _creator; + private readonly UpstreamTemplatePatternCreator _creator; private UpstreamPathTemplate _result; public UpstreamTemplatePatternCreatorTests() @@ -191,6 +191,36 @@ namespace Ocelot.UnitTests.Configuration .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; diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs index b4f82703..b29d2d29 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs @@ -28,6 +28,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private IInternalConfiguration _configuration; private Mock _qosOptionsCreator; private Response _resultTwo; + private string _upstreamQuery; public DownstreamRouteCreatorTests() { @@ -247,12 +248,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void WhenICreate() { - _result = _creator.Get(_upstreamUrlPath, _upstreamHttpMethod, _configuration, _upstreamHost); + _result = _creator.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _configuration, _upstreamHost); } private void WhenICreateAgain() { - _resultTwo = _creator.Get(_upstreamUrlPath, _upstreamHttpMethod, _configuration, _upstreamHost); + _resultTwo = _creator.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _configuration, _upstreamHost); } private void ThenTheDownstreamRoutesAreTheSameReference() diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 19b50e24..298492af 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -86,7 +86,7 @@ { _downstreamRoute = new OkResponse(downstreamRoute); _finder - .Setup(x => x.Get(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.Get(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(_downstreamRoute); } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index b99bc865..360d58f4 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -25,6 +25,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private Response _match; private string _upstreamHttpMethod; private string _upstreamHost; + private string _upstreamQuery; public DownstreamRouteFinderTests() { @@ -48,22 +49,22 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false)) .Build() }, string.Empty, serviceProviderConfig)) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) @@ -73,13 +74,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .Build()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .Build() ))) .BDDfy(); @@ -100,22 +101,22 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0, false)) .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .Build() }, string.Empty, serviceProviderConfig)) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) @@ -125,10 +126,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .WithUpstreamHttpMethod(new List { "Post" }) .Build()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1, false)) .WithUpstreamHttpMethod(new List { "Post" }) .Build() ))) @@ -151,11 +152,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() }, string.Empty, serviceProviderConfig )) @@ -169,10 +170,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -195,11 +196,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() }, string.Empty, serviceProviderConfig )) @@ -213,10 +214,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly("matchInUrlMatcher")) @@ -240,11 +241,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() }, string.Empty, serviceProviderConfig )) @@ -257,10 +258,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() ))) .BDDfy(); @@ -283,22 +284,22 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPathForAPost") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build() }, string.Empty, serviceProviderConfig )) @@ -311,10 +312,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPathForAPost") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build() ))) .BDDfy(); @@ -333,11 +334,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("somPath") .WithUpstreamPathTemplate("somePath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1, false)) .Build()) .WithUpstreamPathTemplate("somePath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1, false)) .Build(), }, string.Empty, serviceProviderConfig )) @@ -367,11 +368,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build() }, string.Empty, serviceProviderConfig )) @@ -384,10 +385,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build() ))) .BDDfy(); @@ -410,11 +411,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build() }, string.Empty, serviceProviderConfig )) @@ -427,10 +428,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build() ))) .BDDfy(); @@ -453,11 +454,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1, false)) .Build() }, string.Empty, serviceProviderConfig )) @@ -486,12 +487,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -506,10 +507,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -533,11 +534,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() }, string.Empty, serviceProviderConfig )) @@ -551,10 +552,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -576,12 +577,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build(), new ReRouteBuilder() @@ -589,12 +590,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { }) // empty list of methods - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { }) // empty list of methods - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -622,12 +623,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -655,12 +656,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -689,23 +690,23 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("THENULLPATH") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build(), new ReRouteBuilder() .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build()) .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .WithUpstreamHost("MATCH") .Build() }, string.Empty, serviceProviderConfig @@ -720,10 +721,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamReRoute(new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build()) .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1, false)) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(2)) @@ -738,7 +739,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void GivenTheTemplateVariableAndNameFinderReturns(Response> response) { _finder - .Setup(x => x.Find(It.IsAny(), It.IsAny())) + .Setup(x => x.Find(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(response); } @@ -755,32 +756,32 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void ThenTheUrlMatcherIsCalledCorrectly() { _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); + .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamPathTemplate.Value, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Once); } private void ThenTheUrlMatcherIsCalledCorrectly(int times) { _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Exactly(times)); + .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamPathTemplate.Value, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Exactly(times)); } private void ThenTheUrlMatcherIsCalledCorrectly(string expectedUpstreamUrlPath) { _mockMatcher - .Verify(x => x.Match(expectedUpstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); + .Verify(x => x.Match(expectedUpstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamPathTemplate.Value, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Once); } private void ThenTheUrlMatcherIsNotCalled() { _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Never); + .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamPathTemplate.Value, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Never); } private void GivenTheUrlMatcherReturns(Response match) { _match = match; _mockMatcher - .Setup(x => x.Match(It.IsAny(), It.IsAny())) + .Setup(x => x.Match(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(_match); } @@ -797,7 +798,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void WhenICallTheFinder() { - _result = _downstreamRouteFinder.Get(_upstreamUrlPath, _upstreamHttpMethod, _config, _upstreamHost); + _result = _downstreamRouteFinder.Get(_upstreamUrlPath, _upstreamQuery, _upstreamHttpMethod, _config, _upstreamHost); } private void ThenTheFollowingIsReturned(DownstreamRoute expected) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs index 0f3fea3b..10fe106e 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs @@ -9,22 +9,65 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher public class RegExUrlMatcherTests { private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; - private string _downstreamUrlPath; + 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(); + } + + [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].*"; + const string regExForwardSlashAndOnePlaceHolder = "^/[0-9a-zA-Z].+"; this.Given(x => x.GivenIHaveAUpstreamPath("/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(RegExForwardSlashAndOnePlaceHolder)) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(regExForwardSlashAndOnePlaceHolder)) .When(x => x.WhenIMatchThePaths()) .And(x => x.ThenTheResultIsFalse()) .BDDfy(); @@ -44,7 +87,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher public void should_not_match_issue_134() { this.Given(x => x.GivenIHaveAUpstreamPath("/api/vacancy/1/")) - .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)/vacancy/.*/$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)/vacancy/.+/$")) .When(x => x.WhenIMatchThePaths()) .And(x => x.ThenTheResultIsFalse()) .BDDfy(); @@ -64,7 +107,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher 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/.*$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/api/products/.+$")) .When(x => x.WhenIMatchThePaths()) .And(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -124,7 +167,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher 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/.*$")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -134,7 +177,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher 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/.*/.*$")) + .Given(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/.+$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -144,7 +187,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher 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/.*$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/categories/.+$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -154,7 +197,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher 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/.*$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/categories/.+/variant/.+$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -164,7 +207,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher 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/$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/categories/.+/variant/$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -174,7 +217,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher 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/$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^(?i)api/product/products/.+/categories/.+/variant/$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsTrue()) .BDDfy(); @@ -184,15 +227,20 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher 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/$")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^api/product/products/.+/categories/.+/variant/$")) .When(x => x.WhenIMatchThePaths()) .Then(x => x.ThenTheResultIsFalse()) .BDDfy(); } - private void GivenIHaveAUpstreamPath(string downstreamPath) + private void GivenIHaveAUpstreamPath(string path) { - _downstreamUrlPath = downstreamPath; + _path = path; + } + + private void GivenIHaveAQueryString(string queryString) + { + _queryString = queryString; } private void GivenIHaveAnUpstreamUrlTemplatePattern(string downstreamUrlTemplate) @@ -202,7 +250,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher private void WhenIMatchThePaths() { - _result = _urlMatcher.Match(_downstreamUrlPath, _downstreamPathTemplate); + _result = _urlMatcher.Match(_path, _queryString, _downstreamPathTemplate, _containsQueryString); } private void ThenTheResultIsTrue() @@ -214,5 +262,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher { _result.Data.Match.ShouldBeFalse(); } + + private void GivenThereIsAQueryInTemplate() + { + _containsQueryString = true; + } } -} \ No newline at end of file +} diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs index 29bc8fcd..7ce2bb1d 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs @@ -14,6 +14,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher private string _downstreamUrlPath; private string _downstreamPathTemplate; private Response> _result; + private string _query; public UrlPathPlaceholderNameAndValueFinderTests() { @@ -114,6 +115,91 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher .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() { @@ -260,7 +346,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher private void WhenIFindTheUrlVariableNamesAndValues() { - _result = _finder.Find(_downstreamUrlPath, _downstreamPathTemplate); + _result = _finder.Find(_downstreamUrlPath, _query, _downstreamPathTemplate); + } + + private void GivenIHaveAQuery(string query) + { + _query = query; } } } diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index 98fc7c11..ac59b736 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -72,6 +72,103 @@ .BDDfy(); } + [Fact] + public void should_replace_query_string() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/api/units/{subscriptionId}/{unitId}/updates") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithDownstreamScheme("https") + .Build(); + + var config = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List + { + new PlaceholderNameAndValue("{subscriptionId}", "1"), + new PlaceholderNameAndValue("{unitId}", "2") + }, + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:5000/api/subscriptions/1/updates?unitId=2")) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheUrlReplacerWillReturn("api/units/1/2/updates")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("https://localhost:5000/api/units/1/2/updates")) + .And(x => ThenTheQueryStringIs("")) + .BDDfy(); + } + + [Fact] + public void should_replace_query_string_but_leave_non_placeholder_queries() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/api/units/{subscriptionId}/{unitId}/updates") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithDownstreamScheme("https") + .Build(); + + var config = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List + { + new PlaceholderNameAndValue("{subscriptionId}", "1"), + new PlaceholderNameAndValue("{unitId}", "2") + }, + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:5000/api/subscriptions/1/updates?unitId=2&productId=2")) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheUrlReplacerWillReturn("api/units/1/2/updates")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("https://localhost:5000/api/units/1/2/updates?productId=2")) + .And(x => ThenTheQueryStringIs("?productId=2")) + .BDDfy(); + } + + [Fact] + public void should_replace_query_string_exact_match() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/api/units/{subscriptionId}/{unitId}/updates/{unitIdIty}") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithDownstreamScheme("https") + .Build(); + + var config = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List + { + new PlaceholderNameAndValue("{subscriptionId}", "1"), + new PlaceholderNameAndValue("{unitId}", "2"), + new PlaceholderNameAndValue("{unitIdIty}", "3") + }, + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:5000/api/subscriptions/1/updates?unitId=2?unitIdIty=3")) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheUrlReplacerWillReturn("api/units/1/2/updates/3")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("https://localhost:5000/api/units/1/2/updates/3")) + .And(x => ThenTheQueryStringIs("")) + .BDDfy(); + } + [Fact] public void should_not_create_service_fabric_url() { From 5c940acf0e60a6cb737609a70cad689e92efb42e Mon Sep 17 00:00:00 2001 From: aqa510415008 <510415008@qq.com> Date: Fri, 20 Jul 2018 00:45:46 +0800 Subject: [PATCH 15/16] Expand other branch pipes (#416) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Expand other branch pipes * Expand other branch pipes * Expand other branch pipes * optimization Expand other branch pipes ,Add Unit test * I hope to add two attributes to IOcelotBuilder for easy extension. --- .../Builder/QoSOptionsBuilder.cs | 2 +- .../DependencyInjection/IOcelotBuilder.cs | 4 +++ .../DependencyInjection/OcelotBuilder.cs | 4 +++ .../Middleware/ExceptionHandlerMiddleware.cs | 4 +-- .../Middleware/OcelotMiddlewareExtensions.cs | 32 +++++++++++-------- .../Middleware/OcelotPipelineConfiguration.cs | 8 ++++- .../OcelotPipelineBuilderExtensions.cs | 31 +++++++++++++++--- .../Pipeline/OcelotPipelineExtensions.cs | 9 ++++++ .../OcelotPipelineExtensionsTests.cs | 32 +++++++++++++++++++ .../Middleware/OcelotPiplineBuilderTests.cs | 2 ++ 10 files changed, 107 insertions(+), 21 deletions(-) diff --git a/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs index 69660d83..e1b86d1a 100644 --- a/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs +++ b/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs @@ -6,7 +6,7 @@ private int _durationOfBreak; - private int _timeoutValue; + private int _timeoutValue; private string _key; diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index 20eafd36..9a4a60b8 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -4,11 +4,15 @@ using System; using System.Net.Http; using IdentityServer4.AccessTokenValidation; using Ocelot.Middleware.Multiplexer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; namespace Ocelot.DependencyInjection { public interface IOcelotBuilder { + IServiceCollection Services { get; } + IConfiguration Configuration { get; } IOcelotBuilder AddStoreOcelotConfigurationInConsul(); IOcelotBuilder AddCacheManager(Action settings); diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 1b93ddbb..72064a9d 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -55,6 +55,10 @@ namespace Ocelot.DependencyInjection private readonly IServiceCollection _services; private readonly IConfiguration _configurationRoot; + public IServiceCollection Services => _services; + + public IConfiguration Configuration => _configurationRoot; + public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot) { _configurationRoot = configurationRoot; diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs index b7e51ef9..f1353346 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs @@ -34,7 +34,7 @@ namespace Ocelot.Errors.Middleware public async Task Invoke(DownstreamContext context) { try - { + { //try and get the global request id and set it for logs... //should this basically be immutable per request...i guess it should! //first thing is get config @@ -44,7 +44,7 @@ namespace Ocelot.Errors.Middleware { throw new Exception($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}"); } - + TrySetGlobalRequestId(context, configuration.Data); context.Configuration = configuration.Data; diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index a35a8e1a..d97dd71e 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -27,15 +27,21 @@ 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); - + var configuration = await CreateConfiguration(builder); + CreateAdministrationArea(builder, configuration); - if(UsingRafty(builder)) + if (UsingRafty(builder)) { SetUpRafty(builder); } @@ -78,7 +84,7 @@ private static bool UsingRafty(IApplicationBuilder builder) { var possible = builder.ApplicationServices.GetService(typeof(INode)) as INode; - if(possible != null) + if (possible != null) { return true; } @@ -99,9 +105,9 @@ { // 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 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); @@ -196,7 +202,7 @@ { var ocelotConfiguration = provider.Get(); - if(ocelotConfiguration?.Data == null || ocelotConfiguration.IsError) + if (ocelotConfiguration?.Data == null || ocelotConfiguration.IsError) { ThrowToStopOcelotStarting(ocelotConfiguration); } @@ -216,7 +222,7 @@ private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration) { - if(!string.IsNullOrEmpty(configuration.AdministrationPath)) + if (!string.IsNullOrEmpty(configuration.AdministrationPath)) { builder.Map(configuration.AdministrationPath, app => { @@ -239,8 +245,8 @@ 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)); diff --git a/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs b/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs index d58b8b09..c7f6e231 100644 --- a/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs +++ b/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs @@ -1,6 +1,8 @@ namespace Ocelot.Middleware { + using Ocelot.Middleware.Pipeline; using System; + using System.Collections.Generic; using System.Threading.Tasks; public class OcelotPipelineConfiguration @@ -37,6 +39,10 @@ /// /// This allows the user to implement there own query string manipulation logic /// - public Func, Task> PreQueryStringBuilderMiddleware { get; set; } + public Func, Task> PreQueryStringBuilderMiddleware { get; set; } + /// + /// This is an extension that will branch to different pipes + /// + public List>> MapWhenOcelotPipeline { get; } = new List>>(); } } diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs index b9aabfe3..de2a35eb 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs @@ -80,13 +80,14 @@ namespace Ocelot.Middleware.Pipeline var diagnosticListener = (DiagnosticListener)app.ApplicationServices.GetService(typeof(DiagnosticListener)); var middlewareName = ocelotDelegate.Target.GetType().Name; - OcelotRequestDelegate wrapped = context => { + OcelotRequestDelegate wrapped = context => + { try { Write(diagnosticListener, "Ocelot.MiddlewareStarted", middlewareName, context); return ocelotDelegate(context); } - catch(Exception ex) + catch (Exception ex) { WriteException(diagnosticListener, ex, "Ocelot.MiddlewareException", middlewareName, context); throw ex; @@ -117,7 +118,7 @@ namespace Ocelot.Middleware.Pipeline private static void Write(DiagnosticListener diagnosticListener, string message, string middlewareName, DownstreamContext context) { - if(diagnosticListener != null) + if (diagnosticListener != null) { diagnosticListener.Write(message, new { name = middlewareName, context = context }); } @@ -125,7 +126,7 @@ namespace Ocelot.Middleware.Pipeline private static void WriteException(DiagnosticListener diagnosticListener, Exception exception, string message, string middlewareName, DownstreamContext context) { - if(diagnosticListener != null) + if (diagnosticListener != null) { diagnosticListener.Write(message, new { name = middlewareName, context = context, exception = exception }); } @@ -160,6 +161,28 @@ namespace Ocelot.Middleware.Pipeline return app.Use(next => new MapWhenMiddleware(next, options).Invoke); } + public static IOcelotPipelineBuilder MapWhen(this IOcelotPipelineBuilder app, Func pipelineBuilderFunc) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + if (pipelineBuilderFunc == null) + { + throw new ArgumentNullException(nameof(pipelineBuilderFunc)); + } + var branchBuilder = app.New(); + var predicate = pipelineBuilderFunc.Invoke(app); + var branch = branchBuilder.Build(); + + var options = new MapWhenOptions + { + Predicate = predicate, + Branch = branch + }; + return app.Use(next => new MapWhenMiddleware(next, options).Invoke); + } + private static Func Compile(MethodInfo methodinfo, ParameterInfo[] parameters) { var middleware = typeof(T); diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs index f9a74235..5c10d7c0 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs @@ -28,6 +28,15 @@ 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 => diff --git a/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs index 74f6c787..fd6fd8bf 100644 --- a/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs +++ b/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs @@ -3,8 +3,13 @@ namespace Ocelot.UnitTests.Middleware using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Ocelot.DependencyInjection; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.DownstreamUrlCreator.Middleware; + using Ocelot.LoadBalancer.Middleware; using Ocelot.Middleware; using Ocelot.Middleware.Pipeline; + using Ocelot.Request.Middleware; + using Ocelot.WebSockets.Middleware; using Pivotal.Discovery.Client; using Shouldly; using Steeltoe.Common.Discovery; @@ -26,6 +31,16 @@ namespace Ocelot.UnitTests.Middleware .BDDfy(); } + [Fact] + public void should_expand_pipeline() + { + this.Given(_ => GivenTheDepedenciesAreSetUp()) + .When(_ => WhenIExpandBuild()) + .Then(_ => ThenThePipelineIsBuilt()) + .BDDfy(); + } + + private void ThenThePipelineIsBuilt() { _handlers.ShouldNotBeNull(); @@ -36,6 +51,23 @@ namespace Ocelot.UnitTests.Middleware _handlers = _builder.BuildOcelotPipeline(new OcelotPipelineConfiguration()); } + private void WhenIExpandBuild() + { + OcelotPipelineConfiguration configuration = new OcelotPipelineConfiguration(); + configuration.MapWhenOcelotPipeline.Add((app) => + { + app.UseDownstreamRouteFinderMiddleware(); + app.UseDownstreamRequestInitialiser(); + app.UseLoadBalancingMiddleware(); + app.UseDownstreamUrlCreatorMiddleware(); + app.UseWebSocketsProxyMiddleware(); + + return context => context.HttpContext.WebSockets.IsWebSocketRequest; + }); + _handlers = _builder.BuildOcelotPipeline(new OcelotPipelineConfiguration()); + } + + private void GivenTheDepedenciesAreSetUp() { IConfigurationBuilder test = new ConfigurationBuilder(); diff --git a/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs index 21649a80..a798ae44 100644 --- a/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs +++ b/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs @@ -79,6 +79,8 @@ namespace Ocelot.UnitTests.Middleware del.Invoke(_downstreamContext); } + + private void ThenTheFuncIsInThePipeline() { _counter.ShouldBe(1); From 12ef3bc00f61c12909ac7b34855199bb8812f324 Mon Sep 17 00:00:00 2001 From: Edwin van Wijk Date: Thu, 19 Jul 2018 18:49:47 +0200 Subject: [PATCH 16/16] Added overload of the IConfigurationBuilder.AddOcelot extension-method that accepts a specific folder (#476) --- .../ConfigurationBuilderExtensions.cs | 11 ++++-- .../ConfigurationBuilderExtensionsTests.cs | 39 ++++++++++++++++--- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs index 7eb94329..1ddf6fcc 100644 --- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs +++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs @@ -29,12 +29,17 @@ namespace Ocelot.DependencyInjection } public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder) + { + return builder.AddOcelot("."); + } + + public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder, string folder) { const string pattern = "(?i)ocelot\\.([a-zA-Z0-9]*)(\\.json)$"; var reg = new Regex(pattern); - var files = Directory.GetFiles(".") + var files = Directory.GetFiles(folder) .Where(path => reg.IsMatch(path)) .ToList(); @@ -43,7 +48,7 @@ namespace Ocelot.DependencyInjection foreach (var file in files) { // windows and unix sigh... - if(files.Count > 1 && (file == "./ocelot.json" || file == ".\\ocelot.json")) + if(files.Count > 1 && (Path.GetFileName(file) == "ocelot.json")) { continue; } @@ -53,7 +58,7 @@ namespace Ocelot.DependencyInjection var config = JsonConvert.DeserializeObject(lines); // windows and unix sigh... - if (file == "./ocelot.global.json" || file == ".\\ocelot.global.json") + if (Path.GetFileName(file) == "ocelot.global.json") { fileConfiguration.GlobalConfiguration = config.GlobalConfiguration; } diff --git a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs index 7c29977d..43176e5a 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs @@ -32,14 +32,29 @@ [Fact] public void should_merge_files() { - this.Given(_ => GivenMultipleConfigurationFiles()) + this.Given(_ => GivenMultipleConfigurationFiles("")) .When(_ => WhenIAddOcelotConfiguration()) .Then(_ => ThenTheConfigsAreMerged()) .BDDfy(); } - private void GivenMultipleConfigurationFiles() + [Fact] + public void should_merge_files_in_specific_folder() { + string configFolder = "ConfigFiles"; + this.Given(_ => GivenMultipleConfigurationFiles(configFolder)) + .When(_ => WhenIAddOcelotConfigurationWithSpecificFolder(configFolder)) + .Then(_ => ThenTheConfigsAreMerged()) + .BDDfy(); + } + + private void GivenMultipleConfigurationFiles(string folder) + { + if (!string.IsNullOrEmpty(folder)) + { + Directory.CreateDirectory(folder); + } + _globalConfig = new FileConfiguration { GlobalConfiguration = new FileGlobalConfiguration @@ -159,10 +174,15 @@ } }; - File.WriteAllText("ocelot.global.json", JsonConvert.SerializeObject(_globalConfig)); - File.WriteAllText("ocelot.reRoutesA.json", JsonConvert.SerializeObject(_reRouteA)); - File.WriteAllText("ocelot.reRoutesB.json", JsonConvert.SerializeObject(_reRouteB)); - File.WriteAllText("ocelot.aggregates.json", JsonConvert.SerializeObject(_aggregate)); + string globalFilename = Path.Combine(folder, "ocelot.global.json"); + string reroutesAFilename = Path.Combine(folder, "ocelot.reRoutesA.json"); + string reroutesBFilename = Path.Combine(folder, "ocelot.reRoutesB.json"); + string aggregatesFilename = Path.Combine(folder, "ocelot.aggregates.json"); + + File.WriteAllText(globalFilename, JsonConvert.SerializeObject(_globalConfig)); + File.WriteAllText(reroutesAFilename, JsonConvert.SerializeObject(_reRouteA)); + File.WriteAllText(reroutesBFilename, JsonConvert.SerializeObject(_reRouteB)); + File.WriteAllText(aggregatesFilename, JsonConvert.SerializeObject(_aggregate)); } private void WhenIAddOcelotConfiguration() @@ -172,6 +192,13 @@ _configRoot = builder.Build(); } + private void WhenIAddOcelotConfigurationWithSpecificFolder(string folder) + { + IConfigurationBuilder builder = new ConfigurationBuilder(); + builder.AddOcelot(folder); + _configRoot = builder.Build(); + } + private void ThenTheConfigsAreMerged() { var fc = (FileConfiguration)_configRoot.Get(typeof(FileConfiguration));