From c85ea41951409a4fc1a0a38077847edb38e9492b Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 1 Mar 2017 07:54:07 +0000 Subject: [PATCH 01/19] refactoring ocelot config creation process --- .../Creator/AuthenticationOptionsCreator.cs | 20 ++ .../Creator/ClaimsToThingCreator.cs | 41 +++ .../Creator/FileOcelotConfigurationCreator.cs | 98 ++----- .../Creator/IAuthenticationOptionsCreator.cs | 9 + .../Creator/IClaimsToThingCreator.cs | 9 + .../IUpstreamTemplatePatternCreator.cs | 9 + .../Creator/UpstreamTemplatePatternCreator.cs | 56 ++++ .../ServiceCollectionExtensions.cs | 3 + .../AuthenticationOptionsCreatorTests.cs | 74 ++++++ .../ClaimsToThingCreatorTests.cs | 110 ++++++++ .../FileConfigurationCreatorTests.cs | 246 ++++-------------- .../UpstreamTemplatePatternCreatorTests.cs | 122 +++++++++ 12 files changed, 517 insertions(+), 280 deletions(-) create mode 100644 src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/ClaimsToThingCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/IAuthenticationOptionsCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/IClaimsToThingCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs create mode 100644 test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs create mode 100644 test/Ocelot.UnitTests/Configuration/ClaimsToThingCreatorTests.cs create mode 100644 test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs diff --git a/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs b/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs new file mode 100644 index 00000000..85219296 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs @@ -0,0 +1,20 @@ +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class AuthenticationOptionsCreator : IAuthenticationOptionsCreator + { + public AuthenticationOptions Create(FileReRoute fileReRoute) + { + return new AuthenticationOptionsBuilder() + .WithProvider(fileReRoute.AuthenticationOptions?.Provider) + .WithProviderRootUrl(fileReRoute.AuthenticationOptions?.ProviderRootUrl) + .WithScopeName(fileReRoute.AuthenticationOptions?.ScopeName) + .WithRequireHttps(fileReRoute.AuthenticationOptions.RequireHttps) + .WithAdditionalScopes(fileReRoute.AuthenticationOptions?.AdditionalScopes) + .WithScopeSecret(fileReRoute.AuthenticationOptions?.ScopeSecret) + .Build(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/ClaimsToThingCreator.cs b/src/Ocelot/Configuration/Creator/ClaimsToThingCreator.cs new file mode 100644 index 00000000..27f52a5f --- /dev/null +++ b/src/Ocelot/Configuration/Creator/ClaimsToThingCreator.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Ocelot.Configuration.Parser; +using Ocelot.Logging; + +namespace Ocelot.Configuration.Creator +{ + public class ClaimsToThingCreator : IClaimsToThingCreator + { + private readonly IClaimToThingConfigurationParser _claimToThingConfigParser; + private readonly IOcelotLogger _logger; + + public ClaimsToThingCreator(IClaimToThingConfigurationParser claimToThingConfigurationParser, + IOcelotLoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + _claimToThingConfigParser = claimToThingConfigurationParser; + } + + public List Create(Dictionary inputToBeParsed) + { + var claimsToThings = new List(); + + foreach (var input in inputToBeParsed) + { + var claimToThing = _claimToThingConfigParser.Extract(input.Key, input.Value); + + if (claimToThing.IsError) + { + _logger.LogDebug("ClaimsToThingCreator.BuildAddThingsToRequest", + $"Unable to extract configuration for key: {input.Key} and value: {input.Value} your configuration file is incorrect"); + } + else + { + claimsToThings.Add(claimToThing.Data); + } + } + + return claimsToThings; + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index b4abdec8..0d853f94 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -22,36 +22,38 @@ namespace Ocelot.Configuration.Creator { private readonly IOptions _options; private readonly IConfigurationValidator _configurationValidator; - private const string RegExMatchEverything = ".*"; - private const string RegExMatchEndString = "$"; - private const string RegExIgnoreCase = "(?i)"; - private const string RegExForwardSlashOnly = "^/$"; - private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser; private readonly ILogger _logger; private readonly ILoadBalancerFactory _loadBalanceFactory; private readonly ILoadBalancerHouse _loadBalancerHouse; private readonly IQoSProviderFactory _qoSProviderFactory; private readonly IQosProviderHouse _qosProviderHouse; + private readonly IClaimsToThingCreator _claimsToThingCreator; + private readonly IAuthenticationOptionsCreator _authOptionsCreator; + private IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; public FileOcelotConfigurationCreator( IOptions options, IConfigurationValidator configurationValidator, - IClaimToThingConfigurationParser claimToThingConfigurationParser, ILogger logger, ILoadBalancerFactory loadBalancerFactory, ILoadBalancerHouse loadBalancerHouse, IQoSProviderFactory qoSProviderFactory, - IQosProviderHouse qosProviderHouse) + IQosProviderHouse qosProviderHouse, + IClaimsToThingCreator claimsToThingCreator, + IAuthenticationOptionsCreator authOptionsCreator, + IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator) { + _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; + _authOptionsCreator = authOptionsCreator; _loadBalanceFactory = loadBalancerFactory; _loadBalancerHouse = loadBalancerHouse; _qoSProviderFactory = qoSProviderFactory; _qosProviderHouse = qosProviderHouse; _options = options; _configurationValidator = configurationValidator; - _claimToThingConfigurationParser = claimToThingConfigurationParser; _logger = logger; + _claimsToThingCreator = claimsToThingCreator; } public async Task> Create() @@ -107,19 +109,19 @@ namespace Ocelot.Configuration.Creator var reRouteKey = BuildReRouteKey(fileReRoute); - var upstreamTemplatePattern = BuildUpstreamTemplatePattern(fileReRoute); + var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); var isQos = IsQoS(fileReRoute); var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration); - var authOptionsForRoute = BuildAuthenticationOptions(fileReRoute); + var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute); - var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest); + var claimsToHeaders = _claimsToThingCreator.Create(fileReRoute.AddHeadersToRequest); - var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest); + var claimsToClaims = _claimsToThingCreator.Create(fileReRoute.AddClaimsToRequest); - var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest); + var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest); var qosOptions = BuildQoSOptions(fileReRoute); @@ -153,6 +155,7 @@ namespace Ocelot.Configuration.Creator .WithEnableRateLimiting(enableRateLimiting) .WithRateLimitOptions(rateLimitOption) .Build(); + await SetupLoadBalancer(reRoute); SetupQosProvider(reRoute); return reRoute; @@ -225,18 +228,6 @@ namespace Ocelot.Configuration.Creator return loadBalancerKey; } - private AuthenticationOptions BuildAuthenticationOptions(FileReRoute fileReRoute) - { - return new AuthenticationOptionsBuilder() - .WithProvider(fileReRoute.AuthenticationOptions?.Provider) - .WithProviderRootUrl(fileReRoute.AuthenticationOptions?.ProviderRootUrl) - .WithScopeName(fileReRoute.AuthenticationOptions?.ScopeName) - .WithRequireHttps(fileReRoute.AuthenticationOptions.RequireHttps) - .WithAdditionalScopes(fileReRoute.AuthenticationOptions?.AdditionalScopes) - .WithScopeSecret(fileReRoute.AuthenticationOptions?.ScopeSecret) - .Build(); - } - private async Task SetupLoadBalancer(ReRoute reRoute) { var loadBalancer = await _loadBalanceFactory.Get(reRoute); @@ -267,63 +258,6 @@ namespace Ocelot.Configuration.Creator .Build(); } - private string BuildUpstreamTemplatePattern(FileReRoute reRoute) - { - var upstreamTemplate = reRoute.UpstreamPathTemplate; - - upstreamTemplate = upstreamTemplate.SetLastCharacterAs('/'); - - var placeholders = new List(); - - for (var i = 0; i < upstreamTemplate.Length; i++) - { - if (IsPlaceHolder(upstreamTemplate, i)) - { - var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i); - var difference = postitionOfPlaceHolderClosingBracket - i + 1; - var variableName = upstreamTemplate.Substring(i, difference); - placeholders.Add(variableName); - } - } - - foreach (var placeholder in placeholders) - { - upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything); - } - - if (upstreamTemplate == "/") - { - return RegExForwardSlashOnly; - } - - var route = reRoute.ReRouteIsCaseSensitive - ? $"{upstreamTemplate}{RegExMatchEndString}" - : $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; - - return route; - } - - private List BuildAddThingsToRequest(Dictionary thingBeingAdded) - { - var claimsToTHings = new List(); - - foreach (var add in thingBeingAdded) - { - var claimToHeader = _claimToThingConfigurationParser.Extract(add.Key, add.Value); - - if (claimToHeader.IsError) - { - _logger.LogCritical(new EventId(1, "Application Failed to start"), - $"Unable to extract configuration for key: {add.Key} and value: {add.Value} your configuration file is incorrect"); - - throw new Exception(claimToHeader.Errors[0].Message); - } - claimsToTHings.Add(claimToHeader.Data); - } - - return claimsToTHings; - } - private bool IsPlaceHolder(string upstreamTemplate, int i) { return upstreamTemplate[i] == '{'; diff --git a/src/Ocelot/Configuration/Creator/IAuthenticationOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IAuthenticationOptionsCreator.cs new file mode 100644 index 00000000..e5e82ca8 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IAuthenticationOptionsCreator.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IAuthenticationOptionsCreator + { + AuthenticationOptions Create(FileReRoute fileReRoute); + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/IClaimsToThingCreator.cs b/src/Ocelot/Configuration/Creator/IClaimsToThingCreator.cs new file mode 100644 index 00000000..54ff8ddc --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IClaimsToThingCreator.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Ocelot.Configuration.Creator +{ + public interface IClaimsToThingCreator + { + List Create(Dictionary thingsBeingAdded); + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs new file mode 100644 index 00000000..ae62c47a --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IUpstreamTemplatePatternCreator + { + string Create(FileReRoute reRoute); + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs new file mode 100644 index 00000000..95b339a9 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using Ocelot.Configuration.File; +using Ocelot.Utilities; + +namespace Ocelot.Configuration.Creator +{ + public class UpstreamTemplatePatternCreator : IUpstreamTemplatePatternCreator + { + private const string RegExMatchEverything = ".*"; + private const string RegExMatchEndString = "$"; + private const string RegExIgnoreCase = "(?i)"; + private const string RegExForwardSlashOnly = "^/$"; + + public string Create(FileReRoute reRoute) + { + var upstreamTemplate = reRoute.UpstreamPathTemplate; + + upstreamTemplate = upstreamTemplate.SetLastCharacterAs('/'); + + var placeholders = new List(); + + for (var i = 0; i < upstreamTemplate.Length; i++) + { + if (IsPlaceHolder(upstreamTemplate, i)) + { + var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i); + var difference = postitionOfPlaceHolderClosingBracket - i + 1; + var variableName = upstreamTemplate.Substring(i, difference); + placeholders.Add(variableName); + } + } + + foreach (var placeholder in placeholders) + { + upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything); + } + + if (upstreamTemplate == "/") + { + return RegExForwardSlashOnly; + } + + var route = reRoute.ReRouteIsCaseSensitive + ? $"{upstreamTemplate}{RegExMatchEndString}" + : $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; + + return route; + } + + + private bool IsPlaceHolder(string upstreamTemplate, int i) + { + return upstreamTemplate[i] == '{'; + } + } +} \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index c076f367..54fef846 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -60,6 +60,9 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(); diff --git a/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs new file mode 100644 index 00000000..9273caf7 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class AuthenticationOptionsCreatorTests + { + private AuthenticationOptionsCreator _authOptionsCreator; + private FileReRoute _fileReRoute; + private AuthenticationOptions _result; + + public AuthenticationOptionsCreatorTests() + { + _authOptionsCreator = new AuthenticationOptionsCreator(); + } + + [Fact] + public void should_return_auth_options() + { + var fileReRoute = new FileReRoute() + { + AuthenticationOptions = new FileAuthenticationOptions + { + Provider = "Geoff", + ProviderRootUrl = "http://www.bbc.co.uk/", + ScopeName = "Laura", + RequireHttps = true, + AdditionalScopes = new List {"cheese"}, + ScopeSecret = "secret" + } + }; + + var expected = new AuthenticationOptionsBuilder() + .WithProvider(fileReRoute.AuthenticationOptions?.Provider) + .WithProviderRootUrl(fileReRoute.AuthenticationOptions?.ProviderRootUrl) + .WithScopeName(fileReRoute.AuthenticationOptions?.ScopeName) + .WithRequireHttps(fileReRoute.AuthenticationOptions.RequireHttps) + .WithAdditionalScopes(fileReRoute.AuthenticationOptions?.AdditionalScopes) + .WithScopeSecret(fileReRoute.AuthenticationOptions?.ScopeSecret) + .Build(); + + this.Given(x => x.GivenTheFollowing(fileReRoute)) + .When(x => x.WhenICreateTheAuthenticationOptions()) + .Then(x => x.ThenTheFollowingIsReturned(expected)) + .BDDfy(); + } + + private void GivenTheFollowing(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void WhenICreateTheAuthenticationOptions() + { + _result = _authOptionsCreator.Create(_fileReRoute); + } + + private void ThenTheFollowingIsReturned(AuthenticationOptions expected) + { + _result.AdditionalScopes.ShouldBe(expected.AdditionalScopes); + _result.Provider.ShouldBe(expected.Provider); + _result.ProviderRootUrl.ShouldBe(expected.ProviderRootUrl); + _result.RequireHttps.ShouldBe(expected.RequireHttps); + _result.ScopeName.ShouldBe(expected.ScopeName); + _result.ScopeSecret.ShouldBe(expected.ScopeSecret); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/ClaimsToThingCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ClaimsToThingCreatorTests.cs new file mode 100644 index 00000000..467835d0 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/ClaimsToThingCreatorTests.cs @@ -0,0 +1,110 @@ +using System.Collections.Generic; +using System.Linq; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.Parser; +using Ocelot.Errors; +using Ocelot.Logging; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class ClaimsToThingCreatorTests + { + private readonly Mock _configParser; + private Dictionary _claimsToThings; + private ClaimsToThingCreator _claimsToThingsCreator; + private Mock _loggerFactory; + private List _result; + private Mock _logger; + + public ClaimsToThingCreatorTests() + { + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory + .Setup(x => x.CreateLogger()) + .Returns(_logger.Object); + _configParser = new Mock(); + _claimsToThingsCreator = new ClaimsToThingCreator(_configParser.Object, _loggerFactory.Object); + } + + [Fact] + public void should_return_claims_to_things() + { + var userInput = new Dictionary() + { + {"CustomerId", "Claims[CustomerId] > value"} + }; + + var claimsToThing = new OkResponse(new ClaimToThing("CustomerId", "CustomerId", "", 0)); + + this.Given(x => x.GivenTheFollowingDictionary(userInput)) + .And(x => x.GivenTheConfigHeaderExtractorReturns(claimsToThing)) + .When(x => x.WhenIGetTheThings()) + .Then(x => x.ThenTheConfigParserIsCalledCorrectly()) + .And(x => x.ThenClaimsToThingsAreReturned()) + .BDDfy(); + } + + [Fact] + public void should_log_error_if_cannot_parse_claim_to_thing() + { + var userInput = new Dictionary() + { + {"CustomerId", "Claims[CustomerId] > value"} + }; + + var claimsToThing = new ErrorResponse(It.IsAny()); + + this.Given(x => x.GivenTheFollowingDictionary(userInput)) + .And(x => x.GivenTheConfigHeaderExtractorReturns(claimsToThing)) + .When(x => x.WhenIGetTheThings()) + .Then(x => x.ThenTheConfigParserIsCalledCorrectly()) + .And(x => x.ThenNoClaimsToThingsAreReturned()) + .BDDfy(); + } + + private void ThenTheLoggerIsCalledCorrectly() + { + _logger + .Verify(x => x.LogDebug(It.IsAny(), It.IsAny()), Times.Once); + } + + private void ThenClaimsToThingsAreReturned() + { + _result.Count.ShouldBeGreaterThan(0); + } + private void GivenTheFollowingDictionary(Dictionary claimsToThings) + { + _claimsToThings = claimsToThings; + } + + private void GivenTheConfigHeaderExtractorReturns(Response expected) + { + _configParser + .Setup(x => x.Extract(It.IsAny(), It.IsAny())) + .Returns(expected); + } + + private void ThenNoClaimsToThingsAreReturned() + { + _result.Count.ShouldBe(0); + } + + private void WhenIGetTheThings() + { + _result = _claimsToThingsCreator.Create(_claimsToThings); + } + + private void ThenTheConfigParserIsCalledCorrectly() + { + _configParser + .Verify(x => x.Extract(_claimsToThings.First().Key, _claimsToThings.First().Value), Times.Once); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index fa1bbda3..bafea3cd 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -23,7 +23,6 @@ namespace Ocelot.UnitTests.Configuration private readonly Mock _validator; private Response _config; private FileConfiguration _fileConfiguration; - private readonly Mock _configParser; private readonly Mock> _logger; private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator; private readonly Mock _loadBalancerFactory; @@ -32,6 +31,9 @@ namespace Ocelot.UnitTests.Configuration private readonly Mock _qosProviderFactory; private readonly Mock _qosProviderHouse; private readonly Mock _qosProvider; + private Mock _claimsToThingCreator; + private Mock _authOptionsCreator; + private Mock _upstreamTemplatePatternCreator; public FileConfigurationCreatorTests() { @@ -39,16 +41,20 @@ namespace Ocelot.UnitTests.Configuration _qosProviderHouse = new Mock(); _qosProvider = new Mock(); _logger = new Mock>(); - _configParser = new Mock(); _validator = new Mock(); _fileConfig = new Mock>(); _loadBalancerFactory = new Mock(); _loadBalancerHouse = new Mock(); _loadBalancer = new Mock(); + _claimsToThingCreator = new Mock(); + _authOptionsCreator = new Mock(); + _upstreamTemplatePatternCreator = new Mock(); + _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( - _fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object, + _fileConfig.Object, _validator.Object, _logger.Object, _loadBalancerFactory.Object, _loadBalancerHouse.Object, - _qosProviderFactory.Object, _qosProviderHouse.Object); + _qosProviderFactory.Object, _qosProviderHouse.Object, _claimsToThingCreator.Object, + _authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object); } [Fact] @@ -130,7 +136,6 @@ namespace Ocelot.UnitTests.Configuration .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") - .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .Build() })) .BDDfy(); @@ -161,7 +166,6 @@ namespace Ocelot.UnitTests.Configuration .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") - .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .Build() })) .BDDfy(); @@ -200,7 +204,6 @@ namespace Ocelot.UnitTests.Configuration .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") - .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder() .WithUseServiceDiscovery(true) .WithServiceDiscoveryProvider("consul") @@ -236,7 +239,6 @@ namespace Ocelot.UnitTests.Configuration .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") - .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder() .WithUseServiceDiscovery(false) .Build()) @@ -246,7 +248,7 @@ namespace Ocelot.UnitTests.Configuration } [Fact] - public void should_use_reroute_case_sensitivity_value() + public void should_call_template_pattern_creator_correctly() { this.Given(x => x.GivenTheConfigIs(new FileConfiguration { @@ -262,6 +264,7 @@ namespace Ocelot.UnitTests.Configuration } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheUpstreamTemplatePatternCreatorReturns("(?i)/api/products/.*/$")) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(new List { @@ -275,65 +278,6 @@ namespace Ocelot.UnitTests.Configuration .BDDfy(); } - [Fact] - public void should_set_upstream_template_pattern_to_ignore_case_sensitivity() - { - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = "Get" - } - } - })) - .And(x => x.GivenTheConfigIsValid()) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") - .WithUpstreamHttpMethod("Get") - .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") - .Build() - })) - .BDDfy(); - } - - [Fact] - public void should_set_upstream_template_pattern_to_respect_case_sensitivity() - { - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = "Get", - ReRouteIsCaseSensitive = true - } - } - })) - .And(x => x.GivenTheConfigIsValid()) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") - .WithUpstreamHttpMethod("Get") - .WithUpstreamTemplatePattern("/api/products/.*/$") - .Build() - })) - .BDDfy(); - } - [Fact] public void should_set_global_request_id_key() { @@ -362,43 +306,12 @@ namespace Ocelot.UnitTests.Configuration .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") - .WithUpstreamTemplatePattern("/api/products/.*/$") .WithRequestIdKey("blahhhh") .Build() })) .BDDfy(); } - [Fact] - public void should_create_template_pattern_that_matches_anything_to_end_of_string() - { - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = "Get", - ReRouteIsCaseSensitive = true - } - } - })) - .And(x => x.GivenTheConfigIsValid()) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") - .WithUpstreamHttpMethod("Get") - .WithUpstreamTemplatePattern("/api/products/.*/$") - .Build() - })) - .BDDfy(); - } - [Fact] public void should_create_with_headers_to_extract() { @@ -417,7 +330,6 @@ namespace Ocelot.UnitTests.Configuration .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") - .WithUpstreamTemplatePattern("/api/products/.*/$") .WithAuthenticationOptions(authenticationOptions) .WithClaimsToHeaders(new List { @@ -453,20 +365,17 @@ namespace Ocelot.UnitTests.Configuration } })) .And(x => x.GivenTheConfigIsValid()) - .And(x => x.GivenTheConfigHeaderExtractorReturns(new ClaimToThing("CustomerId", "CustomerId", "", 0))) + .And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions)) + .And(x => x.GivenTheClaimsToThingCreatorReturns(new List{new ClaimToThing("CustomerId", "CustomerId", "", 0)})) .And(x => x.GivenTheLoadBalancerFactoryReturns()) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(expected)) .And(x => x.ThenTheAuthenticationOptionsAre(expected)) + .And(x => x.ThenTheAuthOptionsCreatorIsCalledCorrectly()) .BDDfy(); } - private void GivenTheConfigHeaderExtractorReturns(ClaimToThing expected) - { - _configParser - .Setup(x => x.Extract(It.IsAny(), It.IsAny())) - .Returns(new OkResponse(expected)); - } + [Fact] public void should_create_with_authentication_properties() @@ -486,7 +395,6 @@ namespace Ocelot.UnitTests.Configuration .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") - .WithUpstreamTemplatePattern("/api/products/.*/$") .WithAuthenticationOptions(authenticationOptions) .Build() }; @@ -514,100 +422,12 @@ namespace Ocelot.UnitTests.Configuration } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions)) .And(x => x.GivenTheLoadBalancerFactoryReturns()) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(expected)) .And(x => x.ThenTheAuthenticationOptionsAre(expected)) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_matches_more_than_one_placeholder() - { - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = "Get", - ReRouteIsCaseSensitive = true - } - } - })) - .And(x => x.GivenTheConfigIsValid()) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}/variants/{variantId}") - .WithUpstreamHttpMethod("Get") - .WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$") - .Build() - })) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_matches_more_than_one_placeholder_with_trailing_slash() - { - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}/", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = "Get", - ReRouteIsCaseSensitive = true - } - } - })) - .And(x => x.GivenTheConfigIsValid()) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}/variants/{variantId}/") - .WithUpstreamHttpMethod("Get") - .WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$") - .Build() - })) - .BDDfy(); - } - - [Fact] - public void should_create_template_pattern_that_matches_to_end_of_string() - { - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/", - DownstreamPathTemplate = "/api/products/", - UpstreamHttpMethod = "Get", - ReRouteIsCaseSensitive = true - } - } - })) - .And(x => x.GivenTheConfigIsValid()) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamPathTemplate("/api/products/") - .WithUpstreamPathTemplate("/") - .WithUpstreamHttpMethod("Get") - .WithUpstreamTemplatePattern("^/$") - .Build() - })) + .And(x => x.ThenTheAuthOptionsCreatorIsCalledCorrectly()) .BDDfy(); } @@ -642,6 +462,9 @@ namespace Ocelot.UnitTests.Configuration result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); result.UpstreamPathTemplate.Value.ShouldBe(expected.UpstreamPathTemplate.Value); result.UpstreamTemplatePattern.ShouldBe(expected.UpstreamTemplatePattern); + result.ClaimsToClaims.Count.ShouldBe(expected.ClaimsToClaims.Count); + result.ClaimsToHeaders.Count.ShouldBe(expected.ClaimsToHeaders.Count); + result.ClaimsToQueries.Count.ShouldBe(expected.ClaimsToQueries.Count); } } @@ -699,5 +522,32 @@ namespace Ocelot.UnitTests.Configuration _qosProviderHouse .Verify(x => x.Add(It.IsAny(), _qosProvider.Object), Times.Once); } + + private void GivenTheClaimsToThingCreatorReturns(List claimsToThing) + { + _claimsToThingCreator + .Setup(x => x.Create(_fileConfiguration.ReRoutes[0].AddHeadersToRequest)) + .Returns(claimsToThing); + } + + private void GivenTheAuthOptionsCreatorReturns(AuthenticationOptions authOptions) + { + _authOptionsCreator + .Setup(x => x.Create(It.IsAny())) + .Returns(authOptions); + } + + private void ThenTheAuthOptionsCreatorIsCalledCorrectly() + { + _authOptionsCreator + .Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once); + } + + private void GivenTheUpstreamTemplatePatternCreatorReturns(string pattern) + { + _upstreamTemplatePatternCreator + .Setup(x => x.Create(It.IsAny())) + .Returns(pattern); + } } } diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs new file mode 100644 index 00000000..34db1232 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs @@ -0,0 +1,122 @@ +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class UpstreamTemplatePatternCreatorTests + { + private FileReRoute _fileReRoute; + private UpstreamTemplatePatternCreator _creator; + private string _result; + + public UpstreamTemplatePatternCreatorTests() + { + _creator = new UpstreamTemplatePatternCreator(); + } + + [Fact] + public void should_set_upstream_template_pattern_to_ignore_case_sensitivity() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/PRODUCTS/{productId}", + ReRouteIsCaseSensitive = false + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("(?i)/PRODUCTS/.*/$")) + .BDDfy(); + } + + [Fact] + public void should_set_upstream_template_pattern_to_respect_case_sensitivity() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/PRODUCTS/{productId}", + ReRouteIsCaseSensitive = true + }; + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("/PRODUCTS/.*/$")) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_matches_anything_to_end_of_string() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}", + ReRouteIsCaseSensitive = true + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("/api/products/.*/$")) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_matches_more_than_one_placeholder() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}", + ReRouteIsCaseSensitive = true + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("/api/products/.*/variants/.*/$")) + .BDDfy(); + } + [Fact] + public void should_create_template_pattern_that_matches_more_than_one_placeholder_with_trailing_slash() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}/", + ReRouteIsCaseSensitive = true + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("/api/products/.*/variants/.*/$")) + .BDDfy(); + } + + [Fact] + public void should_create_template_pattern_that_matches_to_end_of_string() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/" + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/$")) + .BDDfy(); + } + + private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void WhenICreateTheTemplatePattern() + { + _result = _creator.Create(_fileReRoute); + } + + private void ThenTheFollowingIsReturned(string expected) + { + _result.ShouldBe(expected); + } + } +} \ No newline at end of file From d4119ab33dfeb1bcd4268fa9e1dca6eb921329f6 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 1 Mar 2017 08:11:39 +0000 Subject: [PATCH 02/19] extracted thing that creates request id key --- .../Creator/FileOcelotConfigurationCreator.cs | 18 +--- .../Creator/IRequestIdKeyCreator.cs | 9 ++ .../Creator/RequestIdKeyCreator.cs | 18 ++++ .../ServiceCollectionExtensions.cs | 1 + .../FileConfigurationCreatorTests.cs | 25 ++++- .../Configuration/RequestIdKeyCreatorTests.cs | 91 +++++++++++++++++++ 6 files changed, 146 insertions(+), 16 deletions(-) create mode 100644 src/Ocelot/Configuration/Creator/IRequestIdKeyCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/RequestIdKeyCreator.cs create mode 100644 test/Ocelot.UnitTests/Configuration/RequestIdKeyCreatorTests.cs diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 0d853f94..c9161dee 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -31,6 +31,7 @@ namespace Ocelot.Configuration.Creator private readonly IClaimsToThingCreator _claimsToThingCreator; private readonly IAuthenticationOptionsCreator _authOptionsCreator; private IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; + private IRequestIdKeyCreator _requestIdKeyCreator; public FileOcelotConfigurationCreator( IOptions options, @@ -42,8 +43,10 @@ namespace Ocelot.Configuration.Creator IQosProviderHouse qosProviderHouse, IClaimsToThingCreator claimsToThingCreator, IAuthenticationOptionsCreator authOptionsCreator, - IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator) + IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator, + IRequestIdKeyCreator requestIdKeyCreator) { + _requestIdKeyCreator = requestIdKeyCreator; _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; _authOptionsCreator = authOptionsCreator; _loadBalanceFactory = loadBalancerFactory; @@ -105,7 +108,7 @@ namespace Ocelot.Configuration.Creator var isCached = IsCached(fileReRoute); - var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration); + var requestIdKey = _requestIdKeyCreator.Create(fileReRoute, globalConfiguration); var reRouteKey = BuildReRouteKey(fileReRoute); @@ -210,17 +213,6 @@ namespace Ocelot.Configuration.Creator return fileReRoute.FileCacheOptions.TtlSeconds > 0; } - private string BuildRequestId(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) - { - var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); - - var requestIdKey = globalRequestIdConfiguration - ? globalConfiguration.RequestIdKey - : fileReRoute.RequestIdKey; - - return requestIdKey; - } - private string BuildReRouteKey(FileReRoute fileReRoute) { //note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain diff --git a/src/Ocelot/Configuration/Creator/IRequestIdKeyCreator.cs b/src/Ocelot/Configuration/Creator/IRequestIdKeyCreator.cs new file mode 100644 index 00000000..2800f8a5 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IRequestIdKeyCreator.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IRequestIdKeyCreator + { + string Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration); + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/RequestIdKeyCreator.cs b/src/Ocelot/Configuration/Creator/RequestIdKeyCreator.cs new file mode 100644 index 00000000..caf00402 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/RequestIdKeyCreator.cs @@ -0,0 +1,18 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class RequestIdKeyCreator : IRequestIdKeyCreator + { + public string Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) + { + var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); + + var requestIdKey = globalRequestIdConfiguration + ? globalConfiguration.RequestIdKey + : fileReRoute.RequestIdKey; + + return requestIdKey; + } + } +} \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 54fef846..9b777248 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -63,6 +63,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(); diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index bafea3cd..b0a4fa06 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -34,6 +34,7 @@ namespace Ocelot.UnitTests.Configuration private Mock _claimsToThingCreator; private Mock _authOptionsCreator; private Mock _upstreamTemplatePatternCreator; + private Mock _requestIdKeyCreator; public FileConfigurationCreatorTests() { @@ -49,12 +50,13 @@ namespace Ocelot.UnitTests.Configuration _claimsToThingCreator = new Mock(); _authOptionsCreator = new Mock(); _upstreamTemplatePatternCreator = new Mock(); + _requestIdKeyCreator = new Mock(); _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _fileConfig.Object, _validator.Object, _logger.Object, _loadBalancerFactory.Object, _loadBalancerHouse.Object, _qosProviderFactory.Object, _qosProviderHouse.Object, _claimsToThingCreator.Object, - _authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object); + _authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object, _requestIdKeyCreator.Object); } [Fact] @@ -279,7 +281,7 @@ namespace Ocelot.UnitTests.Configuration } [Fact] - public void should_set_global_request_id_key() + public void should_call_request_id_creator() { this.Given(x => x.GivenTheConfigIs(new FileConfiguration { @@ -298,7 +300,8 @@ namespace Ocelot.UnitTests.Configuration RequestIdKey = "blahhhh" } })) - .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheRequestIdCreatorReturns("blahhhh")) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(new List { @@ -309,6 +312,7 @@ namespace Ocelot.UnitTests.Configuration .WithRequestIdKey("blahhhh") .Build() })) + .And(x => x.ThenTheRequestIdKeyCreatorIsCalledCorrectly()) .BDDfy(); } @@ -465,6 +469,7 @@ namespace Ocelot.UnitTests.Configuration result.ClaimsToClaims.Count.ShouldBe(expected.ClaimsToClaims.Count); result.ClaimsToHeaders.Count.ShouldBe(expected.ClaimsToHeaders.Count); result.ClaimsToQueries.Count.ShouldBe(expected.ClaimsToQueries.Count); + result.RequestIdKey.ShouldBe(expected.RequestIdKey); } } @@ -549,5 +554,19 @@ namespace Ocelot.UnitTests.Configuration .Setup(x => x.Create(It.IsAny())) .Returns(pattern); } + + private void ThenTheRequestIdKeyCreatorIsCalledCorrectly() + { + _requestIdKeyCreator + .Verify(x => x.Create(_fileConfiguration.ReRoutes[0], _fileConfiguration.GlobalConfiguration), Times.Once); + } + + private void GivenTheRequestIdCreatorReturns(string requestId) + { + _requestIdKeyCreator + .Setup(x => x.Create(It.IsAny(), It.IsAny())) + .Returns(requestId); + } + } } diff --git a/test/Ocelot.UnitTests/Configuration/RequestIdKeyCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RequestIdKeyCreatorTests.cs new file mode 100644 index 00000000..f52cb808 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/RequestIdKeyCreatorTests.cs @@ -0,0 +1,91 @@ +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class RequestIdKeyCreatorTests + { + private FileReRoute _fileReRoute; + private FileGlobalConfiguration _fileGlobalConfig; + private string _result; + private RequestIdKeyCreator _creator; + + public RequestIdKeyCreatorTests() + { + _creator = new RequestIdKeyCreator(); + } + + [Fact] + public void should_use_global_configuration() + { + var reRoute = new FileReRoute(); + var globalConfig = new FileGlobalConfiguration + { + RequestIdKey = "cheese" + }; + + this.Given(x => x.GivenTheFollowingReRoute(reRoute)) + .And(x => x.GivenTheFollowingGlobalConfig(globalConfig)) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheFollowingIsReturned("cheese")) + .BDDfy(); + } + + [Fact] + public void should_use_re_route_specific() + { + var reRoute = new FileReRoute + { + RequestIdKey = "cheese" + }; + var globalConfig = new FileGlobalConfiguration(); + + this.Given(x => x.GivenTheFollowingReRoute(reRoute)) + .And(x => x.GivenTheFollowingGlobalConfig(globalConfig)) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheFollowingIsReturned("cheese")) + .BDDfy(); + } + + [Fact] + public void should_use_global_cofiguration_over_re_route_specific() + { + var reRoute = new FileReRoute + { + RequestIdKey = "cheese" + }; var globalConfig = new FileGlobalConfiguration + { + RequestIdKey = "cheese" + }; + + this.Given(x => x.GivenTheFollowingReRoute(reRoute)) + .And(x => x.GivenTheFollowingGlobalConfig(globalConfig)) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheFollowingIsReturned("cheese")) + .BDDfy(); + } + + private void GivenTheFollowingReRoute(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void GivenTheFollowingGlobalConfig(FileGlobalConfiguration globalConfig) + { + _fileGlobalConfig = globalConfig; + } + + private void WhenICreate() + { + _result = _creator.Create(_fileReRoute, _fileGlobalConfig); + } + + private void ThenTheFollowingIsReturned(string expected) + { + _result.ShouldBe(expected); + } + } +} \ No newline at end of file From fff743ccf8102f528900aa7ef407866f92bcf394 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 1 Mar 2017 12:42:37 +0000 Subject: [PATCH 03/19] pulling out service config cretor --- .../Configuration/Builder/ReRouteBuilder.cs | 4 +-- .../ServiceProviderConfiguraionBuilder.cs | 4 +-- .../Creator/FileOcelotConfigurationCreator.cs | 29 +++++-------------- .../IServiceProviderConfigurationCreator.cs | 9 ++++++ .../ServiceProviderConfigurationCreator.cs | 26 +++++++++++++++++ src/Ocelot/Configuration/ReRoute.cs | 4 +-- .../ServiceProviderConfiguraion.cs | 4 +-- .../ServiceCollectionExtensions.cs | 1 + .../IServiceDiscoveryProviderFactory.cs | 2 +- .../ServiceDiscoveryProviderFactory.cs | 2 +- .../FileConfigurationCreatorTests.cs | 21 +++++++++++++- .../LoadBalancer/LoadBalancerFactoryTests.cs | 4 +-- .../ServiceProviderFactoryTests.cs | 4 +-- 13 files changed, 77 insertions(+), 37 deletions(-) create mode 100644 src/Ocelot/Configuration/Creator/IServiceProviderConfigurationCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 1fc31f03..30d9d87a 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -25,7 +25,7 @@ namespace Ocelot.Configuration.Builder private string _downstreamHost; private int _downstreamPort; private string _loadBalancer; - private ServiceProviderConfiguraion _serviceProviderConfiguraion; + private ServiceProviderConfiguration _serviceProviderConfiguraion; private bool _useQos; private QoSOptions _qosOptions; public bool _enableRateLimiting; @@ -150,7 +150,7 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithServiceProviderConfiguraion(ServiceProviderConfiguraion serviceProviderConfiguraion) + public ReRouteBuilder WithServiceProviderConfiguraion(ServiceProviderConfiguration serviceProviderConfiguraion) { _serviceProviderConfiguraion = serviceProviderConfiguraion; return this; diff --git a/src/Ocelot/Configuration/Builder/ServiceProviderConfiguraionBuilder.cs b/src/Ocelot/Configuration/Builder/ServiceProviderConfiguraionBuilder.cs index 129acae8..7c3e598a 100644 --- a/src/Ocelot/Configuration/Builder/ServiceProviderConfiguraionBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ServiceProviderConfiguraionBuilder.cs @@ -53,9 +53,9 @@ namespace Ocelot.Configuration.Builder } - public ServiceProviderConfiguraion Build() + public ServiceProviderConfiguration Build() { - return new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _downstreamPort, _userServiceDiscovery, + return new ServiceProviderConfiguration(_serviceName, _downstreamHost, _downstreamPort, _userServiceDiscovery, _serviceDiscoveryProvider, _serviceDiscoveryProviderHost,_serviceDiscoveryProviderPort); } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index c9161dee..9432aeee 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -32,6 +32,7 @@ namespace Ocelot.Configuration.Creator private readonly IAuthenticationOptionsCreator _authOptionsCreator; private IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; private IRequestIdKeyCreator _requestIdKeyCreator; + private IServiceProviderConfigurationCreator _serviceProviderConfigCreator; public FileOcelotConfigurationCreator( IOptions options, @@ -44,7 +45,8 @@ namespace Ocelot.Configuration.Creator IClaimsToThingCreator claimsToThingCreator, IAuthenticationOptionsCreator authOptionsCreator, IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator, - IRequestIdKeyCreator requestIdKeyCreator) + IRequestIdKeyCreator requestIdKeyCreator, + IServiceProviderConfigurationCreator serviceProviderConfigCreator) { _requestIdKeyCreator = requestIdKeyCreator; _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; @@ -57,6 +59,7 @@ namespace Ocelot.Configuration.Creator _configurationValidator = configurationValidator; _logger = logger; _claimsToThingCreator = claimsToThingCreator; + _serviceProviderConfigCreator = serviceProviderConfigCreator; } public async Task> Create() @@ -110,13 +113,13 @@ namespace Ocelot.Configuration.Creator var requestIdKey = _requestIdKeyCreator.Create(fileReRoute, globalConfiguration); - var reRouteKey = BuildReRouteKey(fileReRoute); + var reRouteKey = CreateReRouteKey(fileReRoute); var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); var isQos = IsQoS(fileReRoute); - var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration); + var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileReRoute, globalConfiguration); var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute); @@ -213,7 +216,7 @@ namespace Ocelot.Configuration.Creator return fileReRoute.FileCacheOptions.TtlSeconds > 0; } - private string BuildReRouteKey(FileReRoute fileReRoute) + private string CreateReRouteKey(FileReRoute fileReRoute) { //note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}"; @@ -232,24 +235,6 @@ namespace Ocelot.Configuration.Creator _qosProviderHouse.Add(reRoute.ReRouteKey, loadBalancer); } - private ServiceProviderConfiguraion BuildServiceProviderConfiguration(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) - { - var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName) - && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); - - var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; - - return new ServiceProviderConfiguraionBuilder() - .WithServiceName(fileReRoute.ServiceName) - .WithDownstreamHost(fileReRoute.DownstreamHost) - .WithDownstreamPort(fileReRoute.DownstreamPort) - .WithUseServiceDiscovery(useServiceDiscovery) - .WithServiceDiscoveryProvider(globalConfiguration?.ServiceDiscoveryProvider?.Provider) - .WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host) - .WithServiceDiscoveryProviderPort(serviceProviderPort) - .Build(); - } - private bool IsPlaceHolder(string upstreamTemplate, int i) { return upstreamTemplate[i] == '{'; diff --git a/src/Ocelot/Configuration/Creator/IServiceProviderConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IServiceProviderConfigurationCreator.cs new file mode 100644 index 00000000..1c03d893 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IServiceProviderConfigurationCreator.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IServiceProviderConfigurationCreator + { + ServiceProviderConfiguration Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration); + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs new file mode 100644 index 00000000..074d1661 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs @@ -0,0 +1,26 @@ +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class ServiceProviderConfigurationCreator : IServiceProviderConfigurationCreator + { + public ServiceProviderConfiguration Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) + { + var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName) + && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); + + var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; + + return new ServiceProviderConfiguraionBuilder() + .WithServiceName(fileReRoute.ServiceName) + .WithDownstreamHost(fileReRoute.DownstreamHost) + .WithDownstreamPort(fileReRoute.DownstreamPort) + .WithUseServiceDiscovery(useServiceDiscovery) + .WithServiceDiscoveryProvider(globalConfiguration?.ServiceDiscoveryProvider?.Provider) + .WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host) + .WithServiceDiscoveryProviderPort(serviceProviderPort) + .Build(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index f629867a..97bf8e39 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -25,7 +25,7 @@ namespace Ocelot.Configuration string downstreamHost, int downstreamPort, string reRouteKey, - ServiceProviderConfiguraion serviceProviderConfiguraion, + ServiceProviderConfiguration serviceProviderConfiguraion, bool isQos, QoSOptions qos, bool enableRateLimit, @@ -81,7 +81,7 @@ namespace Ocelot.Configuration public string LoadBalancer {get;private set;} public string DownstreamHost { get; private set; } public int DownstreamPort { get; private set; } - public ServiceProviderConfiguraion ServiceProviderConfiguraion { get; private set; } + public ServiceProviderConfiguration ServiceProviderConfiguraion { get; private set; } public bool EnableEndpointRateLimiting { get; private set; } public RateLimitOptions RateLimitOptions { get; private set; } } diff --git a/src/Ocelot/Configuration/ServiceProviderConfiguraion.cs b/src/Ocelot/Configuration/ServiceProviderConfiguraion.cs index d471a9e5..664fa4c3 100644 --- a/src/Ocelot/Configuration/ServiceProviderConfiguraion.cs +++ b/src/Ocelot/Configuration/ServiceProviderConfiguraion.cs @@ -1,8 +1,8 @@ namespace Ocelot.Configuration { - public class ServiceProviderConfiguraion + public class ServiceProviderConfiguration { - public ServiceProviderConfiguraion(string serviceName, string downstreamHost, + public ServiceProviderConfiguration(string serviceName, string downstreamHost, int downstreamPort, bool useServiceDiscovery, string serviceDiscoveryProvider, string serviceProviderHost, int serviceProviderPort) { ServiceName = serviceName; diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 9b777248..7d950e84 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -64,6 +64,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(); diff --git a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs index 6c6c3d4c..bece9fc6 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs @@ -5,6 +5,6 @@ namespace Ocelot.ServiceDiscovery { public interface IServiceDiscoveryProviderFactory { - IServiceDiscoveryProvider Get(ServiceProviderConfiguraion serviceConfig); + IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig); } } \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index 00622190..49151c01 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -6,7 +6,7 @@ namespace Ocelot.ServiceDiscovery { public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory { - public IServiceDiscoveryProvider Get(ServiceProviderConfiguraion serviceConfig) + public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig) { if (serviceConfig.UseServiceDiscovery) { diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index b0a4fa06..987816fd 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -35,6 +35,7 @@ namespace Ocelot.UnitTests.Configuration private Mock _authOptionsCreator; private Mock _upstreamTemplatePatternCreator; private Mock _requestIdKeyCreator; + private Mock _serviceProviderConfigCreator; public FileConfigurationCreatorTests() { @@ -51,12 +52,14 @@ namespace Ocelot.UnitTests.Configuration _authOptionsCreator = new Mock(); _upstreamTemplatePatternCreator = new Mock(); _requestIdKeyCreator = new Mock(); + _serviceProviderConfigCreator = new Mock(); _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _fileConfig.Object, _validator.Object, _logger.Object, _loadBalancerFactory.Object, _loadBalancerHouse.Object, _qosProviderFactory.Object, _qosProviderHouse.Object, _claimsToThingCreator.Object, - _authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object, _requestIdKeyCreator.Object); + _authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object, _requestIdKeyCreator.Object, + _serviceProviderConfigCreator.Object); } [Fact] @@ -470,9 +473,25 @@ namespace Ocelot.UnitTests.Configuration result.ClaimsToHeaders.Count.ShouldBe(expected.ClaimsToHeaders.Count); result.ClaimsToQueries.Count.ShouldBe(expected.ClaimsToQueries.Count); result.RequestIdKey.ShouldBe(expected.RequestIdKey); + } } + private void ThenTheServiceConfigurationIs(ServiceProviderConfiguration expected) + { + for (int i = 0; i < _config.Data.ReRoutes.Count; i++) + { + var result = _config.Data.ReRoutes[i]; + result.ServiceProviderConfiguraion.DownstreamHost.ShouldBe(expected.DownstreamHost); + result.ServiceProviderConfiguraion.DownstreamPort.ShouldBe(expected.DownstreamPort); + result.ServiceProviderConfiguraion.ServiceDiscoveryProvider.ShouldBe(expected.ServiceDiscoveryProvider); + result.ServiceProviderConfiguraion.ServiceName.ShouldBe(expected.ServiceName); + result.ServiceProviderConfiguraion.ServiceProviderHost.ShouldBe(expected.ServiceProviderHost); + result.ServiceProviderConfiguraion.ServiceProviderPort.ShouldBe(expected.ServiceProviderPort); + } + } + + private void ThenTheAuthenticationOptionsAre(List expectedReRoutes) { for (int i = 0; i < _config.Data.ReRoutes.Count; i++) diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index 767b4272..9b2fc727 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -90,14 +90,14 @@ namespace Ocelot.UnitTests.LoadBalancer private void GivenTheServiceProviderFactoryReturns() { _serviceProviderFactory - .Setup(x => x.Get(It.IsAny())) + .Setup(x => x.Get(It.IsAny())) .Returns(_serviceProvider.Object); } private void ThenTheServiceProviderIsCalledCorrectly() { _serviceProviderFactory - .Verify(x => x.Get(It.IsAny()), Times.Once); + .Verify(x => x.Get(It.IsAny()), Times.Once); } private void GivenAReRoute(ReRoute reRoute) diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index b3053afa..9f88b95e 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -9,7 +9,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery { public class ServiceProviderFactoryTests { - private ServiceProviderConfiguraion _serviceConfig; + private ServiceProviderConfiguration _serviceConfig; private IServiceDiscoveryProvider _result; private readonly ServiceDiscoveryProviderFactory _factory; @@ -48,7 +48,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery .BDDfy(); } - private void GivenTheReRoute(ServiceProviderConfiguraion serviceConfig) + private void GivenTheReRoute(ServiceProviderConfiguration serviceConfig) { _serviceConfig = serviceConfig; } From 034732ce909ae2898553ab8e073720ca5d5da259 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 1 Mar 2017 23:12:00 +0000 Subject: [PATCH 04/19] added lame test for service config creator --- ...=> ServiceProviderConfigurationBuilder.cs} | 16 ++-- .../ServiceProviderConfigurationCreator.cs | 2 +- .../FileConfigurationCreatorTests.cs | 4 +- .../ServiceProviderCreatorTests.cs | 76 +++++++++++++++++++ .../LoadBalancer/LoadBalancerFactoryTests.cs | 8 +- .../ServiceProviderFactoryTests.cs | 4 +- 6 files changed, 93 insertions(+), 17 deletions(-) rename src/Ocelot/Configuration/Builder/{ServiceProviderConfiguraionBuilder.cs => ServiceProviderConfigurationBuilder.cs} (64%) create mode 100644 test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs diff --git a/src/Ocelot/Configuration/Builder/ServiceProviderConfiguraionBuilder.cs b/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs similarity index 64% rename from src/Ocelot/Configuration/Builder/ServiceProviderConfiguraionBuilder.cs rename to src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs index 7c3e598a..cb3c521c 100644 --- a/src/Ocelot/Configuration/Builder/ServiceProviderConfiguraionBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs @@ -1,6 +1,6 @@ namespace Ocelot.Configuration.Builder { - public class ServiceProviderConfiguraionBuilder + public class ServiceProviderConfigurationBuilder { private string _serviceName; private string _downstreamHost; @@ -10,43 +10,43 @@ namespace Ocelot.Configuration.Builder private string _serviceDiscoveryProviderHost; private int _serviceDiscoveryProviderPort; - public ServiceProviderConfiguraionBuilder WithServiceName(string serviceName) + public ServiceProviderConfigurationBuilder WithServiceName(string serviceName) { _serviceName = serviceName; return this; } - public ServiceProviderConfiguraionBuilder WithDownstreamHost(string downstreamHost) + public ServiceProviderConfigurationBuilder WithDownstreamHost(string downstreamHost) { _downstreamHost = downstreamHost; return this; } - public ServiceProviderConfiguraionBuilder WithDownstreamPort(int downstreamPort) + public ServiceProviderConfigurationBuilder WithDownstreamPort(int downstreamPort) { _downstreamPort = downstreamPort; return this; } - public ServiceProviderConfiguraionBuilder WithUseServiceDiscovery(bool userServiceDiscovery) + public ServiceProviderConfigurationBuilder WithUseServiceDiscovery(bool userServiceDiscovery) { _userServiceDiscovery = userServiceDiscovery; return this; } - public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProvider(string serviceDiscoveryProvider) + public ServiceProviderConfigurationBuilder WithServiceDiscoveryProvider(string serviceDiscoveryProvider) { _serviceDiscoveryProvider = serviceDiscoveryProvider; return this; } - public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProviderHost(string serviceDiscoveryProviderHost) + public ServiceProviderConfigurationBuilder WithServiceDiscoveryProviderHost(string serviceDiscoveryProviderHost) { _serviceDiscoveryProviderHost = serviceDiscoveryProviderHost; return this; } - public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProviderPort(int serviceDiscoveryProviderPort) + public ServiceProviderConfigurationBuilder WithServiceDiscoveryProviderPort(int serviceDiscoveryProviderPort) { _serviceDiscoveryProviderPort = serviceDiscoveryProviderPort; return this; diff --git a/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs index 074d1661..8132d021 100644 --- a/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs @@ -12,7 +12,7 @@ namespace Ocelot.Configuration.Creator var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; - return new ServiceProviderConfiguraionBuilder() + return new ServiceProviderConfigurationBuilder() .WithServiceName(fileReRoute.ServiceName) .WithDownstreamHost(fileReRoute.DownstreamHost) .WithDownstreamPort(fileReRoute.DownstreamPort) diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 987816fd..3ea7af3b 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -209,7 +209,7 @@ namespace Ocelot.UnitTests.Configuration .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") - .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder() + .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder() .WithUseServiceDiscovery(true) .WithServiceDiscoveryProvider("consul") .WithServiceDiscoveryProviderHost("127.0.0.1") @@ -244,7 +244,7 @@ namespace Ocelot.UnitTests.Configuration .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") - .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder() + .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder() .WithUseServiceDiscovery(false) .Build()) .Build() diff --git a/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs new file mode 100644 index 00000000..8d76e864 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs @@ -0,0 +1,76 @@ +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class ServiceProviderCreatorTests + { + private ServiceProviderConfigurationCreator _creator; + private FileReRoute _reRoute; + private FileGlobalConfiguration _globalConfig; + private ServiceProviderConfiguration _result; + + public ServiceProviderCreatorTests() + { + _creator = new ServiceProviderConfigurationCreator(); + } + + [Fact] + public void should_create_service_provider_config() + { + var reRoute = new FileReRoute(); + + var globalConfig = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Provider = "consul", + Host = "127.0.0.1", + Port = 1234 + } + }; + + var expected = new ServiceProviderConfigurationBuilder() + .WithServiceDiscoveryProvider("consul") + .WithServiceDiscoveryProviderHost("127.0.0.1") + .WithServiceDiscoveryProviderPort(1234) + .Build(); + + this.Given(x => x.GivenTheFollowingReRoute(reRoute)) + .And(x => x.GivenTheFollowingGlobalConfig(globalConfig)) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheConfigIs(expected)) + .BDDfy(); + } + + private void GivenTheFollowingReRoute(FileReRoute fileReRoute) + { + _reRoute = fileReRoute; + } + + private void GivenTheFollowingGlobalConfig(FileGlobalConfiguration fileGlobalConfig) + { + _globalConfig = fileGlobalConfig; + } + + private void WhenICreate() + { + _result = _creator.Create(_reRoute, _globalConfig); + } + + private void ThenTheConfigIs(ServiceProviderConfiguration expected) + { + _result.DownstreamHost.ShouldBe(expected.DownstreamHost); + _result.DownstreamPort.ShouldBe(expected.DownstreamPort); + _result.ServiceDiscoveryProvider.ShouldBe(expected.ServiceDiscoveryProvider); + _result.ServiceName.ShouldBe(expected.ServiceName); + _result.ServiceProviderHost.ShouldBe(expected.ServiceProviderHost); + _result.ServiceProviderPort.ShouldBe(expected.ServiceProviderPort); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index 9b2fc727..f8bf8f6c 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -28,7 +28,7 @@ namespace Ocelot.UnitTests.LoadBalancer public void should_return_no_load_balancer() { var reRoute = new ReRouteBuilder() - .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) + .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder().Build()) .WithUpstreamHttpMethod("Get") .Build(); @@ -45,7 +45,7 @@ namespace Ocelot.UnitTests.LoadBalancer var reRoute = new ReRouteBuilder() .WithLoadBalancer("RoundRobin") .WithUpstreamHttpMethod("Get") - .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) + .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder().Build()) .Build(); this.Given(x => x.GivenAReRoute(reRoute)) @@ -61,7 +61,7 @@ namespace Ocelot.UnitTests.LoadBalancer var reRoute = new ReRouteBuilder() .WithLoadBalancer("LeastConnection") .WithUpstreamHttpMethod("Get") - .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) + .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder().Build()) .Build(); this.Given(x => x.GivenAReRoute(reRoute)) @@ -77,7 +77,7 @@ namespace Ocelot.UnitTests.LoadBalancer var reRoute = new ReRouteBuilder() .WithLoadBalancer("RoundRobin") .WithUpstreamHttpMethod("Get") - .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) + .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder().Build()) .Build(); this.Given(x => x.GivenAReRoute(reRoute)) diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index 9f88b95e..afa86c1b 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -21,7 +21,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery [Fact] public void should_return_no_service_provider() { - var serviceConfig = new ServiceProviderConfiguraionBuilder() + var serviceConfig = new ServiceProviderConfigurationBuilder() .WithDownstreamHost("127.0.0.1") .WithDownstreamPort(80) .WithUseServiceDiscovery(false) @@ -36,7 +36,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery [Fact] public void should_return_consul_service_provider() { - var serviceConfig = new ServiceProviderConfiguraionBuilder() + var serviceConfig = new ServiceProviderConfigurationBuilder() .WithServiceName("product") .WithUseServiceDiscovery(true) .WithServiceDiscoveryProvider("Consul") From 6661cb5f32f9d5562123ab7aeed6188bc58f1126 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 1 Mar 2017 23:15:30 +0000 Subject: [PATCH 05/19] use config tests --- .../Configuration/Creator/FileOcelotConfigurationCreator.cs | 1 - src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs | 1 - .../Configuration/FileConfigurationCreatorTests.cs | 2 -- 3 files changed, 4 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 9432aeee..65962070 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -22,7 +22,6 @@ namespace Ocelot.Configuration.Creator { private readonly IOptions _options; private readonly IConfigurationValidator _configurationValidator; - private readonly ILogger _logger; private readonly ILoadBalancerFactory _loadBalanceFactory; private readonly ILoadBalancerHouse _loadBalancerHouse; diff --git a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs index bece9fc6..18c88c76 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs @@ -1,4 +1,3 @@ -using System; using Ocelot.Configuration; namespace Ocelot.ServiceDiscovery diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 3ea7af3b..f8b9425f 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -6,7 +6,6 @@ using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; -using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Requester.QoS; @@ -491,7 +490,6 @@ namespace Ocelot.UnitTests.Configuration } } - private void ThenTheAuthenticationOptionsAre(List expectedReRoutes) { for (int i = 0; i < _config.Data.ReRoutes.Count; i++) From 0a2d7a6922e0508a67db3fd50489b241e7e05035 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 1 Mar 2017 23:28:32 +0000 Subject: [PATCH 06/19] qos options creator in own class --- .../Creator/FileOcelotConfigurationCreator.cs | 17 +++--- .../Creator/IQoSOptionsCreator.cs | 9 ++++ .../Creator/QoSOptionsCreator.cs | 17 ++++++ .../ServiceCollectionExtensions.cs | 1 + .../FileConfigurationCreatorTests.cs | 54 ++++++++++++++++++- .../FileConfigurationControllerTests.cs | 2 - 6 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 src/Ocelot/Configuration/Creator/IQoSOptionsCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 65962070..4dd873cd 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -32,6 +32,7 @@ namespace Ocelot.Configuration.Creator private IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; private IRequestIdKeyCreator _requestIdKeyCreator; private IServiceProviderConfigurationCreator _serviceProviderConfigCreator; + private IQoSOptionsCreator _qosOptionsCreator; public FileOcelotConfigurationCreator( IOptions options, @@ -45,7 +46,9 @@ namespace Ocelot.Configuration.Creator IAuthenticationOptionsCreator authOptionsCreator, IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator, IRequestIdKeyCreator requestIdKeyCreator, - IServiceProviderConfigurationCreator serviceProviderConfigCreator) + IServiceProviderConfigurationCreator serviceProviderConfigCreator, + IQoSOptionsCreator qosOptionsCreator + ) { _requestIdKeyCreator = requestIdKeyCreator; _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; @@ -59,6 +62,7 @@ namespace Ocelot.Configuration.Creator _logger = logger; _claimsToThingCreator = claimsToThingCreator; _serviceProviderConfigCreator = serviceProviderConfigCreator; + _qosOptionsCreator = qosOptionsCreator; } public async Task> Create() @@ -128,7 +132,7 @@ namespace Ocelot.Configuration.Creator var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest); - var qosOptions = BuildQoSOptions(fileReRoute); + var qosOptions = _qosOptionsCreator.Create(fileReRoute); var enableRateLimiting = IsEnableRateLimiting(fileReRoute); @@ -186,15 +190,6 @@ namespace Ocelot.Configuration.Creator return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false; } - private QoSOptions BuildQoSOptions(FileReRoute fileReRoute) - { - return new QoSOptionsBuilder() - .WithExceptionsAllowedBeforeBreaking(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking) - .WithDurationOfBreak(fileReRoute.QoSOptions.DurationOfBreak) - .WithTimeoutValue(fileReRoute.QoSOptions.TimeoutValue) - .Build(); - } - private bool IsQoS(FileReRoute fileReRoute) { return fileReRoute.QoSOptions?.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions?.TimeoutValue > 0; diff --git a/src/Ocelot/Configuration/Creator/IQoSOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IQoSOptionsCreator.cs new file mode 100644 index 00000000..a0686ae5 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IQoSOptionsCreator.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IQoSOptionsCreator + { + QoSOptions Create(FileReRoute fileReRoute); + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs b/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs new file mode 100644 index 00000000..ddeef910 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs @@ -0,0 +1,17 @@ +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class QoSOptionsCreator : IQoSOptionsCreator + { + public QoSOptions Create(FileReRoute fileReRoute) + { + return new QoSOptionsBuilder() + .WithExceptionsAllowedBeforeBreaking(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking) + .WithDurationOfBreak(fileReRoute.QoSOptions.DurationOfBreak) + .WithTimeoutValue(fileReRoute.QoSOptions.TimeoutValue) + .Build(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 7d950e84..1ebb6d42 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -65,6 +65,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(); diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index f8b9425f..bb5866f9 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -35,6 +35,7 @@ namespace Ocelot.UnitTests.Configuration private Mock _upstreamTemplatePatternCreator; private Mock _requestIdKeyCreator; private Mock _serviceProviderConfigCreator; + private Mock _qosOptionsCreator; public FileConfigurationCreatorTests() { @@ -52,13 +53,50 @@ namespace Ocelot.UnitTests.Configuration _upstreamTemplatePatternCreator = new Mock(); _requestIdKeyCreator = new Mock(); _serviceProviderConfigCreator = new Mock(); + _qosOptionsCreator = new Mock(); _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _fileConfig.Object, _validator.Object, _logger.Object, _loadBalancerFactory.Object, _loadBalancerHouse.Object, _qosProviderFactory.Object, _qosProviderHouse.Object, _claimsToThingCreator.Object, _authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object, _requestIdKeyCreator.Object, - _serviceProviderConfigCreator.Object); + _serviceProviderConfigCreator.Object, _qosOptionsCreator.Object); + } + + [Fact] + public void should_call_qos_options_creator() + { + var expected = new QoSOptionsBuilder() + .WithDurationOfBreak(1) + .WithExceptionsAllowedBeforeBreaking(1) + .WithTimeoutValue(1) + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHost = "127.0.0.1", + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = "Get", + QoSOptions = new FileQoSOptions + { + TimeoutValue = 1, + DurationOfBreak = 1, + ExceptionsAllowedBeforeBreaking = 1 + } + } + }, + })) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheQosProviderFactoryReturns()) + .And(x => x.GivenTheQosOptionsCreatorReturns(expected)) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheQosOptionsAre(expected)) + .BDDfy(); } [Fact] @@ -585,5 +623,19 @@ namespace Ocelot.UnitTests.Configuration .Returns(requestId); } + private void GivenTheQosOptionsCreatorReturns(QoSOptions qosOptions) + { + _qosOptionsCreator + .Setup(x => x.Create(_fileConfiguration.ReRoutes[0])) + .Returns(qosOptions); + } + + private void ThenTheQosOptionsAre(QoSOptions qosOptions) + { + _config.Data.ReRoutes[0].QosOptions.DurationOfBreak.ShouldBe(qosOptions.DurationOfBreak); + + _config.Data.ReRoutes[0].QosOptions.ExceptionsAllowedBeforeBreaking.ShouldBe(qosOptions.ExceptionsAllowedBeforeBreaking); + _config.Data.ReRoutes[0].QosOptions.TimeoutValue.ShouldBe(qosOptions.TimeoutValue); + } } } diff --git a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs index 7f4d7b87..5e041e7e 100644 --- a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs @@ -1,5 +1,3 @@ -using System; -using System.Net; using Microsoft.AspNetCore.Mvc; using Moq; using Ocelot.Configuration.File; From b44c02510af9904a5253ab0a3e6f1f6be9cd8aeb Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 1 Mar 2017 23:34:56 +0000 Subject: [PATCH 07/19] unit test for qos --- .../Configuration/QoSOptionsCreatorTests.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 test/Ocelot.UnitTests/Configuration/QoSOptionsCreatorTests.cs diff --git a/test/Ocelot.UnitTests/Configuration/QoSOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/QoSOptionsCreatorTests.cs new file mode 100644 index 00000000..be3ac27a --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/QoSOptionsCreatorTests.cs @@ -0,0 +1,63 @@ +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class QoSOptionsCreatorTests + { + private QoSOptionsCreator _creator; + private FileReRoute _fileReRoute; + private QoSOptions _result; + + public QoSOptionsCreatorTests() + { + _creator = new QoSOptionsCreator(); + } + + [Fact] + public void should_create_qos_options() + { + var reRoute = new FileReRoute + { + QoSOptions = new FileQoSOptions + { + ExceptionsAllowedBeforeBreaking = 1, + DurationOfBreak = 1, + TimeoutValue = 1 + } + }; + var expected = new QoSOptionsBuilder() + .WithDurationOfBreak(1) + .WithExceptionsAllowedBeforeBreaking(1) + .WithTimeoutValue(1) + .Build(); + + this.Given(x => x.GivenTheFollowingReRoute(reRoute)) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheFollowingIsReturned(expected)) + .BDDfy(); + } + + private void GivenTheFollowingReRoute(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void WhenICreate() + { + _result = _creator.Create(_fileReRoute); + } + + private void ThenTheFollowingIsReturned(QoSOptions expected) + { + _result.DurationOfBreak.ShouldBe(expected.DurationOfBreak); + _result.ExceptionsAllowedBeforeBreaking.ShouldBe(expected.ExceptionsAllowedBeforeBreaking); + _result.TimeoutValue.ShouldBe(expected.TimeoutValue); + } + } +} \ No newline at end of file From 8bbd781820a5429ef36705e1c7742123ff46f552 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Thu, 2 Mar 2017 09:18:53 +0000 Subject: [PATCH 08/19] updated file options --- .../Builder/ReRouteOptionsBuilder.cs | 46 ++++++ .../Creator/FileOcelotConfigurationCreator.cs | 57 ++------ .../Creator/IReRouteOptionsCreator.cs | 9 ++ .../Creator/ReRouteOptionsCreator.cs | 52 +++++++ src/Ocelot/Configuration/ReRouteOptions.cs | 20 +++ .../ServiceCollectionExtensions.cs | 1 + .../FileConfigurationCreatorTests.cs | 131 +++++++++++------- .../ReRouteOptionsCreatorTests.cs | 84 +++++++++++ 8 files changed, 301 insertions(+), 99 deletions(-) create mode 100644 src/Ocelot/Configuration/Builder/ReRouteOptionsBuilder.cs create mode 100644 src/Ocelot/Configuration/Creator/IReRouteOptionsCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs create mode 100644 src/Ocelot/Configuration/ReRouteOptions.cs create mode 100644 test/Ocelot.UnitTests/Configuration/ReRouteOptionsCreatorTests.cs diff --git a/src/Ocelot/Configuration/Builder/ReRouteOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteOptionsBuilder.cs new file mode 100644 index 00000000..b8520a26 --- /dev/null +++ b/src/Ocelot/Configuration/Builder/ReRouteOptionsBuilder.cs @@ -0,0 +1,46 @@ +namespace Ocelot.Configuration.Builder +{ + public class ReRouteOptionsBuilder + { + private bool _isAuthenticated; + private bool _isAuthorised; + private bool _isCached; + private bool _isQoS; + private bool _enableRateLimiting; + + public ReRouteOptionsBuilder WithIsCached(bool isCached) + { + _isCached = isCached; + return this; + } + + public ReRouteOptionsBuilder WithIsAuthenticated(bool isAuthenticated) + { + _isAuthenticated = isAuthenticated; + return this; + } + + public ReRouteOptionsBuilder WithIsAuthorised(bool isAuthorised) + { + _isAuthorised = isAuthorised; + return this; + } + + public ReRouteOptionsBuilder WithIsQos(bool isQoS) + { + _isQoS = isQoS; + return this; + } + + public ReRouteOptionsBuilder WithRateLimiting(bool enableRateLimiting) + { + _enableRateLimiting = enableRateLimiting; + return this; + } + + public ReRouteOptions Build() + { + return new ReRouteOptions(_isAuthenticated, _isAuthorised, _isCached, _isQoS, _enableRateLimiting); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 4dd873cd..d6caf6a0 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -33,6 +33,7 @@ namespace Ocelot.Configuration.Creator private IRequestIdKeyCreator _requestIdKeyCreator; private IServiceProviderConfigurationCreator _serviceProviderConfigCreator; private IQoSOptionsCreator _qosOptionsCreator; + private IReRouteOptionsCreator _fileReRouteOptionsCreator; public FileOcelotConfigurationCreator( IOptions options, @@ -47,7 +48,8 @@ namespace Ocelot.Configuration.Creator IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator, IRequestIdKeyCreator requestIdKeyCreator, IServiceProviderConfigurationCreator serviceProviderConfigCreator, - IQoSOptionsCreator qosOptionsCreator + IQoSOptionsCreator qosOptionsCreator, + IReRouteOptionsCreator fileReRouteOptionsCreator ) { _requestIdKeyCreator = requestIdKeyCreator; @@ -63,6 +65,7 @@ namespace Ocelot.Configuration.Creator _claimsToThingCreator = claimsToThingCreator; _serviceProviderConfigCreator = serviceProviderConfigCreator; _qosOptionsCreator = qosOptionsCreator; + _fileReRouteOptionsCreator = fileReRouteOptionsCreator; } public async Task> Create() @@ -108,11 +111,7 @@ namespace Ocelot.Configuration.Creator private async Task SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) { - var isAuthenticated = IsAuthenticated(fileReRoute); - - var isAuthorised = IsAuthorised(fileReRoute); - - var isCached = IsCached(fileReRoute); + var fileReRouteOptions = _fileReRouteOptionsCreator.Create(fileReRoute); var requestIdKey = _requestIdKeyCreator.Create(fileReRoute, globalConfiguration); @@ -120,8 +119,6 @@ namespace Ocelot.Configuration.Creator var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); - var isQos = IsQoS(fileReRoute); - var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileReRoute, globalConfiguration); var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute); @@ -134,24 +131,22 @@ namespace Ocelot.Configuration.Creator var qosOptions = _qosOptionsCreator.Create(fileReRoute); - var enableRateLimiting = IsEnableRateLimiting(fileReRoute); - - var rateLimitOption = BuildRateLimitOptions(fileReRoute, globalConfiguration, enableRateLimiting); + var rateLimitOption = BuildRateLimitOptions(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting); var reRoute = new ReRouteBuilder() .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) .WithUpstreamTemplatePattern(upstreamTemplatePattern) - .WithIsAuthenticated(isAuthenticated) + .WithIsAuthenticated(fileReRouteOptions.IsAuthenticated) .WithAuthenticationOptions(authOptionsForRoute) .WithClaimsToHeaders(claimsToHeaders) .WithClaimsToClaims(claimsToClaims) .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) - .WithIsAuthorised(isAuthorised) + .WithIsAuthorised(fileReRouteOptions.IsAuthorised) .WithClaimsToQueries(claimsToQueries) .WithRequestIdKey(requestIdKey) - .WithIsCached(isCached) + .WithIsCached(fileReRouteOptions.IsCached) .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)) .WithDownstreamScheme(fileReRoute.DownstreamScheme) .WithLoadBalancer(fileReRoute.LoadBalancer) @@ -159,9 +154,9 @@ namespace Ocelot.Configuration.Creator .WithDownstreamPort(fileReRoute.DownstreamPort) .WithLoadBalancerKey(reRouteKey) .WithServiceProviderConfiguraion(serviceProviderConfiguration) - .WithIsQos(isQos) + .WithIsQos(fileReRouteOptions.IsQos) .WithQosOptions(qosOptions) - .WithEnableRateLimiting(enableRateLimiting) + .WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting) .WithRateLimitOptions(rateLimitOption) .Build(); @@ -185,31 +180,6 @@ namespace Ocelot.Configuration.Creator return rateLimitOption; } - private static bool IsEnableRateLimiting(FileReRoute fileReRoute) - { - return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false; - } - - private bool IsQoS(FileReRoute fileReRoute) - { - return fileReRoute.QoSOptions?.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions?.TimeoutValue > 0; - } - - private bool IsAuthenticated(FileReRoute fileReRoute) - { - return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); - } - - private bool IsAuthorised(FileReRoute fileReRoute) - { - return fileReRoute.RouteClaimsRequirement?.Count > 0; - } - - private bool IsCached(FileReRoute fileReRoute) - { - return fileReRoute.FileCacheOptions.TtlSeconds > 0; - } - private string CreateReRouteKey(FileReRoute fileReRoute) { //note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain @@ -228,10 +198,5 @@ namespace Ocelot.Configuration.Creator var loadBalancer = _qoSProviderFactory.Get(reRoute); _qosProviderHouse.Add(reRoute.ReRouteKey, loadBalancer); } - - private bool IsPlaceHolder(string upstreamTemplate, int i) - { - return upstreamTemplate[i] == '{'; - } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/IReRouteOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IReRouteOptionsCreator.cs new file mode 100644 index 00000000..9bc4a07a --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IReRouteOptionsCreator.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IReRouteOptionsCreator + { + ReRouteOptions Create(FileReRoute fileReRoute); + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs b/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs new file mode 100644 index 00000000..21e46932 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs @@ -0,0 +1,52 @@ +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class ReRouteOptionsCreator : IReRouteOptionsCreator + { + public ReRouteOptions Create(FileReRoute fileReRoute) + { + var isAuthenticated = IsAuthenticated(fileReRoute); + var isAuthorised = IsAuthorised(fileReRoute); + var isCached = IsCached(fileReRoute); + var isQos = IsQoS(fileReRoute); + var enableRateLimiting = IsEnableRateLimiting(fileReRoute); + + var options = new ReRouteOptionsBuilder() + .WithIsAuthenticated(isAuthenticated) + .WithIsAuthorised(isAuthorised) + .WithIsCached(isCached) + .WithIsQos(isQos) + .WithRateLimiting(enableRateLimiting) + .Build(); + + return options; + } + + private static bool IsEnableRateLimiting(FileReRoute fileReRoute) + { + return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false; + } + + private bool IsQoS(FileReRoute fileReRoute) + { + return fileReRoute.QoSOptions?.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions?.TimeoutValue > 0; + } + + private bool IsAuthenticated(FileReRoute fileReRoute) + { + return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); + } + + private bool IsAuthorised(FileReRoute fileReRoute) + { + return fileReRoute.RouteClaimsRequirement?.Count > 0; + } + + private bool IsCached(FileReRoute fileReRoute) + { + return fileReRoute.FileCacheOptions.TtlSeconds > 0; + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/ReRouteOptions.cs b/src/Ocelot/Configuration/ReRouteOptions.cs new file mode 100644 index 00000000..2e42f161 --- /dev/null +++ b/src/Ocelot/Configuration/ReRouteOptions.cs @@ -0,0 +1,20 @@ +namespace Ocelot.Configuration +{ + public class ReRouteOptions + { + public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isQos, bool isEnableRateLimiting) + { + IsAuthenticated = isAuthenticated; + IsAuthorised = isAuthorised; + IsCached = isCached; + IsQos = isQos; + EnableRateLimiting = isEnableRateLimiting; + + } + public bool IsAuthenticated { get; private set; } + public bool IsAuthorised { get; private set; } + public bool IsCached { get; private set; } + public bool IsQos { get; private set; } + public bool EnableRateLimiting { get; private set; } + } +} \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 1ebb6d42..d69e7741 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -66,6 +66,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(); diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index bb5866f9..8a64b654 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -36,6 +36,7 @@ namespace Ocelot.UnitTests.Configuration private Mock _requestIdKeyCreator; private Mock _serviceProviderConfigCreator; private Mock _qosOptionsCreator; + private Mock _fileReRouteOptionsCreator; public FileConfigurationCreatorTests() { @@ -54,13 +55,14 @@ namespace Ocelot.UnitTests.Configuration _requestIdKeyCreator = new Mock(); _serviceProviderConfigCreator = new Mock(); _qosOptionsCreator = new Mock(); + _fileReRouteOptionsCreator = new Mock(); _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _fileConfig.Object, _validator.Object, _logger.Object, _loadBalancerFactory.Object, _loadBalancerHouse.Object, _qosProviderFactory.Object, _qosProviderHouse.Object, _claimsToThingCreator.Object, _authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object, _requestIdKeyCreator.Object, - _serviceProviderConfigCreator.Object, _qosOptionsCreator.Object); + _serviceProviderConfigCreator.Object, _qosOptionsCreator.Object, _fileReRouteOptionsCreator.Object); } [Fact] @@ -72,6 +74,10 @@ namespace Ocelot.UnitTests.Configuration .WithTimeoutValue(1) .Build(); + var serviceOptions = new ReRouteOptionsBuilder() + .WithIsQos(true) + .Build(); + this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List @@ -92,16 +98,22 @@ namespace Ocelot.UnitTests.Configuration }, })) .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(serviceOptions)) .And(x => x.GivenTheQosProviderFactoryReturns()) .And(x => x.GivenTheQosOptionsCreatorReturns(expected)) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheQosOptionsAre(expected)) + .And(x => x.TheQosProviderFactoryIsCalledCorrectly()) + .And(x => x.ThenTheQosProviderHouseIsCalledCorrectly()) .BDDfy(); } [Fact] public void should_create_load_balancer() { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List @@ -116,6 +128,7 @@ namespace Ocelot.UnitTests.Configuration }, })) .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .And(x => x.GivenTheLoadBalancerFactoryReturns()) .When(x => x.WhenICreateTheConfig()) .Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly()) @@ -123,69 +136,46 @@ namespace Ocelot.UnitTests.Configuration .BDDfy(); } - [Fact] - public void should_create_qos_provider() - { - this.Given(x => x.GivenTheConfigIs(new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamHost = "127.0.0.1", - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = "Get", - QoSOptions = new FileQoSOptions - { - TimeoutValue = 1, - DurationOfBreak = 1, - ExceptionsAllowedBeforeBreaking = 1 - } - } - }, - })) - .And(x => x.GivenTheConfigIsValid()) - .And(x => x.GivenTheQosProviderFactoryReturns()) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.TheQosProviderFactoryIsCalledCorrectly()) - .And(x => x.ThenTheQosProviderHouseIsCalledCorrectly()) - .BDDfy(); - } - [Fact] public void should_use_downstream_host() { - this.Given(x => x.GivenTheConfigIs(new FileConfiguration + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List { - ReRoutes = new List + new FileReRoute { - new FileReRoute - { - DownstreamHost = "127.0.0.1", - UpstreamPathTemplate = "/api/products/{productId}", - DownstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = "Get", - } - }, + DownstreamHost = "127.0.0.1", + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = "Get", + } + }, + })) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheReRoutesAre(new List + { + new ReRouteBuilder() + .WithDownstreamHost("127.0.0.1") + .WithDownstreamPathTemplate("/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod("Get") + .Build() })) - .And(x => x.GivenTheConfigIsValid()) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - new ReRouteBuilder() - .WithDownstreamHost("127.0.0.1") - .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamPathTemplate("/api/products/{productId}") - .WithUpstreamHttpMethod("Get") - .Build() - })) - .BDDfy(); + .BDDfy(); } [Fact] public void should_use_downstream_scheme() { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List @@ -200,6 +190,7 @@ namespace Ocelot.UnitTests.Configuration }, })) .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(new List { @@ -216,6 +207,9 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void should_use_service_discovery_for_downstream_service_host() { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List @@ -239,6 +233,7 @@ namespace Ocelot.UnitTests.Configuration } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(new List { @@ -260,6 +255,9 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void should_not_use_service_discovery_for_downstream_host_url_when_no_service_name() { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List @@ -274,6 +272,7 @@ namespace Ocelot.UnitTests.Configuration } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(new List { @@ -292,6 +291,9 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void should_call_template_pattern_creator_correctly() { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List @@ -306,6 +308,7 @@ namespace Ocelot.UnitTests.Configuration } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .And(x => x.GivenTheUpstreamTemplatePatternCreatorReturns("(?i)/api/products/.*/$")) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(new List @@ -323,6 +326,9 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void should_call_request_id_creator() { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List @@ -341,6 +347,7 @@ namespace Ocelot.UnitTests.Configuration } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .And(x => x.GivenTheRequestIdCreatorReturns("blahhhh")) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(new List @@ -359,6 +366,10 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void should_create_with_headers_to_extract() { + var reRouteOptions = new ReRouteOptionsBuilder() + .WithIsAuthenticated(true) + .Build(); + var authenticationOptions = new AuthenticationOptionsBuilder() .WithProvider("IdentityServer") .WithProviderRootUrl("http://localhost:51888") @@ -410,6 +421,7 @@ namespace Ocelot.UnitTests.Configuration })) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions)) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .And(x => x.GivenTheClaimsToThingCreatorReturns(new List{new ClaimToThing("CustomerId", "CustomerId", "", 0)})) .And(x => x.GivenTheLoadBalancerFactoryReturns()) .When(x => x.WhenICreateTheConfig()) @@ -424,6 +436,10 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void should_create_with_authentication_properties() { + var reRouteOptions = new ReRouteOptionsBuilder() + .WithIsAuthenticated(true) + .Build(); + var authenticationOptions = new AuthenticationOptionsBuilder() .WithProvider("IdentityServer") .WithProviderRootUrl("http://localhost:51888") @@ -466,6 +482,7 @@ namespace Ocelot.UnitTests.Configuration } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions)) .And(x => x.GivenTheLoadBalancerFactoryReturns()) .When(x => x.WhenICreateTheConfig()) @@ -475,6 +492,14 @@ namespace Ocelot.UnitTests.Configuration .BDDfy(); } + private void GivenTheFollowingOptionsAreReturned(ReRouteOptions fileReRouteOptions) + { + _fileReRouteOptionsCreator + .Setup(x => x.Create(It.IsAny())) + .Returns(fileReRouteOptions); + } + + private void GivenTheConfigIsValid() { _validator diff --git a/test/Ocelot.UnitTests/Configuration/ReRouteOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ReRouteOptionsCreatorTests.cs new file mode 100644 index 00000000..79872ea1 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/ReRouteOptionsCreatorTests.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class ReRouteOptionsCreatorTests + { + private ReRouteOptionsCreator _creator; + private FileReRoute _reRoute; + private ReRouteOptions _result; + + public ReRouteOptionsCreatorTests() + { + _creator = new ReRouteOptionsCreator(); + } + + [Fact] + public void should_create_re_route_options() + { + var reRoute = new FileReRoute + { + RateLimitOptions = new FileRateLimitRule + { + EnableRateLimiting = true + }, + QoSOptions = new FileQoSOptions + { + ExceptionsAllowedBeforeBreaking = 1, + TimeoutValue = 1 + }, + AuthenticationOptions = new FileAuthenticationOptions + { + Provider = "IdentityServer" + }, + RouteClaimsRequirement = new Dictionary() + { + {"",""} + }, + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 1 + } + }; + + var expected = new ReRouteOptionsBuilder() + .WithIsAuthenticated(true) + .WithIsAuthorised(true) + .WithIsCached(true) + .WithIsQos(true) + .WithRateLimiting(true) + .Build(); + + this.Given(x => x.GivenTheFollowing(reRoute)) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheFollowingIsReturned(expected)) + .BDDfy(); + } + + private void GivenTheFollowing(FileReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenICreate() + { + _result = _creator.Create(_reRoute); + } + + private void ThenTheFollowingIsReturned(ReRouteOptions expected) + { + _result.IsAuthenticated.ShouldBe(expected.IsAuthenticated); + _result.IsAuthorised.ShouldBe(expected.IsAuthorised); + _result.IsQos.ShouldBe(expected.IsQos); + _result.IsCached.ShouldBe(expected.IsCached); + _result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); + } + } +} \ No newline at end of file From 558a0dfdab1027989889a5b36afcde1efafac7ca Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 5 Mar 2017 16:56:41 +0000 Subject: [PATCH 09/19] finished refactoring config cretor --- .../Builder/RateLimitOptionsBuilder.cs | 71 ++++++++++++ .../Creator/FileOcelotConfigurationCreator.cs | 22 +--- .../Creator/IRateLimitOptionsCreator.cs | 9 ++ .../Creator/RateLimitOptionsCreator.cs | 32 ++++++ .../ServiceCollectionExtensions.cs | 1 + .../FileConfigurationCreatorTests.cs | 38 +++++- .../RateLimitOptionsCreatorTests.cs | 108 ++++++++++++++++++ 7 files changed, 261 insertions(+), 20 deletions(-) create mode 100644 src/Ocelot/Configuration/Builder/RateLimitOptionsBuilder.cs create mode 100644 src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs create mode 100644 test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs diff --git a/src/Ocelot/Configuration/Builder/RateLimitOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/RateLimitOptionsBuilder.cs new file mode 100644 index 00000000..532d990b --- /dev/null +++ b/src/Ocelot/Configuration/Builder/RateLimitOptionsBuilder.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; + +namespace Ocelot.Configuration.Builder +{ + public class RateLimitOptionsBuilder + { + private bool _enableRateLimiting; + private string _clientIdHeader; + private List _clientWhitelist; + private bool _disableRateLimitHeaders; + private string _quotaExceededMessage; + private string _rateLimitCounterPrefix; + private RateLimitRule _rateLimitRule; + private int _httpStatusCode; + + public RateLimitOptionsBuilder WithEnableRateLimiting(bool enableRateLimiting) + { + _enableRateLimiting = enableRateLimiting; + return this; + } + + public RateLimitOptionsBuilder WithClientIdHeader(string clientIdheader) + { + _clientIdHeader = clientIdheader; + return this; + } + + public RateLimitOptionsBuilder WithClientWhiteList(List clientWhitelist) + { + _clientWhitelist = clientWhitelist; + return this; + } + + public RateLimitOptionsBuilder WithDisableRateLimitHeaders(bool disableRateLimitHeaders) + { + _disableRateLimitHeaders = disableRateLimitHeaders; + return this; + } + + public RateLimitOptionsBuilder WithQuotaExceededMessage(string quotaExceededMessage) + { + _quotaExceededMessage = quotaExceededMessage; + return this; + } + + public RateLimitOptionsBuilder WithRateLimitCounterPrefix(string rateLimitCounterPrefix) + { + _rateLimitCounterPrefix = rateLimitCounterPrefix; + return this; + } + + public RateLimitOptionsBuilder WithRateLimitRule(RateLimitRule rateLimitRule) + { + _rateLimitRule = rateLimitRule; + return this; + } + + public RateLimitOptionsBuilder WithHttpStatusCode(int httpStatusCode) + { + _httpStatusCode = httpStatusCode; + return this; + } + + public RateLimitOptions Build() + { + return new RateLimitOptions(_enableRateLimiting, _clientIdHeader, _clientWhitelist, + _disableRateLimitHeaders, _quotaExceededMessage, _rateLimitCounterPrefix, + _rateLimitRule, _httpStatusCode); + } + } +} diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index d6caf6a0..7147b40a 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -34,6 +34,7 @@ namespace Ocelot.Configuration.Creator private IServiceProviderConfigurationCreator _serviceProviderConfigCreator; private IQoSOptionsCreator _qosOptionsCreator; private IReRouteOptionsCreator _fileReRouteOptionsCreator; + private IRateLimitOptionsCreator _rateLimitOptionsCreator; public FileOcelotConfigurationCreator( IOptions options, @@ -49,9 +50,11 @@ namespace Ocelot.Configuration.Creator IRequestIdKeyCreator requestIdKeyCreator, IServiceProviderConfigurationCreator serviceProviderConfigCreator, IQoSOptionsCreator qosOptionsCreator, - IReRouteOptionsCreator fileReRouteOptionsCreator + IReRouteOptionsCreator fileReRouteOptionsCreator, + IRateLimitOptionsCreator rateLimitOptionsCreator ) { + _rateLimitOptionsCreator = rateLimitOptionsCreator; _requestIdKeyCreator = requestIdKeyCreator; _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; _authOptionsCreator = authOptionsCreator; @@ -131,7 +134,7 @@ namespace Ocelot.Configuration.Creator var qosOptions = _qosOptionsCreator.Create(fileReRoute); - var rateLimitOption = BuildRateLimitOptions(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting); + var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting); var reRoute = new ReRouteBuilder() .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) @@ -165,21 +168,6 @@ namespace Ocelot.Configuration.Creator return reRoute; } - private static RateLimitOptions BuildRateLimitOptions(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting) - { - RateLimitOptions rateLimitOption = null; - if (enableRateLimiting) - { - rateLimitOption = new RateLimitOptions(enableRateLimiting, globalConfiguration.RateLimitOptions.ClientIdHeader, - fileReRoute.RateLimitOptions.ClientWhitelist, globalConfiguration.RateLimitOptions.DisableRateLimitHeaders, - globalConfiguration.RateLimitOptions.QuotaExceededMessage, globalConfiguration.RateLimitOptions.RateLimitCounterPrefix, - new RateLimitRule(fileReRoute.RateLimitOptions.Period, TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan), fileReRoute.RateLimitOptions.Limit) - , globalConfiguration.RateLimitOptions.HttpStatusCode); - } - - return rateLimitOption; - } - private string CreateReRouteKey(FileReRoute fileReRoute) { //note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain diff --git a/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs new file mode 100644 index 00000000..42f03a96 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IRateLimitOptionsCreator + { + RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting); + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs b/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs new file mode 100644 index 00000000..68bf2a33 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs @@ -0,0 +1,32 @@ +using System; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class RateLimitOptionsCreator : IRateLimitOptionsCreator + { + public RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting) + { + RateLimitOptions rateLimitOption = null; + + if (enableRateLimiting) + { + rateLimitOption = new RateLimitOptionsBuilder() + .WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader) + .WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) + .WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders) + .WithEnableRateLimiting(fileReRoute.RateLimitOptions.EnableRateLimiting) + .WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode) + .WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage) + .WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix) + .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, + TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan), + fileReRoute.RateLimitOptions.Limit)) + .Build(); + } + + return rateLimitOption; + } + } +} diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index d69e7741..e99b2490 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -67,6 +67,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(); diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 8a64b654..e3293a90 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -37,6 +37,7 @@ namespace Ocelot.UnitTests.Configuration private Mock _serviceProviderConfigCreator; private Mock _qosOptionsCreator; private Mock _fileReRouteOptionsCreator; + private Mock _rateLimitOptions; public FileConfigurationCreatorTests() { @@ -56,13 +57,41 @@ namespace Ocelot.UnitTests.Configuration _serviceProviderConfigCreator = new Mock(); _qosOptionsCreator = new Mock(); _fileReRouteOptionsCreator = new Mock(); + _rateLimitOptions = new Mock(); _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _fileConfig.Object, _validator.Object, _logger.Object, _loadBalancerFactory.Object, _loadBalancerHouse.Object, _qosProviderFactory.Object, _qosProviderHouse.Object, _claimsToThingCreator.Object, _authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object, _requestIdKeyCreator.Object, - _serviceProviderConfigCreator.Object, _qosOptionsCreator.Object, _fileReRouteOptionsCreator.Object); + _serviceProviderConfigCreator.Object, _qosOptionsCreator.Object, _fileReRouteOptionsCreator.Object, + _rateLimitOptions.Object); + } + + [Fact] + public void should_call_rate_limit_options_creator() + { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHost = "127.0.0.1", + UpstreamPathTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = "Get", + } + }, + })) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheRateLimitOptionsCreatorIsCalledCorrectly()) + .BDDfy(); } [Fact] @@ -431,8 +460,6 @@ namespace Ocelot.UnitTests.Configuration .BDDfy(); } - - [Fact] public void should_create_with_authentication_properties() { @@ -499,6 +526,11 @@ namespace Ocelot.UnitTests.Configuration .Returns(fileReRouteOptions); } + private void ThenTheRateLimitOptionsCreatorIsCalledCorrectly() + { + _rateLimitOptions + .Verify(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } private void GivenTheConfigIsValid() { diff --git a/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs new file mode 100644 index 00000000..027de7d4 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class RateLimitOptionsCreatorTests + { + private FileReRoute _fileReRoute; + private FileGlobalConfiguration _fileGlobalConfig; + private bool _enabled; + private RateLimitOptionsCreator _creator; + private RateLimitOptions _result; + + public RateLimitOptionsCreatorTests() + { + _creator = new RateLimitOptionsCreator(); + } + + [Fact] + public void should_create_rate_limit_options() + { + var fileReRoute = new FileReRoute + { + RateLimitOptions = new FileRateLimitRule + { + ClientWhitelist = new List(), + Period = "Period", + Limit = 1, + PeriodTimespan = 1, + EnableRateLimiting = true + } + }; + var fileGlobalConfig = new FileGlobalConfiguration + { + RateLimitOptions = new FileRateLimitOptions + { + ClientIdHeader = "ClientIdHeader", + DisableRateLimitHeaders = true, + QuotaExceededMessage = "QuotaExceededMessage", + RateLimitCounterPrefix = "RateLimitCounterPrefix", + HttpStatusCode = 200 + } + }; + var expected = new RateLimitOptionsBuilder() + .WithClientIdHeader("ClientIdHeader") + .WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) + .WithDisableRateLimitHeaders(true) + .WithEnableRateLimiting(true) + .WithHttpStatusCode(200) + .WithQuotaExceededMessage("QuotaExceededMessage") + .WithRateLimitCounterPrefix("RateLimitCounterPrefix") + .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, + TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan), + fileReRoute.RateLimitOptions.Limit)) + .Build(); + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .And(x => x.GivenTheFollowingFileGlobalConfig(fileGlobalConfig)) + .And(x => x.GivenRateLimitingIsEnabled()) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheFollowingIsReturned(expected)) + .BDDfy(); + } + + private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void GivenTheFollowingFileGlobalConfig(FileGlobalConfiguration fileGlobalConfig) + { + _fileGlobalConfig = fileGlobalConfig; + } + + private void GivenRateLimitingIsEnabled() + { + _enabled = true; + } + + private void WhenICreate() + { + _result = _creator.Create(_fileReRoute, _fileGlobalConfig, _enabled); + } + + private void ThenTheFollowingIsReturned(RateLimitOptions expected) + { + _result.ClientIdHeader.ShouldBe(expected.ClientIdHeader); + _result.ClientWhitelist.ShouldBe(expected.ClientWhitelist); + _result.DisableRateLimitHeaders.ShouldBe(expected.DisableRateLimitHeaders); + _result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); + _result.HttpStatusCode.ShouldBe(expected.HttpStatusCode); + _result.QuotaExceededMessage.ShouldBe(expected.QuotaExceededMessage); + _result.RateLimitCounterPrefix.ShouldBe(expected.RateLimitCounterPrefix); + _result.RateLimitRule.Limit.ShouldBe(expected.RateLimitRule.Limit); + _result.RateLimitRule.Period.ShouldBe(expected.RateLimitRule.Period); + _result.RateLimitRule.PeriodTimespan.Ticks.ShouldBe(expected.RateLimitRule.PeriodTimespan.Ticks); + } + } +} From c3c37bc7853c037484e55d8d38c7a56ce0a6543e Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 5 Mar 2017 17:33:18 +0000 Subject: [PATCH 10/19] moved readme into docs --- README.md | 508 +----------------------------------------------------- 1 file changed, 4 insertions(+), 504 deletions(-) diff --git a/README.md b/README.md index 3cd339f8..c7bee6fe 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ it reaches a request builder middleware where it creates a HttpRequestMessage ob used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository -and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware +and retrieved as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features. @@ -35,34 +35,12 @@ That is basically it with a bunch of other features. Pull requests, issues and commentary welcome! No special process just create a request and get in touch either via gitter or create an issue. -## Building Ocelot - -You should be able to just build Ocelot using the ./build.ps1 or ./build.sh scripts if you are on -windows or mac. Alternatively you can build the project in VS2015 with the latest .NET Core SDK. - -The tests should all just run and work apart from the integration tests which need the following -environmental variables setting. This is a manual step at the moment. - - OCELOT_USERNAME=admin - OCELOT_HASH=kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4= - OCELOT_SALT=zzWITpnDximUNKYLiUam/w== - -On windows you can use.. - - SETX OCELOT_USERNAME admin - -On mac.. - - export OCELOT_USERNAME=admin - -I need to work out a nicer way of doing this in the future. - ## How to install Ocelot is designed to work with ASP.NET core only and is currently built to netcoreapp1.1 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you. -Install Ocelot and it's dependecies using nuget. At the moment +Install Ocelot and it's dependencies using NuGet. At the moment all we have is the pre version. Once we have something working in a half decent way we will drop a version. @@ -70,487 +48,9 @@ a half decent way we will drop a version. All versions can be found [here](https://www.nuget.org/packages/Ocelot/) -## Configuration +## Documentation -An example configuration can be found [here](https://github.com/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/configuration.json) -and an explained configuration can be found [here](https://github.com/TomPallister/Ocelot/blob/develop/configuration-explanation.txt). More detailed instructions to come on how to configure this. - -There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration. -The ReRoutes are the objects that tell Ocelot how to treat an upstream request. The Global -configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful -if you don't want to manage lots of ReRoute specific settings. - -```json -{ - "ReRoutes": [], - "GlobalConfiguration": {} -} -``` -More information on how to use these options is below.. - -## Startup - -An example startup using a json file for configuration can be seen below. -Currently this is the only way to get configuration into Ocelot. - -```csharp -public class Startup -{ - public Startup(IHostingEnvironment env) - { - var builder = new ConfigurationBuilder() - .SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddJsonFile("configuration.json") - .AddEnvironmentVariables(); - - Configuration = builder.Build(); - } - - public IConfigurationRoot Configuration { get; } - - public void ConfigureServices(IServiceCollection services) - { - Action settings = (x) => - { - x.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithDictionaryHandle(); - }; - - services.AddOcelotOutputCaching(settings); - services.AddOcelot(Configuration); - } - - public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) - { - loggerFactory.AddConsole(Configuration.GetSection("Logging")); - - await app.UseOcelot(); - } - } -} -``` - -Then in your Program.cs you will want to have the following. This can be changed if you -don't wan't to use the default url e.g. UseUrls(someUrls) and should work as long as you keep the WebHostBuilder registration. - -```csharp - public class Program - { - public static void Main(string[] args) - { - IWebHostBuilder builder = new WebHostBuilder(); - - builder.ConfigureServices(s => { - s.AddSingleton(builder); - }); - - builder.UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup(); - - var host = builder.Build(); - - host.Run(); - } - } -``` - -Sadly we need to inject the IWebHostBuilder interface to get the applications scheme, url and port later. I cannot -find a better way of doing this at the moment without setting this in a static or some kind of config. - -This is pretty much all you need to get going.......more to come! - -## 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 always adds a trailing slash to an UpstreamPathTemplate. - -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. - -```json -{ - "ReRoutes": [ - ] -} -``` - -In order to set up a ReRoute you need to add one to the json array called ReRoutes like -the following. - -```json -{ - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamPort": 80, - "DownstreamHost" "localhost" - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Put" -} -``` - -The DownstreamPathTemplate,Scheme, Port and Host make the URL that this request will be forwarded to. -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 :) -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. - -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. - -```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! - -## Administration - -Ocelot supports changing configuration during runtime via an authenticated HTTP API. The API is authenticated -using bearer tokens that you request from iteself. This is provided by the amazing [IdentityServer](https://github.com/IdentityServer/IdentityServer4) -project that I have been using for a few years now. Check them out. - -In order to enable the administration section you need to do a few things. First of all add this to your -initial configuration.json. The value can be anything you want and it is obviously reccomended don't use -a url you would like to route through with Ocelot as this will not work. The administration uses the -MapWhen functionality of asp.net core and all requests to root/administration will be sent there not -to the Ocelot middleware. - -```json -"GlobalConfiguration": { - "AdministrationPath": "/administration" -} -``` -This will get the admin area set up but not the authentication. Please note that this is a very basic approach to -this problem and if needed we can obviously improve on this! - -You need to set 3 environmental variables. - - OCELOT_USERNAME - OCELOT_HASH - OCELOT_SALT - -These need to be the admin username you want to use with Ocelot and the hash and salt of the password you want to -use given hashing algorythm. When requesting bearer tokens for use with the administration api you will need to -supply username and password. - -In order to create a hash and salt of your password please check out HashCreationTests.should_create_hash_and_salt() -this technique is based on [this](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/consumer-apis/password-hashing) -using SHA256 rather than SHA1. - -Now if you went with the configuration options above and want to access the API you can use the postman scripts -called ocelot.postman_collection.json in the solution to change the Ocelot configuration. Obviously these -will need to be changed if you are running Ocelot on a different url to http://localhost:5000. - -The scripts show you how to request a bearer token from ocelot and then use it to GET the existing configuration and POST -a configuration. - -## Service Discovery - -Ocelot allows you to specify a service discovery provider and will use this to find the host and port -for the downstream service Ocelot is forwarding a request to. At the moment this is only supported in the -GlobalConfiguration section which means the same service discovery provider will be used for all ReRoutes -you specify a ServiceName for at ReRoute level. - -In the future we can add a feature that allows ReRoute specfic configuration. - -At the moment the only supported service discovery provider is Consul. The following is required in the -GlobalConfiguration. The Provider is required and if you do not specify a host and port the Consul default -will be used. - -```json -"ServiceDiscoveryProvider": { - "Provider":"Consul", - "Host":"localhost", - "Port":8500 -} -``` - -In order to tell Ocelot a ReRoute is to use the service discovery provider for its host and port you must add the -ServiceName and load balancer you wish to use when making requests downstream. At the moment Ocelot has a RoundRobin -and LeastConnection algorithm you can use. If no load balancer is specified Ocelot will not load balance requests. - -```json -{ - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Put", - "ServiceName": "product", - "LoadBalancer": "LeastConnection" -} -``` - -When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balancer -requests across any available services. - - -## Authentication - -Ocelot currently supports the use of bearer tokens with Identity Server (more providers to -come if required). In order to identity a ReRoute as authenticated it needs the following -configuration added. - -```json -"AuthenticationOptions": { - "Provider": "IdentityServer", - "ProviderRootUrl": "http://localhost:52888", - "ScopeName": "api", - "AdditionalScopes": [ - "openid", - "offline_access" - ], - "ScopeSecret": "secret" -} -``` - -In this example the Provider is specified as IdentityServer. This string is important -because it is used to identity the authentication provider (as previously mentioned in -the future there might be more providers). Identity server requires that the client -talk to it so we need to provide the root url of the IdentityServer as ProviderRootUrl. -IdentityServer requires at least one scope and you can also provider additional scopes. -Finally if you are using IdentityServer reference tokens you need to provide the scope -secret. - -Ocelot will use this configuration to build an authentication handler and if -authentication is succefull the next middleware will be called else the response -is 401 unauthorised. - -## Authorisation - -Ocelot supports claims based authorisation which is run post authentication. This means if -you have a route you want to authorise you can add the following to you ReRoute configuration. - -```json -"RouteClaimsRequirement": { - "UserType": "registered" -}, -``` - -In this example when the authorisation middleware is called Ocelot will check to see -if the user has the claim type UserType and if the value of that claim is registered. -If it isn't then the user will not be authorised and the response will be 403 forbidden. - -## Claims Tranformation - -Ocelot allows the user to access claims and transform them into headers, query string -parameters and other claims. This is only available once a user has been authenticated. - -After the user is authenticated we run the claims to claims transformation middleware. -This allows the user to transform claims before the authorisation middleware is called. -After the user is authorised first we call the claims to headers middleware and Finally -the claims to query strig parameters middleware. - -The syntax for performing the transforms is the same for each proces. In the ReRoute -configuration a json dictionary is added with a specific name either AddClaimsToRequest, -AddHeadersToRequest, AddQueriesToRequest. - -Note I'm not a hotshot programmer so have no idea if this syntax is good.. - -Within this dictionary the entries specify how Ocelot should transform things! -The key to the dictionary is going to become the key of either a claim, header -or query parameter. - -The value of the entry is parsed to logic that will perform the transform. First of -all a dictionary accessor is specified e.g. Claims[CustomerId]. This means we want -to access the claims and get the CustomerId claim type. Next is a greater than (>) -symbol which is just used to split the string. The next entry is either value or value with -and indexer. If value is specifed Ocelot will just take the value and add it to the -transform. If the value has an indexer Ocelot will look for a delimiter which is provided -after another greater than symbol. Ocelot will then split the value on the delimiter -and add whatever was at the index requested to the transform. - -#### Claims to Claims Tranformation - -Below is an example configuration that will transforms claims to claims - -```json - "AddClaimsToRequest": { - "UserType": "Claims[sub] > value[0] > |", - "UserId": "Claims[sub] > value[1] > |" -}, -``` - -This shows a transforms where Ocelot looks at the users sub claim and transforms it into -UserType and UserId claims. Assuming the sub looks like this "usertypevalue|useridvalue". - -#### Claims to Headers Tranformation - -Below is an example configuration that will transforms claims to headers - -```json -"AddHeadersToRequest": { - "CustomerId": "Claims[sub] > value[1] > |" -}, -``` - -This shows a transform where Ocelot looks at the users sub claim and trasnforms it into a -CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue". - -#### Claims to Query String Parameters Tranformation - -Below is an example configuration that will transforms claims to query string parameters - -```json -"AddQueriesToRequest": { - "LocationId": "Claims[LocationId] > value", -}, -``` - -This shows a transform where Ocelot looks at the users LocationId claim and add its as -a query string parameter to be forwarded onto the downstream service. - -## Quality of Service - -Ocelot supports one QoS capability at the current time. You can set on a per ReRoute basis if you -want to use a circuit breaker when making requests to a downstream service. This uses the an awesome -.NET library called Polly check them out [here](https://github.com/App-vNext/Polly). - -Add the following section to a ReRoute configuration. - -```json -"QoSOptions": { - "ExceptionsAllowedBeforeBreaking":3, - "DurationOfBreak":5, - "TimeoutValue":5000 -} -``` - -You must set a number greater than 0 against ExceptionsAllowedBeforeBreaking for this rule to be -implemented. Duration of break is how long the circuit breaker will stay open for after it is tripped. -TimeoutValue means ff a request takes more than 5 seconds it will automatically be timed out. - -If you do not add a QoS section QoS will not be used. - -## RequestId / CorrelationId - -Ocelot supports a client sending a request id in the form of a header. If set Ocelot will -use the requestid for logging as soon as it becomes available in the middleware pipeline. -Ocelot will also forward the request id with the specified header to the downstream service. -I'm not sure if have this spot on yet in terms of the pipeline order becasue there are a few logs -that don't get the users request id at the moment and ocelot just logs not set for request id -which sucks. You can still get the framework request id in the logs if you set -IncludeScopes true in your logging config. This can then be used to match up later logs that do -have an OcelotRequestId. - -In order to use the requestid feature in your ReRoute configuration add this setting - -```json -"RequestIdKey": "OcRequestId" -``` - -In this example OcRequestId is the request header that contains the clients request id. - -There is also a setting in the GlobalConfiguration section which will override whatever has been -set at ReRoute level for the request id. The setting is as fllows. - -```json -"RequestIdKey": "OcRequestId", -``` - -It behaves in exactly the same way as the ReRoute level RequestIdKey settings. - -## Caching - -Ocelot supports some very rudimentary caching at the moment provider by -the [CacheManager](http://cachemanager.net/) project. This is an amazing project -that is solving a lot of caching problems. I would reccomend using this package to -cache with Ocelot. If you look at the example [here](https://github.com/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/Startup.cs) -you can see how the cache manager is setup and then passed into the Ocelot -AddOcelotOutputCaching configuration method. You can use any settings supported by -the CacheManager package and just pass them in. - -Anyway Ocelot currently supports caching on the URL of the downstream service -and setting a TTL in seconds to expire the cache. More to come! - -In orde to use caching on a route in your ReRoute configuration add this setting. - -```json -"FileCacheOptions": { "TtlSeconds": 15 } -``` - -In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds. - -## Ocelot Middleware injection and overrides - -Warning use with caution. If you are seeing any exceptions or strange behavior in your middleware -pipeline and you are using any of the following. Remove them and try again! - -When setting up Ocelot in your Startup.cs you can provide some additonal middleware -and override middleware. This is done as follos. - -```csharp -var configuration = new OcelotMiddlewareConfiguration -{ - PreErrorResponderMiddleware = async (ctx, next) => - { - await next.Invoke(); - } -}; - -app.UseOcelot(configuration); -``` - -In the example above the provided function will run before the first piece of Ocelot middleware. -This allows a user to supply any behaviours they want before and after the Ocelot pipeline has run. -This means you can break everything so use at your own pleasure! - -The user can set functions against the following. - -+ PreErrorResponderMiddleware - Already explained above. - -+ PreAuthenticationMiddleware - This allows the user to run pre authentication logic and then call -Ocelot's authentication middleware. - -+ AuthenticationMiddleware - This overrides Ocelots authentication middleware. - -+ PreAuthorisationMiddleware - This allows the user to run pre authorisation logic and then call -Ocelot's authorisation middleware. - -+ AuthorisationMiddleware - This overrides Ocelots authorisation middleware. - -+ PreQueryStringBuilderMiddleware - This alows the user to manipulate the query string on the -http request before it is passed to Ocelots request creator. - -Obviously you can just add middleware as normal before the call to app.UseOcelot() It cannot be added -after as Ocelot does not call the next middleware. - -## Logging - -Ocelot uses the standard logging interfaces ILoggerFactory / ILogger at the moment. -This is encapsulated in IOcelotLogger / IOcelotLoggerFactory with an implementation -for the standard asp.net core logging stuff at the moment. - -There are a bunch of debugging logs in the ocelot middlewares however I think the -system probably needs more logging in the code it calls into. Other than the debugging -there is a global error handler that should catch any errors thrown and log them as errors. - -The reason for not just using bog standard framework logging is that I could not -work out how to override the request id that get's logged when setting IncludeScopes -to true for logging settings. Nicely onto the next feature. - -## Not supported - -Ocelot does not support... - -+ Chunked Encoding - Ocelot will always get the body size and return Content-Length -header. Sorry if this doesn't work for your use case! - -+ Fowarding a host header - The host header that you send to Ocelot will not be -forwarded to the downstream service. Obviously this would break everything :( +Please click [here](https://github.com/TomPallister/Ocelot/wiki) for the Ocleot documentation. ## Things that are currently annoying me From 9dba870216203d6e412ad594351f42626714b3f5 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 5 Mar 2017 17:34:26 +0000 Subject: [PATCH 11/19] moved readme aroudn --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c7bee6fe..bdd44225 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,10 @@ All versions can be found [here](https://www.nuget.org/packages/Ocelot/) Please click [here](https://github.com/TomPallister/Ocelot/wiki) for the Ocleot documentation. +## Coming up + +You can see what we are working on [here](https://github.com/TomPallister/Ocelot/projects/1) + ## Things that are currently annoying me + The ReRoute configuration object is too large. @@ -62,9 +66,5 @@ that isnt available is annoying. Let alone it be null. [![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results) -## Coming up - -You can see what we are working on [here](https://github.com/TomPallister/Ocelot/projects/1) - From 01fb3c635527ab488791eea8de592eadd733ea10 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 5 Mar 2017 17:37:06 +0000 Subject: [PATCH 12/19] added version to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index bdd44225..361905c6 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ [![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb) +[![NuGet](https://img.shields.io/nuget/v/Nuget.Core.svg)](https://www.nuget.org/packages/Ocelot/) + [![Join the chat at https://gitter.im/Ocelotey/Lobby](https://badges.gitter.im/Ocelotey/Lobby.svg)](https://gitter.im/Ocelotey/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Attempt at a .NET Api Gateway From 74d23d059313c4c57bac779145841254b7be2a1a Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 5 Mar 2017 17:38:59 +0000 Subject: [PATCH 13/19] removed nuget thing --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 361905c6..bdd44225 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ [![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb) -[![NuGet](https://img.shields.io/nuget/v/Nuget.Core.svg)](https://www.nuget.org/packages/Ocelot/) - [![Join the chat at https://gitter.im/Ocelotey/Lobby](https://badges.gitter.im/Ocelotey/Lobby.svg)](https://gitter.im/Ocelotey/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Attempt at a .NET Api Gateway From 3d232efdc115ab3e516c0eb818097bab7b396b88 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 5 Mar 2017 17:41:30 +0000 Subject: [PATCH 14/19] updated readme --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index bdd44225..3eed3593 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,7 @@ touch either via gitter or create an issue. Ocelot is designed to work with ASP.NET core only and is currently built to netcoreapp1.1 [this](https://docs.microsoft.com/en-us/dotnet/articles/standard/library) documentation may prove helpful when working out if Ocelot would be suitable for you. -Install Ocelot and it's dependencies using NuGet. At the moment -all we have is the pre version. Once we have something working in -a half decent way we will drop a version. +Install Ocelot and it's dependencies using NuGet. `Install-Package Ocelot` From d0118ea4a351a6aedc940ed22da405da69511d39 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 5 Mar 2017 17:43:13 +0000 Subject: [PATCH 15/19] updated readme --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3eed3593..f037bf52 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,6 @@ and retrieved as the requests goes back up the Ocelot pipeline. There is a piece that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features. -## Contributing - -Pull requests, issues and commentary welcome! No special process just create a request and get in -touch either via gitter or create an issue. - ## How to install Ocelot is designed to work with ASP.NET core only and is currently @@ -48,12 +43,18 @@ All versions can be found [here](https://www.nuget.org/packages/Ocelot/) ## Documentation -Please click [here](https://github.com/TomPallister/Ocelot/wiki) for the Ocleot documentation. +Please click [here](https://github.com/TomPallister/Ocelot/wiki) for the Ocleot documentation. This includes lots of information and will be helpful if you want to understand the features Ocelot currently offers. ## Coming up You can see what we are working on [here](https://github.com/TomPallister/Ocelot/projects/1) +## Contributing + +Pull requests, issues and commentary welcome! No special process just create a request and get in +touch either via gitter or create an issue. + + ## Things that are currently annoying me + The ReRoute configuration object is too large. From c787202374322dcbd09473a4467664a3d7767def Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 5 Mar 2017 18:05:20 +0000 Subject: [PATCH 16/19] I realised we can get rid of the cookie container as cookies are just sent as a header called cookie... --- .../Request/Builder/HttpRequestCreator.cs | 2 - src/Ocelot/Request/Builder/IRequestCreator.cs | 1 - src/Ocelot/Request/Builder/RequestBuilder.cs | 26 +- .../HttpRequestBuilderMiddleware.cs | 1 - src/Ocelot/Request/Request.cs | 8 +- src/Ocelot/Requester/HttpClientBuilder.cs | 8 +- .../Requester/HttpClientHttpRequester.cs | 49 +- .../PollyCircuitBreakingDelegatingHandler.cs | 4 +- test/Ocelot.ManualTest/configuration.json | 606 +++++++++--------- .../HttpRequestBuilderMiddlewareTests.cs | 6 +- .../Request/RequestBuilderTests.cs | 23 +- .../Requester/HttpRequesterMiddlewareTests.cs | 2 +- 12 files changed, 344 insertions(+), 392 deletions(-) diff --git a/src/Ocelot/Request/Builder/HttpRequestCreator.cs b/src/Ocelot/Request/Builder/HttpRequestCreator.cs index 3264db59..de030f83 100644 --- a/src/Ocelot/Request/Builder/HttpRequestCreator.cs +++ b/src/Ocelot/Request/Builder/HttpRequestCreator.cs @@ -14,7 +14,6 @@ namespace Ocelot.Request.Builder string downstreamUrl, Stream content, IHeaderDictionary headers, - IRequestCookieCollection cookies, QueryString queryString, string contentType, RequestId.RequestId requestId, @@ -29,7 +28,6 @@ namespace Ocelot.Request.Builder .WithContentType(contentType) .WithHeaders(headers) .WithRequestId(requestId) - .WithCookies(cookies) .WithIsQos(isQos) .WithQos(qosProvider) .Build(); diff --git a/src/Ocelot/Request/Builder/IRequestCreator.cs b/src/Ocelot/Request/Builder/IRequestCreator.cs index 7db999b1..379f0aac 100644 --- a/src/Ocelot/Request/Builder/IRequestCreator.cs +++ b/src/Ocelot/Request/Builder/IRequestCreator.cs @@ -12,7 +12,6 @@ namespace Ocelot.Request.Builder string downstreamUrl, Stream content, IHeaderDictionary headers, - IRequestCookieCollection cookies, QueryString queryString, string contentType, RequestId.RequestId requestId, diff --git a/src/Ocelot/Request/Builder/RequestBuilder.cs b/src/Ocelot/Request/Builder/RequestBuilder.cs index ea6511ce..e47eea1f 100644 --- a/src/Ocelot/Request/Builder/RequestBuilder.cs +++ b/src/Ocelot/Request/Builder/RequestBuilder.cs @@ -21,7 +21,6 @@ namespace Ocelot.Request.Builder private string _contentType; private IHeaderDictionary _headers; private RequestId.RequestId _requestId; - private IRequestCookieCollection _cookies; private readonly string[] _unsupportedHeaders = {"host"}; private bool _isQos; private IQoSProvider _qoSProvider; @@ -68,12 +67,6 @@ namespace Ocelot.Request.Builder return this; } - public RequestBuilder WithCookies(IRequestCookieCollection cookies) - { - _cookies = cookies; - return this; - } - public RequestBuilder WithIsQos(bool isqos) { _isQos = isqos; @@ -103,9 +96,7 @@ namespace Ocelot.Request.Builder AddRequestIdHeader(_requestId, httpRequestMessage); } - var cookieContainer = CreateCookieContainer(uri); - - return new Request(httpRequestMessage, cookieContainer,_isQos, _qoSProvider); + return new Request(httpRequestMessage,_isQos, _qoSProvider); } private Uri CreateUri() @@ -153,21 +144,6 @@ namespace Ocelot.Request.Builder return !_unsupportedHeaders.Contains(header.Key.ToLower()); } - private CookieContainer CreateCookieContainer(Uri uri) - { - var cookieContainer = new CookieContainer(); - - if (_cookies != null) - { - foreach (var cookie in _cookies) - { - cookieContainer.Add(uri, new Cookie(cookie.Key, cookie.Value)); - } - } - - return cookieContainer; - } - private void AddRequestIdHeader(RequestId.RequestId requestId, HttpRequestMessage httpRequestMessage) { httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue); diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs index 991e84da..0701e089 100644 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs +++ b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs @@ -48,7 +48,6 @@ namespace Ocelot.Request.Middleware DownstreamUrl, context.Request.Body, context.Request.Headers, - context.Request.Cookies, context.Request.QueryString, context.Request.ContentType, new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier), diff --git a/src/Ocelot/Request/Request.cs b/src/Ocelot/Request/Request.cs index 680ab757..3f1c654c 100644 --- a/src/Ocelot/Request/Request.cs +++ b/src/Ocelot/Request/Request.cs @@ -1,7 +1,4 @@ -using Ocelot.Configuration; -using Ocelot.Values; -using System.Net; -using System.Net.Http; +using System.Net.Http; using Ocelot.Requester.QoS; namespace Ocelot.Request @@ -10,18 +7,15 @@ namespace Ocelot.Request { public Request( HttpRequestMessage httpRequestMessage, - CookieContainer cookieContainer, bool isQos, IQoSProvider qosProvider) { HttpRequestMessage = httpRequestMessage; - CookieContainer = cookieContainer; IsQos = isQos; QosProvider = qosProvider; } public HttpRequestMessage HttpRequestMessage { get; private set; } - public CookieContainer CookieContainer { get; private set; } public bool IsQos { get; private set; } public IQoSProvider QosProvider { get; private set; } } diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index da37ee02..71462644 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -11,17 +11,17 @@ namespace Ocelot.Requester { private readonly Dictionary> _handlers = new Dictionary>(); - public HttpClientBuilder WithQoS(IQoSProvider qoSProvider, IOcelotLogger logger, HttpMessageHandler innerHandler) + public HttpClientBuilder WithQoS(IQoSProvider qoSProvider, IOcelotLogger logger) { - _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qoSProvider, logger, innerHandler)); + _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qoSProvider, logger)); return this; } - internal HttpClient Build(HttpMessageHandler innerHandler) + internal HttpClient Build() { return _handlers.Any() ? new HttpClient(CreateHttpMessageHandler()) : - new HttpClient(innerHandler); + new HttpClient(); } private HttpMessageHandler CreateHttpMessageHandler() diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index 5a2c86c7..08839ccc 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -19,36 +19,33 @@ namespace Ocelot.Requester public async Task> GetResponse(Request.Request request) { - var builder = new HttpClientBuilder(); + var builder = new HttpClientBuilder(); - using (var handler = new HttpClientHandler { CookieContainer = request.CookieContainer }) + if (request.IsQos) { - if (request.IsQos) - { - builder.WithQoS(request.QosProvider, _logger, handler); - } + builder.WithQoS(request.QosProvider, _logger); + } - using (var httpClient = builder.Build(handler)) + using (var httpClient = builder.Build()) + { + try { - try - { - var response = await httpClient.SendAsync(request.HttpRequestMessage); - return new OkResponse(response); - } - catch (TimeoutRejectedException exception) - { - return - new ErrorResponse(new RequestTimedOutError(exception)); - } - catch (BrokenCircuitException exception) - { - return - new ErrorResponse(new RequestTimedOutError(exception)); - } - catch (Exception exception) - { - return new ErrorResponse(new UnableToCompleteRequestError(exception)); - } + var response = await httpClient.SendAsync(request.HttpRequestMessage); + return new OkResponse(response); + } + catch (TimeoutRejectedException exception) + { + return + new ErrorResponse(new RequestTimedOutError(exception)); + } + catch (BrokenCircuitException exception) + { + return + new ErrorResponse(new RequestTimedOutError(exception)); + } + catch (Exception exception) + { + return new ErrorResponse(new UnableToCompleteRequestError(exception)); } } } diff --git a/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs b/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs index a50ec91b..a09ed233 100644 --- a/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs +++ b/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs @@ -16,9 +16,7 @@ namespace Ocelot.Requester public PollyCircuitBreakingDelegatingHandler( IQoSProvider qoSProvider, - IOcelotLogger logger, - HttpMessageHandler innerHandler) - : base(innerHandler) + IOcelotLogger logger) { _qoSProvider = qoSProvider; _logger = logger; diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index 980614bd..b28080d2 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -1,303 +1,311 @@ { - "ReRoutes": [ - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "http", - "DownstreamHost": "localhost", - "DownstreamPort": 52876, - "UpstreamPathTemplate": "/identityserverexample", - "UpstreamHttpMethod": "Get", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "AuthenticationOptions": { - "Provider": "IdentityServer", - "ProviderRootUrl": "http://localhost:52888", - "ScopeName": "api", - "AdditionalScopes": [ - "openid", - "offline_access" - ], - "ScopeSecret": "secret" - }, - "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" - }, - { - "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "https", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 443, - "UpstreamPathTemplate": "/posts", - "UpstreamHttpMethod": "Get", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Get", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}/comments", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/posts/{postId}/comments", - "UpstreamHttpMethod": "Get", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/comments", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/comments", - "UpstreamHttpMethod": "Get", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/posts", - "UpstreamHttpMethod": "Post", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Put", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Patch", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Delete", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/api/products", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/products", - "UpstreamHttpMethod": "Get", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/products/{productId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/products/{productId}", - "UpstreamHttpMethod": "Get", - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/products", - "DownstreamScheme": "http", - "DownstreamHost": "products20161126090340.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/products", - "UpstreamHttpMethod": "Post", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - } - }, - { - "DownstreamPathTemplate": "/api/products/{productId}", - "DownstreamScheme": "http", - "DownstreamHost": "products20161126090340.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/products/{productId}", - "UpstreamHttpMethod": "Put", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/products/{productId}", - "DownstreamScheme": "http", - "DownstreamHost": "products20161126090340.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/products/{productId}", - "UpstreamHttpMethod": "Delete", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/customers", - "UpstreamHttpMethod": "Get", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers/{customerId}", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/customers/{customerId}", - "UpstreamHttpMethod": "Get", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/customers", - "UpstreamHttpMethod": "Post", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers/{customerId}", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/customers/{customerId}", - "UpstreamHttpMethod": "Put", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers/{customerId}", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/customers/{customerId}", - "UpstreamHttpMethod": "Delete", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamPathTemplate": "/posts/", - "UpstreamHttpMethod": "Get", - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - "FileCacheOptions": { "TtlSeconds": 15 } - } - ], + "ReRoutes": [ + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHost": "localhost", + "DownstreamPort": 52876, + "UpstreamPathTemplate": "/identityserverexample", + "UpstreamHttpMethod": "Get", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "AuthenticationOptions": { + "Provider": "IdentityServer", + "ProviderRootUrl": "http://localhost:52888", + "ScopeName": "api", + "AdditionalScopes": [ + "openid", + "offline_access" + ], + "ScopeSecret": "secret" + }, + "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" + }, + { + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "https", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 443, + "UpstreamPathTemplate": "/posts", + "UpstreamHttpMethod": "Get", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Get", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}/comments", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/posts/{postId}/comments", + "UpstreamHttpMethod": "Get", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/comments", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/comments", + "UpstreamHttpMethod": "Get", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/posts", + "UpstreamHttpMethod": "Post", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Put", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Patch", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Delete", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/products", + "UpstreamHttpMethod": "Get", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/products/{productId}", + "UpstreamHttpMethod": "Get", + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/products", + "UpstreamHttpMethod": "Post", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/products/{productId}", + "UpstreamHttpMethod": "Put", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/products/{productId}", + "UpstreamHttpMethod": "Delete", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/customers", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/customers", + "UpstreamHttpMethod": "Get", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/customers/{customerId}", + "UpstreamHttpMethod": "Get", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/customers", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/customers", + "UpstreamHttpMethod": "Post", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/customers/{customerId}", + "UpstreamHttpMethod": "Put", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/customers/{customerId}", + "UpstreamHttpMethod": "Delete", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/posts/", + "UpstreamHttpMethod": "Get", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHost": "www.bbc.co.uk", + "DownstreamPort": 80, + "UpstreamPathTemplate": "/bbc/", + "UpstreamHttpMethod": "Get" + } + ], "GlobalConfiguration": { "RequestIdKey": "OcRequestId", diff --git a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs index b674b015..002e4e02 100644 --- a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs @@ -79,7 +79,7 @@ namespace Ocelot.UnitTests.Request this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) .And(x => x.GivenTheQosProviderHouseReturns(new OkResponse(new NoQoSProvider()))) .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), new CookieContainer(), true, new NoQoSProvider()))) + .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), true, new NoQoSProvider()))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); @@ -104,8 +104,8 @@ namespace Ocelot.UnitTests.Request { _request = new OkResponse(request); _requestBuilder - .Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(),It.IsAny(), It.IsAny())) + .Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(),It.IsAny(), It.IsAny())) .ReturnsAsync(_request); } diff --git a/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs b/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs index 9d1c2ed5..667c80b0 100644 --- a/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs +++ b/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs @@ -195,23 +195,6 @@ namespace Ocelot.UnitTests.Request _qoSProvider = qoSProvider; } - [Fact] - public void should_use_cookies() - { - this.Given(x => x.GivenIHaveHttpMethod("GET")) - .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) - .And(x => x.GivenTheCookiesAre(new RequestCookieCollection(new Dictionary - { - { "TheCookie","Monster" } - }))) - .When(x => x.WhenICreateARequest()) - .And(x => x.ThenTheCorrectCookiesAreUsed(new RequestCookieCollection(new Dictionary - { - { "TheCookie","Monster" } - }))) - .BDDfy(); - } - [Fact] public void should_user_query_string() { @@ -240,14 +223,14 @@ namespace Ocelot.UnitTests.Request private void ThenTheCorrectCookiesAreUsed(IRequestCookieCollection expected) { - var resultCookies = _result.Data.CookieContainer.GetCookies(new Uri(_downstreamUrl + _query)); + /* var resultCookies = _result.Data.CookieContainer.GetCookies(new Uri(_downstreamUrl + _query)); var resultDictionary = resultCookies.Cast().ToDictionary(cook => cook.Name, cook => cook.Value); foreach (var expectedCookie in expected) { var resultCookie = resultDictionary[expectedCookie.Key]; resultCookie.ShouldBe(expectedCookie.Value); - } + }*/ } private void GivenTheCookiesAre(IRequestCookieCollection cookies) @@ -305,7 +288,7 @@ namespace Ocelot.UnitTests.Request private void WhenICreateARequest() { _result = _requestCreator.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers, - _cookies, _query, _contentType, _requestId,_isQos,_qoSProvider).Result; + _query, _contentType, _requestId,_isQos,_qoSProvider).Result; } diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index 37815a64..b95367be 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -62,7 +62,7 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_call_scoped_data_repository_correctly() { - this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),new CookieContainer(),true, new NoQoSProvider()))) + this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),true, new NoQoSProvider()))) .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) .And(x => x.GivenTheScopedRepoReturns()) .When(x => x.WhenICallTheMiddleware()) From fac2346161f76512883e1fbfb35e4525ea65d82c Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 6 Mar 2017 07:33:55 +0000 Subject: [PATCH 17/19] added thread safe test --- configuration.json | 2 +- .../ThreadSafeHeadersTests.cs | 242 ++++++++++++++++++ .../configuration.json | 2 +- 3 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs diff --git a/configuration.json b/configuration.json index 3f39532c..6c4e9ae7 100755 --- a/configuration.json +++ b/configuration.json @@ -1 +1 @@ -{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration"}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":51879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0.0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":null,"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs new file mode 100644 index 00000000..c8792f5d --- /dev/null +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.ManualTest; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using Microsoft.AspNetCore.Http; +using System.Threading.Tasks; +using System.Threading; +using System.Collections.Concurrent; + +namespace Ocelot.IntegrationTests +{ + public class ThreadSafeHeadersTests : IDisposable + { + private readonly HttpClient _httpClient; + private HttpResponseMessage _response; + private IWebHost _builder; + private IWebHostBuilder _webHostBuilder; + private readonly string _ocelotBaseUrl; + private BearerToken _token; + private IWebHost _downstreamBuilder; + private readonly Random _random; + private ConcurrentBag _results; + + public ThreadSafeHeadersTests() + { + _results = new ConcurrentBag(); + _random = new Random(); + _httpClient = new HttpClient(); + _ocelotBaseUrl = "http://localhost:5000"; + _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); + } + + [Fact] + public void should_return_same_response_for_each_different_header_under_load_to_downsteam_service() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = "Get", + } + } + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879")) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 50)) + .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()) + .BDDfy(); + } + private void GivenThereIsAServiceRunningOn(string url) + { + _downstreamBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + var header = context.Request.Headers["ThreadSafeHeadersTest"]; + + context.Response.StatusCode = 200; + await context.Response.WriteAsync(header[0]); + }); + }) + .Build(); + + _downstreamBuilder.Start(); + } + private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) + { + var json = JsonConvert.SerializeObject(updatedConfiguration); + var content = new StringContent(json); + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + _response = _httpClient.PostAsync(url, content).Result; + } + + private void ThenTheResponseShouldBe(FileConfiguration expected) + { + var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); + + response.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath); + response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); + response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); + response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); + response.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider); + + for (var i = 0; i < response.ReRoutes.Count; i++) + { + response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); + response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); + response.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); + response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); + response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.ReRoutes[i].UpstreamPathTemplate); + response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.ReRoutes[i].UpstreamHttpMethod); + } + } + + private void GivenIHaveAddedATokenToMyRequest() + { + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); + } + + private void GivenIHaveAnOcelotToken(string adminPath) + { + var tokenUrl = $"{adminPath}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "admin"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "admin"), + new KeyValuePair("username", "admin"), + new KeyValuePair("password", "secret"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + var response = _httpClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + + private void GivenOcelotIsRunning() + { + _webHostBuilder = new WebHostBuilder() + .UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureServices(x => + { + x.AddSingleton(_webHostBuilder); + }) + .UseStartup(); + + _builder = _webHostBuilder.Build(); + + _builder.Start(); + } + + private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json"; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + var text = File.ReadAllText(configurationPath); + + configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + text = File.ReadAllText(configurationPath); + } + + public void WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues(string url, int times) + { + var tasks = new Task[times]; + + for (int i = 0; i < times; i++) + { + var urlCopy = url; + var random = _random.Next(0, 50); + tasks[i] = GetForThreadSafeHeadersTest(urlCopy, random); + } + + Task.WaitAll(tasks); + } + + private async Task GetForThreadSafeHeadersTest(string url, int random) + { + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Add("ThreadSafeHeadersTest", new List { random.ToString() }); + var response = await _httpClient.SendAsync(request); + var content = await response.Content.ReadAsStringAsync(); + int result = int.Parse(content); + var tshtr = new ThreadSafeHeadersTestResult(result, random); + _results.Add(tshtr); + } + + private void ThenTheSameHeaderValuesAreReturnedByTheDownstreamService() + { + foreach(var result in _results) + { + result.Result.ShouldBe(result.Random); + } + } + public void Dispose() + { + _builder?.Dispose(); + _httpClient?.Dispose(); + _downstreamBuilder?.Dispose(); + } + + class ThreadSafeHeadersTestResult + { + public ThreadSafeHeadersTestResult(int result, int random) + { + Result = result; + Random = random; + + } + + public int Result { get; private set; } + public int Random { get; private set; } + } + } +} diff --git a/test/Ocelot.IntegrationTests/configuration.json b/test/Ocelot.IntegrationTests/configuration.json index 2ce0beff..6c4e9ae7 100755 --- a/test/Ocelot.IntegrationTests/configuration.json +++ b/test/Ocelot.IntegrationTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration","RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":51879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0.0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":null,"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file From f0dcefff38ea346234669252ed075cbc0a64b341 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 6 Mar 2017 07:34:07 +0000 Subject: [PATCH 18/19] test fails on my mac.. --- test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index c8792f5d..25b7acb0 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -63,7 +63,7 @@ namespace Ocelot.IntegrationTests this.Given(x => GivenThereIsAConfiguration(configuration)) .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879")) .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 50)) + .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 100)) .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()) .BDDfy(); } From f44357518517878a02e01ff4584906ef17ba978d Mon Sep 17 00:00:00 2001 From: "tom.pallister" Date: Mon, 6 Mar 2017 13:24:36 +0000 Subject: [PATCH 19/19] thread safe test passing on windows --- .../AdministrationTests.cs | 1 + .../ThreadSafeHeadersTests.cs | 60 ++----------------- 2 files changed, 5 insertions(+), 56 deletions(-) diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 210c7ac5..2f78e795 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -13,6 +13,7 @@ using Shouldly; using TestStack.BDDfy; using Xunit; +[assembly: CollectionBehavior(DisableTestParallelization = true)] namespace Ocelot.IntegrationTests { public class AdministrationTests : IDisposable diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index 25b7acb0..191dea47 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -30,14 +30,14 @@ namespace Ocelot.IntegrationTests private BearerToken _token; private IWebHost _downstreamBuilder; private readonly Random _random; - private ConcurrentBag _results; + private readonly ConcurrentBag _results; public ThreadSafeHeadersTests() { _results = new ConcurrentBag(); _random = new Random(); _httpClient = new HttpClient(); - _ocelotBaseUrl = "http://localhost:5000"; + _ocelotBaseUrl = "http://localhost:5001"; _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); } @@ -63,10 +63,11 @@ namespace Ocelot.IntegrationTests this.Given(x => GivenThereIsAConfiguration(configuration)) .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879")) .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 100)) + .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 300)) .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()) .BDDfy(); } + private void GivenThereIsAServiceRunningOn(string url) { _downstreamBuilder = new WebHostBuilder() @@ -89,59 +90,6 @@ namespace Ocelot.IntegrationTests _downstreamBuilder.Start(); } - private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) - { - var json = JsonConvert.SerializeObject(updatedConfiguration); - var content = new StringContent(json); - content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - _response = _httpClient.PostAsync(url, content).Result; - } - - private void ThenTheResponseShouldBe(FileConfiguration expected) - { - var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); - - response.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath); - response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); - response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); - response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); - response.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider); - - for (var i = 0; i < response.ReRoutes.Count; i++) - { - response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); - response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); - response.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); - response.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); - response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expected.ReRoutes[i].UpstreamPathTemplate); - response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expected.ReRoutes[i].UpstreamHttpMethod); - } - } - - private void GivenIHaveAddedATokenToMyRequest() - { - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - } - - private void GivenIHaveAnOcelotToken(string adminPath) - { - var tokenUrl = $"{adminPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "admin"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "admin"), - new KeyValuePair("username", "admin"), - new KeyValuePair("password", "secret"), - new KeyValuePair("grant_type", "password") - }; - var content = new FormUrlEncodedContent(formData); - - var response = _httpClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - } private void GivenOcelotIsRunning() {