From b2d33f0ec5d0af9874d7415411ae4074bb62e6ae Mon Sep 17 00:00:00 2001 From: "tom.pallister" Date: Wed, 11 Jan 2017 17:23:34 +0000 Subject: [PATCH 001/113] Updated packages and fixed build errors and tests..now need to update nuspec --- .../Creator/AuthenticationHandlerCreator.cs | 6 +- src/Ocelot/project.json | 75 ++++++----- .../AuthenticationTests.cs | 35 ++++-- .../AuthorisationTests.cs | 35 ++++-- .../ClaimsToHeadersForwardingTests.cs | 40 +++--- .../ClaimsToQueryStringForwardingTests.cs | 42 ++++--- test/Ocelot.AcceptanceTests/Steps.cs | 2 +- .../TestConfiguration.cs | 2 +- test/Ocelot.AcceptanceTests/project.json | 58 ++++----- test/Ocelot.Benchmarks/project.json | 36 +++--- test/Ocelot.ManualTest/project.json | 117 +++++++++--------- test/Ocelot.UnitTests/project.json | 65 +++++----- 12 files changed, 271 insertions(+), 242 deletions(-) diff --git a/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs b/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs index 82765d21..65260d64 100644 --- a/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs +++ b/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs @@ -19,11 +19,11 @@ namespace Ocelot.Authentication.Handler.Creator builder.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions { Authority = authOptions.ProviderRootUrl, - ScopeName = authOptions.ScopeName, + ApiName = authOptions.ScopeName, RequireHttpsMetadata = authOptions.RequireHttps, - AdditionalScopes = authOptions.AdditionalScopes, + AllowedScopes = authOptions.AdditionalScopes, SupportedTokens = SupportedTokens.Both, - ScopeSecret = authOptions.ScopeSecret + ApiSecret = authOptions.ScopeSecret }); var authenticationNext = builder.Build(); diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index e5f5389c..1cecd70c 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -1,42 +1,41 @@ { - "version": "1.0.0-*", + "version": "1.0.0-*", - "dependencies": { - "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", - "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0", - "Microsoft.Extensions.Configuration.Json": "1.0.0", - "Microsoft.Extensions.Logging": "1.0.0", - "Microsoft.Extensions.Logging.Console": "1.0.0", - "Microsoft.Extensions.Logging.Debug": "1.0.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", - "Microsoft.AspNetCore.Http": "1.0.0", - "System.Text.RegularExpressions": "4.1.0", - "Microsoft.AspNetCore.Authentication.OAuth": "1.0.0", - "Microsoft.AspNetCore.Authentication.JwtBearer": "1.0.0", - "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.0", - "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0", - "Microsoft.AspNetCore.Authentication.Google": "1.0.0", - "Microsoft.AspNetCore.Authentication.Facebook": "1.0.0", - "Microsoft.AspNetCore.Authentication.Twitter": "1.0.0", - "Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.0.0", - "Microsoft.AspNetCore.Authentication": "1.0.0", - "IdentityServer4.AccessTokenValidation": "1.0.1-rc2", - "Microsoft.AspNetCore.Mvc": "1.0.1", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.1", - "Microsoft.NETCore.App": { - "version": "1.0.1", - "type": "platform" - }, - "CacheManager.Core": "0.9.1", - "CacheManager.Microsoft.Extensions.Configuration": "0.9.1", - "CacheManager.Microsoft.Extensions.Logging": "0.9.1" - }, - - "frameworks": { - "netcoreapp1.4": { - "imports": [ - ] - } + "dependencies": { + "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", + "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", + "Microsoft.Extensions.Configuration.Json": "1.1.0", + "Microsoft.Extensions.Logging": "1.1.0", + "Microsoft.Extensions.Logging.Console": "1.1.0", + "Microsoft.Extensions.Logging.Debug": "1.1.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", + "Microsoft.AspNetCore.Http": "1.1.0", + "System.Text.RegularExpressions": "4.3.0", + "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", + "Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0", + "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0", + "Microsoft.AspNetCore.Authentication.Cookies": "1.1.0", + "Microsoft.AspNetCore.Authentication.Google": "1.1.0", + "Microsoft.AspNetCore.Authentication.Facebook": "1.1.0", + "Microsoft.AspNetCore.Authentication.Twitter": "1.1.0", + "Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.1.0", + "Microsoft.AspNetCore.Authentication": "1.1.0", + "IdentityServer4.AccessTokenValidation": "1.0.2", + "Microsoft.AspNetCore.Mvc": "1.1.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", + "Microsoft.NETCore.App": "1.1.0", + "CacheManager.Core": "0.9.2", + "CacheManager.Microsoft.Extensions.Configuration": "0.9.2", + "CacheManager.Microsoft.Extensions.Logging": "0.9.2" + }, + "runtimes": { + "win10-x64": {} + }, + "frameworks": { + "netcoreapp1.4": { + "imports": [ + ] } + } } diff --git a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs index da63d134..874afe59 100644 --- a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs @@ -4,7 +4,6 @@ using System.IO; using System.Net; using System.Security.Claims; using IdentityServer4.Models; -using IdentityServer4.Services.InMemory; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -15,6 +14,9 @@ using Xunit; namespace Ocelot.AcceptanceTests { + using IdentityServer4; + using IdentityServer4.Test; + public class AuthenticationTests : IDisposable { private IWebHost _servicebuilder; @@ -241,26 +243,34 @@ namespace Ocelot.AcceptanceTests .ConfigureServices(services => { services.AddLogging(); - services.AddDeveloperIdentityServer() - .AddInMemoryScopes(new List + services.AddIdentityServer() + .AddTemporarySigningCredential() + .AddInMemoryApiResources(new List { - new Scope + new ApiResource { Name = scopeName, Description = "My API", Enabled = true, - AllowUnrestrictedIntrospection = true, - ScopeSecrets = new List() + DisplayName = "test", + Scopes = new List() + { + new Scope("api"), + new Scope("openid"), + new Scope("offline_access") + }, + ApiSecrets = new List() { new Secret { Value = "secret".Sha256() } + }, + UserClaims = new List() + { + "CustomerId", "LocationId" } }, - - StandardScopes.OpenId, - StandardScopes.OfflineAccess }) .AddInMemoryClients(new List { @@ -275,14 +285,13 @@ namespace Ocelot.AcceptanceTests RequireClientSecret = false } }) - .AddInMemoryUsers(new List + .AddTestUsers(new List { - new InMemoryUser + new TestUser { Username = "test", Password = "test", - Enabled = true, - Subject = "registered|1231231", + SubjectId = "registered|1231231", Claims = new List { new Claim("CustomerId", "123"), diff --git a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs index 8dfb1314..4ceb03f6 100644 --- a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs @@ -4,7 +4,6 @@ using System.IO; using System.Net; using System.Security.Claims; using IdentityServer4.Models; -using IdentityServer4.Services.InMemory; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -15,6 +14,9 @@ using Xunit; namespace Ocelot.AcceptanceTests { + using IdentityServer4; + using IdentityServer4.Test; + public class AuthorisationTests : IDisposable { private IWebHost _servicebuilder; @@ -164,27 +166,35 @@ namespace Ocelot.AcceptanceTests .ConfigureServices(services => { services.AddLogging(); - services.AddDeveloperIdentityServer() - .AddInMemoryScopes(new List + services.AddIdentityServer() + .AddTemporarySigningCredential() + .AddInMemoryApiResources(new List { - new Scope + new ApiResource { Name = scopeName, Description = "My API", Enabled = true, - AllowUnrestrictedIntrospection = true, - ScopeSecrets = new List() + DisplayName = "test", + Scopes = new List() + { + new Scope("api"), + new Scope("openid"), + new Scope("offline_access") + }, + ApiSecrets = new List() { new Secret { Value = "secret".Sha256() } }, - IncludeAllClaimsForUser = true + UserClaims = new List() + { + "CustomerId", "LocationId", "UserType", "UserId" + } }, - StandardScopes.OpenId, - StandardScopes.OfflineAccess }) .AddInMemoryClients(new List { @@ -199,14 +209,13 @@ namespace Ocelot.AcceptanceTests RequireClientSecret = false } }) - .AddInMemoryUsers(new List + .AddTestUsers(new List { - new InMemoryUser + new TestUser { Username = "test", Password = "test", - Enabled = true, - Subject = "registered|1231231", + SubjectId = "registered|1231231", Claims = new List { new Claim("CustomerId", "123"), diff --git a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs index c181a445..160686d5 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Net; using System.Security.Claims; using IdentityServer4.Models; -using IdentityServer4.Services.InMemory; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -17,6 +16,9 @@ using Xunit; [assembly: CollectionBehavior(DisableTestParallelization = true)] namespace Ocelot.AcceptanceTests { + using IdentityServer4; + using IdentityServer4.Test; + public class ClaimsToHeadersForwardingTests : IDisposable { private IWebHost _servicebuilder; @@ -31,12 +33,11 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_response_200_and_foward_claim_as_header() { - var user = new InMemoryUser + var user = new TestUser() { Username = "test", Password = "test", - Enabled = true, - Subject = "registered|1231231", + SubjectId = "registered|1231231", Claims = new List { new Claim("CustomerId", "123"), @@ -115,7 +116,7 @@ namespace Ocelot.AcceptanceTests _servicebuilder.Start(); } - private void GivenThereIsAnIdentityServerOn(string url, string scopeName, AccessTokenType tokenType, InMemoryUser user) + private void GivenThereIsAnIdentityServerOn(string url, string scopeName, AccessTokenType tokenType, TestUser user) { _identityServerBuilder = new WebHostBuilder() .UseUrls(url) @@ -126,27 +127,34 @@ namespace Ocelot.AcceptanceTests .ConfigureServices(services => { services.AddLogging(); - services.AddDeveloperIdentityServer() - .AddInMemoryScopes(new List + services.AddIdentityServer() + .AddTemporarySigningCredential() + .AddInMemoryApiResources(new List { - new Scope + new ApiResource { Name = scopeName, Description = "My API", Enabled = true, - AllowUnrestrictedIntrospection = true, - ScopeSecrets = new List() + DisplayName = "test", + Scopes = new List() + { + new Scope("api"), + new Scope("openid"), + new Scope("offline_access") + }, + ApiSecrets = new List() { new Secret { Value = "secret".Sha256() } }, - IncludeAllClaimsForUser = true - }, - - StandardScopes.OpenId, - StandardScopes.OfflineAccess + UserClaims = new List() + { + "CustomerId", "LocationId", "UserType", "UserId" + } + } }) .AddInMemoryClients(new List { @@ -161,7 +169,7 @@ namespace Ocelot.AcceptanceTests RequireClientSecret = false } }) - .AddInMemoryUsers(new List + .AddTestUsers(new List { user }); diff --git a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs index 51e76942..36583018 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Net; using System.Security.Claims; using IdentityServer4.Models; -using IdentityServer4.Services.InMemory; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -17,6 +16,9 @@ using Xunit; namespace Ocelot.AcceptanceTests { + using IdentityServer4; + using IdentityServer4.Test; + public class ClaimsToQueryStringForwardingTests : IDisposable { private IWebHost _servicebuilder; @@ -31,12 +33,11 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_response_200_and_foward_claim_as_query_string() { - var user = new InMemoryUser + var user = new TestUser() { Username = "test", Password = "test", - Enabled = true, - Subject = "registered|1231231", + SubjectId = "registered|1231231", Claims = new List { new Claim("CustomerId", "123"), @@ -122,7 +123,7 @@ namespace Ocelot.AcceptanceTests _servicebuilder.Start(); } - private void GivenThereIsAnIdentityServerOn(string url, string scopeName, AccessTokenType tokenType, InMemoryUser user) + private void GivenThereIsAnIdentityServerOn(string url, string scopeName, AccessTokenType tokenType, TestUser user) { _identityServerBuilder = new WebHostBuilder() .UseUrls(url) @@ -133,27 +134,34 @@ namespace Ocelot.AcceptanceTests .ConfigureServices(services => { services.AddLogging(); - services.AddDeveloperIdentityServer() - .AddInMemoryScopes(new List + services.AddIdentityServer() + .AddTemporarySigningCredential() + .AddInMemoryApiResources(new List { - new Scope + new ApiResource { - Name = scopeName, + Name = scopeName, Description = "My API", Enabled = true, - AllowUnrestrictedIntrospection = true, - ScopeSecrets = new List() + DisplayName = "test", + Scopes = new List() + { + new Scope("api"), + new Scope("openid"), + new Scope("offline_access") + }, + ApiSecrets = new List() { new Secret { Value = "secret".Sha256() } }, - IncludeAllClaimsForUser = true - }, - - StandardScopes.OpenId, - StandardScopes.OfflineAccess + UserClaims = new List() + { + "CustomerId", "LocationId", "UserType", "UserId" + } + } }) .AddInMemoryClients(new List { @@ -168,7 +176,7 @@ namespace Ocelot.AcceptanceTests RequireClientSecret = false } }) - .AddInMemoryUsers(new List + .AddTestUsers(new List { user }); diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 5f8f1eac..c2bd7ee7 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -132,8 +132,8 @@ namespace Ocelot.AcceptanceTests using (var httpClient = new HttpClient()) { var response = httpClient.PostAsync(tokenUrl, content).Result; - response.EnsureSuccessStatusCode(); var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); _token = JsonConvert.DeserializeObject(responseContent); } } diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index 69ccc690..99c5577c 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -3,6 +3,6 @@ public static class TestConfiguration { public static double Version => 1.4; - public static string ConfigurationPath => $"./bin/Debug/netcoreapp{Version}/configuration.json"; + public static string ConfigurationPath => $"./bin/Debug/netcoreapp{Version}/win10-x64/configuration.json"; } } diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index dec6fd7b..21e84e6d 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -3,40 +3,40 @@ "buildOptions": { "copyToOutput": { - "include": [ - "configuration.json" - ] + "include": [ + "configuration.json" + ] } }, "testRunner": "xunit", - "dependencies": { - "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", - "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0", - "Microsoft.Extensions.Configuration.Json": "1.0.0", - "Microsoft.Extensions.Logging": "1.0.0", - "Microsoft.Extensions.Logging.Console": "1.0.0", - "Microsoft.Extensions.Logging.Debug": "1.0.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", - "Microsoft.AspNetCore.Http": "1.0.0", - "Ocelot": "1.0.0-*", - "xunit": "2.2.0-beta2-build3300", - "dotnet-test-xunit": "2.2.0-preview2-build1029", - "Ocelot.ManualTest": "1.0.0-*", - "Microsoft.AspNetCore.TestHost": "1.0.0", - "IdentityServer4": "1.0.0-rc2", - "Microsoft.AspNetCore.Mvc": "1.0.1", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.1", - "Microsoft.NETCore.App": { - "version": "1.0.1", - "type": "platform" - }, - "Shouldly": "2.8.2", - "TestStack.BDDfy": "4.3.2" - }, - + "dependencies": { + "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", + "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", + "Microsoft.Extensions.Configuration.Json": "1.1.0", + "Microsoft.Extensions.Logging": "1.1.0", + "Microsoft.Extensions.Logging.Console": "1.1.0", + "Microsoft.Extensions.Logging.Debug": "1.1.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", + "Microsoft.AspNetCore.Http": "1.1.0", + "Microsoft.DotNet.InternalAbstractions": "1.0.0", + "Ocelot": "1.0.0-*", + "xunit": "2.2.0-beta2-build3300", + "dotnet-test-xunit": "2.2.0-preview2-build1029", + "Ocelot.ManualTest": "1.0.0-*", + "Microsoft.AspNetCore.TestHost": "1.1.0", + "IdentityServer4": "1.0.1", + "Microsoft.AspNetCore.Mvc": "1.1.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", + "Microsoft.NETCore.App": "1.1.0", + "Shouldly": "2.8.2", + "TestStack.BDDfy": "4.3.2" + }, + "runtimes": { + "win10-x64": {} + }, "frameworks": { "netcoreapp1.4": { "imports": [ diff --git a/test/Ocelot.Benchmarks/project.json b/test/Ocelot.Benchmarks/project.json index 791fea3d..c4021c17 100644 --- a/test/Ocelot.Benchmarks/project.json +++ b/test/Ocelot.Benchmarks/project.json @@ -1,22 +1,20 @@ -{ - "version": "1.0.0-*", - "buildOptions": { - "emitEntryPoint": true - }, +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true + }, - "dependencies": { - "Microsoft.NETCore.App": { - "type": "platform", - "version": "1.0.1" - }, - "Ocelot": "1.0.0-*", - "BenchmarkDotNet": "0.9.9" - }, - - "frameworks": { - "netcoreapp1.4": { - "imports": [ - ] - } + "dependencies": { + "Ocelot": "1.0.0-*", + "BenchmarkDotNet": "0.10.1" + }, + "runtimes": { + "win10-x64": {} + }, + "frameworks": { + "netcoreapp1.4": { + "imports": [ + ] } + } } diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index 65220938..7646ffb9 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -1,64 +1,61 @@ { - "version": "1.0.0-*", + "version": "1.0.0-*", - "dependencies": { - "Microsoft.AspNetCore.Http": "1.0.0", - "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", - "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0", - "Microsoft.Extensions.Configuration.Json": "1.0.0", - "Microsoft.Extensions.Logging": "1.0.0", - "Microsoft.Extensions.Logging.Console": "1.0.0", - "Microsoft.Extensions.Logging.Debug": "1.0.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", - "Ocelot": "1.0.0-*", - "Microsoft.AspNetCore.Mvc": "1.0.1", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.1", - "Microsoft.NETCore.App": { - "version": "1.0.1", - "type": "platform" - } - }, + "dependencies": { + "Microsoft.AspNetCore.Http": "1.1.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", + "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", + "Microsoft.Extensions.Configuration.Json": "1.1.0", + "Microsoft.Extensions.Logging": "1.1.0", + "Microsoft.Extensions.Logging.Console": "1.1.0", + "Microsoft.Extensions.Logging.Debug": "1.1.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", + "Ocelot": "1.0.0-*", + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", + "Microsoft.NETCore.App": "1.1.0" + }, - "tools": { - "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" - }, - - "frameworks": { - "netcoreapp1.4": { - "imports": [ - ] - } - }, - - "buildOptions": { - "emitEntryPoint": true, - "preserveCompilationContext": true, - "copyToOutput": { - "include": [ - "configuration.json" - ] - } - }, - - "runtimeOptions": { - "configProperties": { - "System.GC.Server": true - } - }, - - "publishOptions": { - "include": [ - "wwwroot", - "Views", - "Areas/**/Views", - "appsettings.json", - "web.config", - "configuration.json" - ] - }, - - "scripts": { - "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] - } + "tools": { + "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" + }, + "runtimes": { + "win10-x64": {} + }, + "frameworks": { + "netcoreapp1.4": { + "imports": [ + ] } + }, + + "buildOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true, + "copyToOutput": { + "include": [ + "configuration.json" + ] + } + }, + + "runtimeOptions": { + "configProperties": { + "System.GC.Server": true + } + }, + + "publishOptions": { + "include": [ + "wwwroot", + "Views", + "Areas/**/Views", + "appsettings.json", + "web.config", + "configuration.json" + ] + }, + + "scripts": { + "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] + } +} diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index beb19321..616a9e45 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -1,37 +1,38 @@ { - "version": "1.0.0-*", + "version": "1.0.0-*", - "testRunner": "xunit", + "testRunner": "xunit", - "dependencies": { - "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", - "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0", - "Microsoft.Extensions.Configuration.Json": "1.0.0", - "Microsoft.Extensions.Logging": "1.0.0", - "Microsoft.Extensions.Logging.Console": "1.0.0", - "Microsoft.Extensions.Logging.Debug": "1.0.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", - "Microsoft.AspNetCore.Http": "1.0.0", - "Ocelot": "1.0.0-*", - "xunit": "2.2.0-beta2-build3300", - "dotnet-test-xunit": "2.2.0-preview2-build1029", - "Moq": "4.6.38-alpha", - "Microsoft.AspNetCore.TestHost": "1.0.0", - "Microsoft.AspNetCore.Mvc": "1.0.1", - "Microsoft.AspNetCore.Server.Kestrel": "1.0.1", - "Microsoft.NETCore.App": { - "version": "1.0.1", - "type": "platform" - }, - "Shouldly": "2.8.2", - "TestStack.BDDfy": "4.3.2" - }, - - "frameworks": { - "netcoreapp1.4": { - "imports": [ - ] - } + "dependencies": { + "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", + "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", + "Microsoft.Extensions.Configuration.Json": "1.1.0", + "Microsoft.Extensions.Logging": "1.1.0", + "Microsoft.Extensions.Logging.Console": "1.1.0", + "Microsoft.Extensions.Logging.Debug": "1.1.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", + "Microsoft.AspNetCore.Http": "1.1.0", + "Ocelot": "1.0.0-*", + "xunit": "2.2.0-beta2-build3300", + "dotnet-test-xunit": "2.2.0-preview2-build1029", + "Moq": "4.6.38-alpha", + "Microsoft.AspNetCore.TestHost": "1.1.0", + "Microsoft.AspNetCore.Mvc": "1.1.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", + "Microsoft.NETCore.App": "1.1.0", + "Shouldly": "2.8.2", + "TestStack.BDDfy": "4.3.2", + "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", + "Microsoft.DotNet.InternalAbstractions": "1.0.0" + }, + "runtimes": { + "win10-x64": {} + }, + "frameworks": { + "netcoreapp1.4": { + "imports": [ + ] } + } } From b873300e82629a293b8538ebbaf424b4b2dd3433 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Thu, 12 Jan 2017 20:13:54 +0000 Subject: [PATCH 002/113] updated nuspec --- Ocelot.nuspec | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/Ocelot.nuspec b/Ocelot.nuspec index f9875da0..d236d60d 100644 --- a/Ocelot.nuspec +++ b/Ocelot.nuspec @@ -13,28 +13,32 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + From a8878b4a198a4fcd5658c23f07dca14f895d9605 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Tue, 17 Jan 2017 10:20:49 +0000 Subject: [PATCH 003/113] test passing on mac and windows --- build-and-run-tests.bat | 0 build.bat | 0 configuration.yaml | 3 ++ run-tests.bat | 0 src/Ocelot/project.json | 3 +- .../TestConfiguration.cs | 28 ++++++++++++++++++- .../Ocelot.AcceptanceTests/configuration.json | 0 test/Ocelot.AcceptanceTests/project.json | 3 +- test/Ocelot.Benchmarks/project.json | 3 +- test/Ocelot.ManualTest/project.json | 3 +- test/Ocelot.UnitTests/project.json | 3 +- 11 files changed, 40 insertions(+), 6 deletions(-) mode change 100644 => 100755 build-and-run-tests.bat mode change 100644 => 100755 build.bat create mode 100755 configuration.yaml mode change 100644 => 100755 run-tests.bat mode change 100644 => 100755 test/Ocelot.AcceptanceTests/configuration.json diff --git a/build-and-run-tests.bat b/build-and-run-tests.bat old mode 100644 new mode 100755 diff --git a/build.bat b/build.bat old mode 100644 new mode 100755 diff --git a/configuration.yaml b/configuration.yaml new file mode 100755 index 00000000..2e47e77d --- /dev/null +++ b/configuration.yaml @@ -0,0 +1,3 @@ +Routes: +- Downstream: http://localhost:51879/ + Upstream: /heee diff --git a/run-tests.bat b/run-tests.bat old mode 100644 new mode 100755 diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 1cecd70c..cd6cbff5 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -30,7 +30,8 @@ "CacheManager.Microsoft.Extensions.Logging": "0.9.2" }, "runtimes": { - "win10-x64": {} + "win10-x64": {}, + "osx.10.11-x64":{} }, "frameworks": { "netcoreapp1.4": { diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index 99c5577c..627d0ff4 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -1,8 +1,34 @@ namespace Ocelot.AcceptanceTests { + using System.Runtime.InteropServices; + public static class TestConfiguration { public static double Version => 1.4; - public static string ConfigurationPath => $"./bin/Debug/netcoreapp{Version}/win10-x64/configuration.json"; + //public static string Runtime => "win10-x64"; + public static string ConfigurationPath => GetConfigurationPath(); + + public static string GetConfigurationPath() + { + var osArchitecture = RuntimeInformation.OSArchitecture.ToString(); + + var oSDescription = string.Empty; + + if(RuntimeInformation.OSDescription.ToLower().Contains("darwin")) + { + oSDescription = "osx.10.11"; + } + + if(RuntimeInformation.OSDescription.ToLower().Contains("windows")) + { + oSDescription = "win10"; + } + + var runTime = $"{oSDescription}-{osArchitecture}".ToLower(); + + var configPath = $"./bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; + + return configPath; + } } } diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json old mode 100644 new mode 100755 diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index 21e84e6d..4d33f2d5 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -35,7 +35,8 @@ "TestStack.BDDfy": "4.3.2" }, "runtimes": { - "win10-x64": {} + "win10-x64": {}, + "osx.10.11-x64":{} }, "frameworks": { "netcoreapp1.4": { diff --git a/test/Ocelot.Benchmarks/project.json b/test/Ocelot.Benchmarks/project.json index c4021c17..4c98cf40 100644 --- a/test/Ocelot.Benchmarks/project.json +++ b/test/Ocelot.Benchmarks/project.json @@ -9,7 +9,8 @@ "BenchmarkDotNet": "0.10.1" }, "runtimes": { - "win10-x64": {} + "win10-x64": {}, + "osx.10.11-x64":{} }, "frameworks": { "netcoreapp1.4": { diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index 7646ffb9..774eabfa 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -19,7 +19,8 @@ "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "runtimes": { - "win10-x64": {} + "win10-x64": {}, + "osx.10.11-x64":{} }, "frameworks": { "netcoreapp1.4": { diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index 616a9e45..6ab9025a 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -27,7 +27,8 @@ "Microsoft.DotNet.InternalAbstractions": "1.0.0" }, "runtimes": { - "win10-x64": {} + "win10-x64": {}, + "osx.10.11-x64":{} }, "frameworks": { "netcoreapp1.4": { From 8c213f133cc57fa9bfa97758d8c9ef067f095a1a Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Tue, 17 Jan 2017 10:33:55 +0000 Subject: [PATCH 004/113] test passing on mac and windows --- src/Ocelot/project.json | 3 ++- test/Ocelot.AcceptanceTests/TestConfiguration.cs | 13 +++++++++---- test/Ocelot.AcceptanceTests/project.json | 3 ++- test/Ocelot.Benchmarks/project.json | 3 ++- test/Ocelot.ManualTest/project.json | 3 ++- test/Ocelot.UnitTests/project.json | 3 ++- 6 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index cd6cbff5..85008568 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -31,7 +31,8 @@ }, "runtimes": { "win10-x64": {}, - "osx.10.11-x64":{} + "osx.10.11-x64":{}, + "win7-x64": {} }, "frameworks": { "netcoreapp1.4": { diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index 627d0ff4..a9776c1b 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -16,14 +16,19 @@ if(RuntimeInformation.OSDescription.ToLower().Contains("darwin")) { - oSDescription = "osx.10.11"; + return FormatConfigurationPath("osx.10.11", osArchitecture); } - if(RuntimeInformation.OSDescription.ToLower().Contains("windows")) - { - oSDescription = "win10"; + if(RuntimeInformation.OSDescription.ToLower().Contains("microsoft windows 10")) + { + return FormatConfigurationPath("win10", osArchitecture); } + + return FormatConfigurationPath("win7", osArchitecture); + } + private static string FormatConfigurationPath(string oSDescription, string osArchitecture) + { var runTime = $"{oSDescription}-{osArchitecture}".ToLower(); var configPath = $"./bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index 4d33f2d5..4b364510 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -36,7 +36,8 @@ }, "runtimes": { "win10-x64": {}, - "osx.10.11-x64":{} + "osx.10.11-x64":{}, + "win7-x64": {} }, "frameworks": { "netcoreapp1.4": { diff --git a/test/Ocelot.Benchmarks/project.json b/test/Ocelot.Benchmarks/project.json index 4c98cf40..da310ddd 100644 --- a/test/Ocelot.Benchmarks/project.json +++ b/test/Ocelot.Benchmarks/project.json @@ -10,7 +10,8 @@ }, "runtimes": { "win10-x64": {}, - "osx.10.11-x64":{} + "osx.10.11-x64":{}, + "win7-x64": {} }, "frameworks": { "netcoreapp1.4": { diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index 774eabfa..181bdb07 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -20,7 +20,8 @@ }, "runtimes": { "win10-x64": {}, - "osx.10.11-x64":{} + "osx.10.11-x64":{}, + "win7-x64": {} }, "frameworks": { "netcoreapp1.4": { diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index 6ab9025a..605b25d6 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -28,7 +28,8 @@ }, "runtimes": { "win10-x64": {}, - "osx.10.11-x64":{} + "osx.10.11-x64":{}, + "win7-x64": {} }, "frameworks": { "netcoreapp1.4": { From 622c49d057b76bff39ae3eb2fab16469f5a32807 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Tue, 17 Jan 2017 15:59:39 +0000 Subject: [PATCH 005/113] removed comment --- test/Ocelot.AcceptanceTests/TestConfiguration.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index a9776c1b..0aa730be 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -5,7 +5,6 @@ public static class TestConfiguration { public static double Version => 1.4; - //public static string Runtime => "win10-x64"; public static string ConfigurationPath => GetConfigurationPath(); public static string GetConfigurationPath() From 044b609ea9d08af99fc5baeb5c5ccee6fa54edc8 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 20 Jan 2017 19:03:18 +0000 Subject: [PATCH 006/113] started implementing service discovery integration --- run-acceptance-tests.bat | 8 + run-tests.bat | 19 +-- run-unit-tests.bat | 8 + .../Configuration/Builder/ReRouteBuilder.cs | 50 ++++++- .../Creator/FileOcelotConfigurationCreator.cs | 15 +- .../File/FileGlobalConfiguration.cs | 5 + src/Ocelot/Configuration/File/FileReRoute.cs | 3 + .../File/FileServiceDiscoveryProvider.cs | 8 + src/Ocelot/Configuration/ReRoute.cs | 18 ++- .../DownstreamTemplateContainsHostError.cs | 12 ++ .../DownstreamTemplateContainsSchemeError.cs | 12 ++ .../Validator/FileConfigurationValidator.cs | 28 ++++ src/Ocelot/Errors/OcelotErrorCode.cs | 4 +- .../ConfigurationValidationTests.cs | 46 +++++- .../FileConfigurationCreatorTests.cs | 137 ++++++++++++++++++ .../FileConfigurationProviderTests.cs | 5 +- 16 files changed, 346 insertions(+), 32 deletions(-) create mode 100755 run-acceptance-tests.bat create mode 100755 run-unit-tests.bat create mode 100644 src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs create mode 100644 src/Ocelot/Configuration/Validator/DownstreamTemplateContainsHostError.cs create mode 100644 src/Ocelot/Configuration/Validator/DownstreamTemplateContainsSchemeError.cs diff --git a/run-acceptance-tests.bat b/run-acceptance-tests.bat new file mode 100755 index 00000000..ba8a3489 --- /dev/null +++ b/run-acceptance-tests.bat @@ -0,0 +1,8 @@ +echo Running Ocelot.AcceptanceTests +cd test/Ocelot.AcceptanceTests/ +dotnet restore +dotnet test +cd ../../ + +echo Restoring Ocelot.ManualTest +dotnet restore test/Ocelot.ManualTest/ \ No newline at end of file diff --git a/run-tests.bat b/run-tests.bat index ae4856fc..39532229 100755 --- a/run-tests.bat +++ b/run-tests.bat @@ -1,17 +1,2 @@ -echo ------------------------- - -echo Restoring Ocelot -dotnet restore src/Ocelot - -echo Restoring Ocelot.ManualTest -dotnet restore test/Ocelot.ManualTest/ - -echo Running Ocelot.UnitTests -dotnet restore test/Ocelot.UnitTests/ -dotnet test test/Ocelot.UnitTests/ - -echo Running Ocelot.AcceptanceTests -cd test/Ocelot.AcceptanceTests/ -dotnet restore -dotnet test -cd ../../ \ No newline at end of file +./run-unit-tests.bat +./run-acceptance-tests.bat \ No newline at end of file diff --git a/run-unit-tests.bat b/run-unit-tests.bat new file mode 100755 index 00000000..9ad6a4f2 --- /dev/null +++ b/run-unit-tests.bat @@ -0,0 +1,8 @@ +echo ------------------------- + +echo Restoring Ocelot +dotnet restore src/Ocelot + +echo Running Ocelot.UnitTests +dotnet restore test/Ocelot.UnitTests/ +dotnet test test/Ocelot.UnitTests/ diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index f77ae66b..3f5ef40b 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Ocelot.Configuration.Builder { @@ -23,12 +24,54 @@ namespace Ocelot.Configuration.Builder private string _requestIdHeaderKey; private bool _isCached; private CacheOptions _fileCacheOptions; + private bool _useServiceDiscovery; + private string _serviceName; + private string _serviceDiscoveryProvider; + private string _serviceDiscoveryAddress; + private string _downstreamScheme; + private string _downstreamHost; public ReRouteBuilder() { _additionalScopes = new List(); } + public ReRouteBuilder WithDownstreamScheme(string downstreamScheme) + { + _downstreamScheme = downstreamScheme; + return this; + } + + public ReRouteBuilder WithDownstreamHost(string downstreamHost) + { + _downstreamHost = downstreamHost; + return this; + } + + public ReRouteBuilder WithServiceDiscoveryAddress(string serviceDiscoveryAddress) + { + _serviceDiscoveryAddress = serviceDiscoveryAddress; + return this; + } + + public ReRouteBuilder WithServiceDiscoveryProvider(string serviceDiscoveryProvider) + { + _serviceDiscoveryProvider = serviceDiscoveryProvider; + return this; + } + + public ReRouteBuilder WithServiceName(string serviceName) + { + _serviceName = serviceName; + return this; + } + + public ReRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery) + { + _useServiceDiscovery = useServiceDiscovery; + return this; + } + public ReRouteBuilder WithDownstreamTemplate(string input) { _downstreamTemplate = input; @@ -143,10 +186,13 @@ namespace Ocelot.Configuration.Builder public ReRoute Build() { + Func downstreamHostFunc = () => { return _downstreamHost; }; + return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, - _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions); + _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _serviceName, + _useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, downstreamHostFunc, _downstreamScheme); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index f74f880b..c71aba23 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -91,6 +91,13 @@ namespace Ocelot.Configuration.Creator ? globalConfiguration.RequestIdKey : reRoute.RequestIdKey; + var useServiceDiscovery = !string.IsNullOrEmpty(reRoute.ServiceName) + && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Address) + && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); + + + Func downstreamHostFunc = () => { return reRoute.DownstreamHost; }; + if (isAuthenticated) { var authOptionsForRoute = new AuthenticationOptions(reRoute.AuthenticationOptions.Provider, @@ -106,14 +113,18 @@ namespace Ocelot.Configuration.Creator reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, authOptionsForRoute, claimsToHeaders, claimsToClaims, reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, - requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds)); + requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), + reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, + globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostFunc, reRoute.DownstreamScheme); } return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, null, new List(), new List(), reRoute.RouteClaimsRequirement, isAuthorised, new List(), - requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds)); + requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), + reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, + globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostFunc, reRoute.DownstreamScheme); } private string BuildUpstreamTemplate(FileReRoute reRoute) diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs index 07c17188..f414bc83 100644 --- a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs @@ -2,6 +2,11 @@ { public class FileGlobalConfiguration { + public FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider(); + } public string RequestIdKey { get; set; } + public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;} } } diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 3773dd9d..3afa03ce 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -25,5 +25,8 @@ namespace Ocelot.Configuration.File public string RequestIdKey { get; set; } public FileCacheOptions FileCacheOptions { get; set; } public bool ReRouteIsCaseSensitive { get; set; } + public string ServiceName { get; set; } + public string DownstreamScheme {get;set;} + public string DownstreamHost {get;set;} } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs new file mode 100644 index 00000000..47efc6df --- /dev/null +++ b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs @@ -0,0 +1,8 @@ +namespace Ocelot.Configuration.File +{ + public class FileServiceDiscoveryProvider + { + public string Provider {get;set;} + public string Address {get;set;} + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 433250b3..8778e5f7 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Ocelot.Configuration { @@ -7,7 +8,8 @@ namespace Ocelot.Configuration public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries, - string requestIdKey, bool isCached, CacheOptions fileCacheOptions) + string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery, + string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func downstreamHost, string downstreamScheme) { DownstreamTemplate = downstreamTemplate; UpstreamTemplate = upstreamTemplate; @@ -26,6 +28,12 @@ namespace Ocelot.Configuration ?? new List(); ClaimsToHeaders = configurationHeaderExtractorProperties ?? new List(); + ServiceName = serviceName; + UseServiceDiscovery = useServiceDiscovery; + ServiceDiscoveryProvider = serviceDiscoveryProvider; + ServiceDiscoveryAddress = serviceDiscoveryAddress; + DownstreamHost = downstreamHost; + DownstreamScheme = downstreamScheme; } public string DownstreamTemplate { get; private set; } @@ -42,5 +50,11 @@ namespace Ocelot.Configuration public string RequestIdKey { get; private set; } public bool IsCached { get; private set; } public CacheOptions FileCacheOptions { get; private set; } + public string ServiceName { get; private set;} + public bool UseServiceDiscovery { get; private set;} + public string ServiceDiscoveryProvider { get; private set;} + public string ServiceDiscoveryAddress { get; private set;} + public Func DownstreamHost {get;private set;} + public string DownstreamScheme {get;private set;} } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsHostError.cs b/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsHostError.cs new file mode 100644 index 00000000..8d9dba92 --- /dev/null +++ b/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsHostError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.Configuration.Validator +{ + public class DownstreamTemplateContainsHostError : Error + { + public DownstreamTemplateContainsHostError(string message) + : base(message, OcelotErrorCode.DownstreamTemplateContainsHostError) + { + } + } +} diff --git a/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsSchemeError.cs b/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsSchemeError.cs new file mode 100644 index 00000000..1901fc88 --- /dev/null +++ b/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsSchemeError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.Configuration.Validator +{ + public class DownstreamTemplateContainsSchemeError : Error + { + public DownstreamTemplateContainsSchemeError(string message) + : base(message, OcelotErrorCode.DownstreamTemplateContainsSchemeError) + { + } + } +} diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs index fb55a10b..3a675d41 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs @@ -26,6 +26,13 @@ namespace Ocelot.Configuration.Validator return new OkResponse(result); } + result = CheckForReRoutesContainingDownstreamScheme(configuration); + + if (result.IsError) + { + return new OkResponse(result); + } + return new OkResponse(result); } @@ -63,6 +70,27 @@ namespace Ocelot.Configuration.Validator return Enum.TryParse(provider, true, out supportedProvider); } + private ConfigurationValidationResult CheckForReRoutesContainingDownstreamScheme(FileConfiguration configuration) + { + var errors = new List(); + + foreach(var reRoute in configuration.ReRoutes) + { + if(reRoute.DownstreamTemplate.Contains("https://") + || reRoute.DownstreamTemplate.Contains("http://")) + { + errors.Add(new DownstreamTemplateContainsSchemeError($"{reRoute.DownstreamTemplate} contains scheme")); + } + } + + if(errors.Any()) + { + return new ConfigurationValidationResult(false, errors); + } + + return new ConfigurationValidationResult(true, errors); + } + private ConfigurationValidationResult CheckForDupliateReRoutes(FileConfiguration configuration) { var hasDupes = configuration.ReRoutes diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index a4864d1a..f0a336f0 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -17,6 +17,8 @@ InstructionNotForClaimsError, UnauthorizedError, ClaimValueNotAuthorisedError, - UserDoesNotHaveClaimError + UserDoesNotHaveClaimError, + DownstreamTemplateContainsSchemeError, + DownstreamTemplateContainsHostError } } diff --git a/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs b/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs index 7f6f4005..174ea8bc 100644 --- a/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs @@ -19,6 +19,44 @@ namespace Ocelot.UnitTests.Configuration _configurationValidator = new FileConfigurationValidator(); } + [Fact] + public void configuration_is_invalid_if_scheme_in_downstream_template() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration() + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamTemplate = "http://www.bbc.co.uk/api/products/{productId}", + UpstreamTemplate = "http://asdf.com" + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .BDDfy(); + } + + [Fact] + public void configuration_is_invalid_if_host_in_downstream_template() + { + this.Given(x => x.GivenAConfiguration(new FileConfiguration() + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamTemplate = "www.bbc.co.uk/api/products/{productId}", + UpstreamTemplate = "http://asdf.com" + } + } + })) + .When(x => x.WhenIValidateTheConfiguration()) + .Then(x => x.ThenTheResultIsNotValid()) + .BDDfy(); + } + [Fact] public void configuration_is_valid_with_one_reroute() { @@ -28,7 +66,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - DownstreamTemplate = "http://www.bbc.co.uk", + DownstreamTemplate = "/api/products/", UpstreamTemplate = "http://asdf.com" } } @@ -47,7 +85,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - DownstreamTemplate = "http://www.bbc.co.uk", + DownstreamTemplate = "/api/products/", UpstreamTemplate = "http://asdf.com", AuthenticationOptions = new FileAuthenticationOptions { @@ -70,7 +108,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - DownstreamTemplate = "http://www.bbc.co.uk", + DownstreamTemplate = "/api/products/", UpstreamTemplate = "http://asdf.com", AuthenticationOptions = new FileAuthenticationOptions { @@ -94,7 +132,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - DownstreamTemplate = "http://www.bbc.co.uk", + DownstreamTemplate = "/api/products/", UpstreamTemplate = "http://asdf.com" }, new FileReRoute diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 4a4a5345..4207a333 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -35,6 +35,143 @@ namespace Ocelot.UnitTests.Configuration _fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object); } + [Fact] + public void should_use_downstream_host() + { + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHost = "127.0.0.1", + UpstreamTemplate = "/api/products/{productId}", + DownstreamTemplate = "/products/{productId}", + UpstreamHttpMethod = "Get", + } + }, + })) + .And(x => x.GivenTheConfigIsValid()) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheReRoutesAre(new List + { + new ReRouteBuilder() + .WithDownstreamHost("127.0.0.1") + .WithDownstreamTemplate("/products/{productId}") + .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod("Get") + .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") + .Build() + })) + .BDDfy(); + } + + public void should_use_downstream_scheme() + { + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamScheme = "https", + UpstreamTemplate = "/api/products/{productId}", + DownstreamTemplate = "/products/{productId}", + UpstreamHttpMethod = "Get", + } + }, + })) + .And(x => x.GivenTheConfigIsValid()) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheReRoutesAre(new List + { + new ReRouteBuilder() + .WithDownstreamScheme("https") + .WithDownstreamTemplate("/products/{productId}") + .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod("Get") + .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") + .Build() + })) + .BDDfy(); + } + + [Fact] + public void should_use_service_discovery_for_downstream_service_host() + { + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + UpstreamTemplate = "/api/products/{productId}", + DownstreamTemplate = "/products/{productId}", + UpstreamHttpMethod = "Get", + ReRouteIsCaseSensitive = false, + ServiceName = "ProductService" + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Provider = "consul", + Address = "127.0.0.1" + } + } + })) + .And(x => x.GivenTheConfigIsValid()) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheReRoutesAre(new List + { + new ReRouteBuilder() + .WithDownstreamTemplate("/products/{productId}") + .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod("Get") + .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") + .WithServiceName("ProductService") + .WithUseServiceDiscovery(true) + .WithServiceDiscoveryProvider("consul") + .WithServiceDiscoveryAddress("127.0.01") + .Build() + })) + .BDDfy(); + } + + [Fact] + public void should_not_use_service_discovery_for_downstream_host_url_when_no_service_name() + { + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + UpstreamTemplate = "/api/products/{productId}", + DownstreamTemplate = "/products/{productId}", + UpstreamHttpMethod = "Get", + ReRouteIsCaseSensitive = false, + } + } + })) + .And(x => x.GivenTheConfigIsValid()) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheReRoutesAre(new List + { + new ReRouteBuilder() + .WithDownstreamTemplate("/products/{productId}") + .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamHttpMethod("Get") + .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") + .WithUseServiceDiscovery(false) + .WithServiceDiscoveryProvider(null) + .WithServiceDiscoveryAddress(null) + .Build() + })) + .BDDfy(); + } + [Fact] public void should_use_reroute_case_sensitivity_value() { diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs index 1bdb3279..56fb6487 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Collections.Generic; using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Creator; From 0f71c040d9e098d2d495e81384df41a5fdba868c Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 21 Jan 2017 09:59:47 +0000 Subject: [PATCH 007/113] split DownstreamTemplate into DownstreamPathTemplate, DownstreamScheme, DownstreamHost and DownstreamPort in order to prepare for service discovery --- Ocelot.sln | 2 + README.md | 11 +- configuration-explanation.txt | 19 ++- .../Configuration/Builder/ReRouteBuilder.cs | 18 ++- .../Creator/FileOcelotConfigurationCreator.cs | 11 +- src/Ocelot/Configuration/File/FileReRoute.cs | 3 +- src/Ocelot/Configuration/ReRoute.cs | 13 +- .../DownstreamPathTemplateAlreadyUsedError.cs | 11 ++ ...wnstreamPathTemplateContainsSchemeError.cs | 12 ++ .../DownstreamTemplateAlreadyUsedError.cs | 11 -- .../DownstreamTemplateContainsHostError.cs | 12 -- .../DownstreamTemplateContainsSchemeError.cs | 12 -- .../Validator/FileConfigurationValidator.cs | 16 +-- .../ServiceCollectionExtensions.cs | 4 +- .../DownstreamRouteFinderMiddleware.cs | 2 +- .../DownstreamHostNullOrEmptyError.cs | 12 ++ .../DownstreamPathNullOrEmptyError.cs | 12 ++ .../DownstreamSchemeNullOrEmptyError.cs | 12 ++ .../DownstreamUrlCreator/IUrlBuilder.cs | 12 ++ .../DownstreamUrlCreatorMiddleware.cs | 39 ++++-- src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs | 47 +++++++ .../DownstreamUrlTemplateVariableReplacer.cs | 14 +- ...wnstreamUrlPathTemplateVariableReplacer.cs | 5 +- src/Ocelot/Errors/OcelotErrorCode.cs | 8 +- src/Ocelot/Values/DownstreamPath.cs | 12 ++ src/Ocelot/Values/DownstreamPathTemplate.cs | 12 ++ .../DownstreamUrl.cs | 4 +- src/Ocelot/Values/HostAndPort.cs | 14 ++ .../AuthenticationTests.cs | 41 ++++-- .../AuthorisationTests.cs | 10 +- test/Ocelot.AcceptanceTests/CachingTests.cs | 10 +- .../CaseSensitiveRoutingTests.cs | 30 ++++- .../ClaimsToHeadersForwardingTests.cs | 5 +- .../ClaimsToQueryStringForwardingTests.cs | 5 +- .../CustomMiddlewareTests.cs | 30 ++++- test/Ocelot.AcceptanceTests/RequestIdTests.cs | 15 ++- .../ReturnsErrorTests.cs | 2 +- test/Ocelot.AcceptanceTests/RoutingTests.cs | 91 ++++++++++++- .../Ocelot.AcceptanceTests/configuration.json | 2 +- test/Ocelot.ManualTest/Program.cs | 1 - test/Ocelot.ManualTest/configuration.json | 100 +++++++++++--- .../Claims/ClaimsBuilderMiddlewareTests.cs | 2 +- .../ConfigurationValidationTests.cs | 47 ++----- .../FileConfigurationCreatorTests.cs | 58 ++++---- .../InMemoryConfigurationRepositoryTests.cs | 10 +- .../DownstreamRouteFinderMiddlewareTests.cs | 2 +- .../DownstreamRouteFinderTests.cs | 14 +- .../DownstreamUrlCreatorMiddlewareTests.cs | 33 +++-- .../DownstreamUrlCreator/UrlBuilderTests.cs | 124 ++++++++++++++++++ ...eamUrlPathTemplateVariableReplacerTests.cs | 25 ++-- ...ttpRequestHeadersBuilderMiddlewareTests.cs | 2 +- .../QueryStringBuilderMiddlewareTests.cs | 2 +- .../RequestId/RequestIdMiddlewareTests.cs | 4 +- 53 files changed, 767 insertions(+), 258 deletions(-) create mode 100644 src/Ocelot/Configuration/Validator/DownstreamPathTemplateAlreadyUsedError.cs create mode 100644 src/Ocelot/Configuration/Validator/DownstreamPathTemplateContainsSchemeError.cs delete mode 100644 src/Ocelot/Configuration/Validator/DownstreamTemplateAlreadyUsedError.cs delete mode 100644 src/Ocelot/Configuration/Validator/DownstreamTemplateContainsHostError.cs delete mode 100644 src/Ocelot/Configuration/Validator/DownstreamTemplateContainsSchemeError.cs create mode 100644 src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs create mode 100644 src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs create mode 100644 src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs create mode 100644 src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs create mode 100644 src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs create mode 100644 src/Ocelot/Values/DownstreamPath.cs create mode 100644 src/Ocelot/Values/DownstreamPathTemplate.cs rename src/Ocelot/{DownstreamUrlCreator/UrlTemplateReplacer => Values}/DownstreamUrl.cs (74%) create mode 100644 src/Ocelot/Values/HostAndPort.cs create mode 100644 test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs diff --git a/Ocelot.sln b/Ocelot.sln index 5ebd0660..0165beb0 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -17,8 +17,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Ocelot.nuspec = Ocelot.nuspec push-to-nuget.bat = push-to-nuget.bat README.md = README.md + run-acceptance-tests.bat = run-acceptance-tests.bat run-benchmarks.bat = run-benchmarks.bat run-tests.bat = run-tests.bat + run-unit-tests.bat = run-unit-tests.bat EndProjectSection EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot", "src\Ocelot\Ocelot.xproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}" diff --git a/README.md b/README.md index dd5f1043..249a99a3 100644 --- a/README.md +++ b/README.md @@ -136,17 +136,20 @@ In order to set up a ReRoute you need to add one to the json array called ReRout the following. { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}", + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamPort": 80, + "DownstreamHost" "localhost" "UpstreamTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Put" } -The DownstreamTemplate is the URL that this request will be forwarded to. +The DownstreamPathTemplate,Scheme, Port and Host make the URL that this request will be forwarded to. The UpstreamTemplate is the URL that Ocelot will use to identity which -DownstreamTemplate to use for a given request. Finally the UpstreamHttpMethod is used so +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 DownstreamTemplate and UpstreamTemplate. If it is +The placeholder needs to be in both the DownstreamPathTemplate and UpstreamTemplate. If it is Ocelot will attempt to replace the placeholder with the correct variable value from the Upstream URL when the request comes in. diff --git a/configuration-explanation.txt b/configuration-explanation.txt index 898be89f..ad020469 100644 --- a/configuration-explanation.txt +++ b/configuration-explanation.txt @@ -1,11 +1,20 @@ { "ReRoutes": [ { - # The url we are forwarding the request to, ocelot will not add a trailing slash - "DownstreamTemplate": "http://somehost.com/identityserverexample", - # The path we are listening on for this re route, Ocelot will add a trailing slash to - # this property. Then when a request is made Ocelot makes sure a trailing slash is added - # to that so everything matches + # The downstream path we are forwarding the request to, ocelot will not add a trailing slash. + # Ocelot replaces any placeholders {etc} with matched values from the incoming request. + "DownstreamPathTemplate": "/identityserverexample/{someid}/something", + # The scheme you want Ocelot to use when making the downstream request + "DownstreamScheme": "https", + # The port you want Ocelot to use when making the downstream request, will default to + # scheme if nothing set + "DownstreamPort": 80, + # The host address of the downstream service, should not have a trailing slash or scheme + # if there is a trailing slash Ocelot will remove it. + "DownstreamHost" "localhost" + # The path template we are listening on for this re route, Ocelot will add a trailing + # slash to this property. Then when a request is made Ocelot makes sure a trailing + # slash is added, so everything matches "UpstreamTemplate": "/identityserverexample", # The method we are listening for on this re route "UpstreamHttpMethod": "Get", diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 3f5ef40b..1e06a440 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using Ocelot.Values; namespace Ocelot.Configuration.Builder { public class ReRouteBuilder { - private string _downstreamTemplate; + private string _downstreamPathTemplate; private string _upstreamTemplate; private string _upstreamTemplatePattern; private string _upstreamHttpMethod; @@ -30,6 +31,7 @@ namespace Ocelot.Configuration.Builder private string _serviceDiscoveryAddress; private string _downstreamScheme; private string _downstreamHost; + private int _dsPort; public ReRouteBuilder() { @@ -72,9 +74,9 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithDownstreamTemplate(string input) + public ReRouteBuilder WithDownstreamPathTemplate(string input) { - _downstreamTemplate = input; + _downstreamPathTemplate = input; return this; } @@ -184,11 +186,17 @@ namespace Ocelot.Configuration.Builder return this; } + public ReRouteBuilder WithDownstreamPort(int port) + { + _dsPort = port; + return this; + } + public ReRoute Build() { - Func downstreamHostFunc = () => { return _downstreamHost; }; + Func downstreamHostFunc = () => new HostAndPort(_downstreamHost, _dsPort); - return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, + return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _serviceName, diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index c71aba23..8884f0d9 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -8,6 +8,7 @@ using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; using Ocelot.Responses; using Ocelot.Utilities; +using Ocelot.Values; namespace Ocelot.Configuration.Creator { @@ -96,7 +97,7 @@ namespace Ocelot.Configuration.Creator && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); - Func downstreamHostFunc = () => { return reRoute.DownstreamHost; }; + Func downstreamHostAndPortFunc = () => new HostAndPort(reRoute.DownstreamHost.Trim('/'), reRoute.DownstreamPort); if (isAuthenticated) { @@ -109,22 +110,22 @@ namespace Ocelot.Configuration.Creator var claimsToClaims = GetAddThingsToRequest(reRoute.AddClaimsToRequest); var claimsToQueries = GetAddThingsToRequest(reRoute.AddQueriesToRequest); - return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, + return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, authOptionsForRoute, claimsToHeaders, claimsToClaims, reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostFunc, reRoute.DownstreamScheme); + globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme); } - return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, + return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, null, new List(), new List(), reRoute.RouteClaimsRequirement, isAuthorised, new List(), requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostFunc, reRoute.DownstreamScheme); + globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme); } private string BuildUpstreamTemplate(FileReRoute reRoute) diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 3afa03ce..a653224a 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -14,7 +14,7 @@ namespace Ocelot.Configuration.File FileCacheOptions = new FileCacheOptions(); } - public string DownstreamTemplate { get; set; } + public string DownstreamPathTemplate { get; set; } public string UpstreamTemplate { get; set; } public string UpstreamHttpMethod { get; set; } public FileAuthenticationOptions AuthenticationOptions { get; set; } @@ -28,5 +28,6 @@ namespace Ocelot.Configuration.File public string ServiceName { get; set; } public string DownstreamScheme {get;set;} public string DownstreamHost {get;set;} + public int DownstreamPort { get; set; } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 8778e5f7..960374cc 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -1,17 +1,18 @@ using System; using System.Collections.Generic; +using Ocelot.Values; namespace Ocelot.Configuration { public class ReRoute { - public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, + public ReRoute(DownstreamPathTemplate downstreamPathTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries, string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery, - string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func downstreamHost, string downstreamScheme) + string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func downstreamHostAndPort, string downstreamScheme) { - DownstreamTemplate = downstreamTemplate; + DownstreamPathTemplate = downstreamPathTemplate; UpstreamTemplate = upstreamTemplate; UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; @@ -32,11 +33,11 @@ namespace Ocelot.Configuration UseServiceDiscovery = useServiceDiscovery; ServiceDiscoveryProvider = serviceDiscoveryProvider; ServiceDiscoveryAddress = serviceDiscoveryAddress; - DownstreamHost = downstreamHost; + DownstreamHostAndPort = downstreamHostAndPort; DownstreamScheme = downstreamScheme; } - public string DownstreamTemplate { get; private set; } + public DownstreamPathTemplate DownstreamPathTemplate { get; private set; } public string UpstreamTemplate { get; private set; } public string UpstreamTemplatePattern { get; private set; } public string UpstreamHttpMethod { get; private set; } @@ -54,7 +55,7 @@ namespace Ocelot.Configuration public bool UseServiceDiscovery { get; private set;} public string ServiceDiscoveryProvider { get; private set;} public string ServiceDiscoveryAddress { get; private set;} - public Func DownstreamHost {get;private set;} + public Func DownstreamHostAndPort {get;private set;} public string DownstreamScheme {get;private set;} } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Validator/DownstreamPathTemplateAlreadyUsedError.cs b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateAlreadyUsedError.cs new file mode 100644 index 00000000..e350753c --- /dev/null +++ b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateAlreadyUsedError.cs @@ -0,0 +1,11 @@ +using Ocelot.Errors; + +namespace Ocelot.Configuration.Validator +{ + public class DownstreamPathTemplateAlreadyUsedError : Error + { + public DownstreamPathTemplateAlreadyUsedError(string message) : base(message, OcelotErrorCode.DownstreampathTemplateAlreadyUsedError) + { + } + } +} diff --git a/src/Ocelot/Configuration/Validator/DownstreamPathTemplateContainsSchemeError.cs b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateContainsSchemeError.cs new file mode 100644 index 00000000..a3dfa309 --- /dev/null +++ b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateContainsSchemeError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.Configuration.Validator +{ + public class DownstreamPathTemplateContainsSchemeError : Error + { + public DownstreamPathTemplateContainsSchemeError(string message) + : base(message, OcelotErrorCode.DownstreamPathTemplateContainsSchemeError) + { + } + } +} diff --git a/src/Ocelot/Configuration/Validator/DownstreamTemplateAlreadyUsedError.cs b/src/Ocelot/Configuration/Validator/DownstreamTemplateAlreadyUsedError.cs deleted file mode 100644 index b836b1eb..00000000 --- a/src/Ocelot/Configuration/Validator/DownstreamTemplateAlreadyUsedError.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.Configuration.Validator -{ - public class DownstreamTemplateAlreadyUsedError : Error - { - public DownstreamTemplateAlreadyUsedError(string message) : base(message, OcelotErrorCode.DownstreamTemplateAlreadyUsedError) - { - } - } -} diff --git a/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsHostError.cs b/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsHostError.cs deleted file mode 100644 index 8d9dba92..00000000 --- a/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsHostError.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.Configuration.Validator -{ - public class DownstreamTemplateContainsHostError : Error - { - public DownstreamTemplateContainsHostError(string message) - : base(message, OcelotErrorCode.DownstreamTemplateContainsHostError) - { - } - } -} diff --git a/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsSchemeError.cs b/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsSchemeError.cs deleted file mode 100644 index 1901fc88..00000000 --- a/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsSchemeError.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.Configuration.Validator -{ - public class DownstreamTemplateContainsSchemeError : Error - { - public DownstreamTemplateContainsSchemeError(string message) - : base(message, OcelotErrorCode.DownstreamTemplateContainsSchemeError) - { - } - } -} diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs index 3a675d41..412613eb 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs @@ -26,7 +26,7 @@ namespace Ocelot.Configuration.Validator return new OkResponse(result); } - result = CheckForReRoutesContainingDownstreamScheme(configuration); + result = CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(configuration); if (result.IsError) { @@ -70,25 +70,25 @@ namespace Ocelot.Configuration.Validator return Enum.TryParse(provider, true, out supportedProvider); } - private ConfigurationValidationResult CheckForReRoutesContainingDownstreamScheme(FileConfiguration configuration) + private ConfigurationValidationResult CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(FileConfiguration configuration) { var errors = new List(); foreach(var reRoute in configuration.ReRoutes) { - if(reRoute.DownstreamTemplate.Contains("https://") - || reRoute.DownstreamTemplate.Contains("http://")) + if(reRoute.DownstreamPathTemplate.Contains("https://") + || reRoute.DownstreamPathTemplate.Contains("http://")) { - errors.Add(new DownstreamTemplateContainsSchemeError($"{reRoute.DownstreamTemplate} contains scheme")); + errors.Add(new DownstreamPathTemplateContainsSchemeError($"{reRoute.DownstreamPathTemplate} contains scheme")); } } if(errors.Any()) { - return new ConfigurationValidationResult(false, errors); + return new ConfigurationValidationResult(true, errors); } - return new ConfigurationValidationResult(true, errors); + return new ConfigurationValidationResult(false, errors); } private ConfigurationValidationResult CheckForDupliateReRoutes(FileConfiguration configuration) @@ -105,7 +105,7 @@ namespace Ocelot.Configuration.Validator .Where(x => x.Skip(1).Any()); var errors = dupes - .Select(d => new DownstreamTemplateAlreadyUsedError(string.Format("Duplicate DownstreamTemplate: {0}", d.Key.UpstreamTemplate))) + .Select(d => new DownstreamPathTemplateAlreadyUsedError(string.Format("Duplicate DownstreamPath: {0}", d.Key.UpstreamTemplate))) .Cast() .ToList(); diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 04839abb..9f40b009 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -18,6 +18,7 @@ using Ocelot.Configuration.Repository; using Ocelot.Configuration.Validator; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.DownstreamUrlCreator; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Headers; using Ocelot.Infrastructure.Claims.Parser; @@ -59,6 +60,7 @@ namespace Ocelot.DependencyInjection services.AddMvcCore().AddJsonFormatters(); services.AddLogging(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -69,7 +71,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index a74b6316..f445b46b 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -44,7 +44,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware return; } - _logger.LogDebug("downstream template is {downstreamRoute.Data.ReRoute.DownstreamTemplate}", downstreamRoute.Data.ReRoute.DownstreamTemplate); + _logger.LogDebug("downstream template is {downstreamRoute.Data.ReRoute.DownstreamPath}", downstreamRoute.Data.ReRoute.DownstreamPathTemplate); SetDownstreamRouteForThisRequest(downstreamRoute.Data); diff --git a/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs b/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs new file mode 100644 index 00000000..8978f665 --- /dev/null +++ b/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.DownstreamUrlCreator +{ + public class DownstreamHostNullOrEmptyError : Error + { + public DownstreamHostNullOrEmptyError() + : base("downstream host was null or empty", OcelotErrorCode.DownstreamHostNullOrEmptyError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs b/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs new file mode 100644 index 00000000..fbc1a5f5 --- /dev/null +++ b/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.DownstreamUrlCreator +{ + public class DownstreamPathNullOrEmptyError : Error + { + public DownstreamPathNullOrEmptyError() + : base("downstream path was null or empty", OcelotErrorCode.DownstreamPathNullOrEmptyError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs b/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs new file mode 100644 index 00000000..e52d3488 --- /dev/null +++ b/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.DownstreamUrlCreator +{ + public class DownstreamSchemeNullOrEmptyError : Error + { + public DownstreamSchemeNullOrEmptyError() + : base("downstream scheme was null or empty", OcelotErrorCode.DownstreamSchemeNullOrEmptyError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs new file mode 100644 index 00000000..18683e62 --- /dev/null +++ b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs @@ -0,0 +1,12 @@ +using Ocelot.Configuration; +using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.DownstreamUrlCreator +{ + public interface IUrlBuilder + { + Response Build(string downstreamPath, string downstreamScheme, HostAndPort downstreamHostAndPort); + } +} diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 8d0af0bd..8144b42b 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using Ocelot.Configuration; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; @@ -11,17 +12,20 @@ namespace Ocelot.DownstreamUrlCreator.Middleware public class DownstreamUrlCreatorMiddleware : OcelotMiddleware { private readonly RequestDelegate _next; - private readonly IDownstreamUrlPathPlaceholderReplacer _urlReplacer; + private readonly IDownstreamPathPlaceholderReplacer _replacer; private readonly IOcelotLogger _logger; + private readonly IUrlBuilder _urlBuilder; public DownstreamUrlCreatorMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, - IDownstreamUrlPathPlaceholderReplacer urlReplacer, - IRequestScopedDataRepository requestScopedDataRepository) + IDownstreamPathPlaceholderReplacer replacer, + IRequestScopedDataRepository requestScopedDataRepository, + IUrlBuilder urlBuilder) :base(requestScopedDataRepository) { _next = next; - _urlReplacer = urlReplacer; + _replacer = replacer; + _urlBuilder = urlBuilder; _logger = loggerFactory.CreateLogger(); } @@ -29,19 +33,34 @@ namespace Ocelot.DownstreamUrlCreator.Middleware { _logger.LogDebug("started calling downstream url creator middleware"); - var downstreamUrl = _urlReplacer.Replace(DownstreamRoute.ReRoute.DownstreamTemplate, DownstreamRoute.TemplatePlaceholderNameAndValues); + var dsPath = _replacer + .Replace(DownstreamRoute.ReRoute.DownstreamPathTemplate, DownstreamRoute.TemplatePlaceholderNameAndValues); - if (downstreamUrl.IsError) + if (dsPath.IsError) { - _logger.LogDebug("IDownstreamUrlPathPlaceholderReplacer returned an error, setting pipeline error"); + _logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error"); - SetPipelineError(downstreamUrl.Errors); + SetPipelineError(dsPath.Errors); return; } - _logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", downstreamUrl.Data.Value); + var dsScheme = DownstreamRoute.ReRoute.DownstreamScheme; - SetDownstreamUrlForThisRequest(downstreamUrl.Data.Value); + var dsHostAndPort = DownstreamRoute.ReRoute.DownstreamHostAndPort(); + + var dsUrl = _urlBuilder.Build(dsPath.Data.Value, dsScheme, dsHostAndPort); + + if (dsUrl.IsError) + { + _logger.LogDebug("IUrlBuilder returned an error, setting pipeline error"); + + SetPipelineError(dsUrl.Errors); + return; + } + + _logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", dsUrl.Data.Value); + + SetDownstreamUrlForThisRequest(dsUrl.Data.Value); _logger.LogDebug("calling next middleware"); diff --git a/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs new file mode 100644 index 00000000..2124ce3b --- /dev/null +++ b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using Ocelot.Configuration; +using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; +using Ocelot.Errors; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.DownstreamUrlCreator +{ + public class UrlBuilder : IUrlBuilder + { + public Response Build(string downstreamPath, string downstreamScheme, HostAndPort downstreamHostAndPort) + { + if (string.IsNullOrEmpty(downstreamPath)) + { + return new ErrorResponse(new List {new DownstreamPathNullOrEmptyError()}); + } + + if (string.IsNullOrEmpty(downstreamScheme)) + { + return new ErrorResponse(new List { new DownstreamSchemeNullOrEmptyError() }); + } + + if (string.IsNullOrEmpty(downstreamHostAndPort.DownstreamHost)) + { + return new ErrorResponse(new List { new DownstreamHostNullOrEmptyError() }); + } + + var builder = new UriBuilder + { + Host = downstreamHostAndPort.DownstreamHost, + Path = downstreamPath, + Scheme = downstreamScheme + }; + + if (downstreamHostAndPort.DownstreamPort > 0) + { + builder.Port = downstreamHostAndPort.DownstreamPort; + } + + var url = builder.Uri.ToString(); + + return new OkResponse(new DownstreamUrl(url)); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs index 9c19f2f9..9e925631 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs @@ -1,25 +1,25 @@ using System.Collections.Generic; using System.Text; -using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Responses; +using Ocelot.Values; namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer { - public class DownstreamUrlPathPlaceholderReplacer : IDownstreamUrlPathPlaceholderReplacer + public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer { - public Response Replace(string downstreamTemplate, List urlPathPlaceholderNameAndValues) + public Response Replace(DownstreamPathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues) { - var upstreamUrl = new StringBuilder(); + var downstreamPath = new StringBuilder(); - upstreamUrl.Append(downstreamTemplate); + downstreamPath.Append(downstreamPathTemplate.Value); foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues) { - upstreamUrl.Replace(placeholderVariableAndValue.TemplateVariableName, placeholderVariableAndValue.TemplateVariableValue); + downstreamPath.Replace(placeholderVariableAndValue.TemplateVariableName, placeholderVariableAndValue.TemplateVariableValue); } - return new OkResponse(new DownstreamUrl(upstreamUrl.ToString())); + return new OkResponse(new DownstreamPath(downstreamPath.ToString())); } } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs index 164c42ef..72d5d4b6 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs @@ -1,11 +1,12 @@ using System.Collections.Generic; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Responses; +using Ocelot.Values; namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer { - public interface IDownstreamUrlPathPlaceholderReplacer + public interface IDownstreamPathPlaceholderReplacer { - Response Replace(string downstreamTemplate, List urlPathPlaceholderNameAndValues); + Response Replace(DownstreamPathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues); } } \ No newline at end of file diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index f0a336f0..5de770cd 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -4,7 +4,7 @@ { UnauthenticatedError, UnknownError, - DownstreamTemplateAlreadyUsedError, + DownstreampathTemplateAlreadyUsedError, UnableToFindDownstreamRouteError, CannotAddDataError, CannotFindDataError, @@ -18,7 +18,9 @@ UnauthorizedError, ClaimValueNotAuthorisedError, UserDoesNotHaveClaimError, - DownstreamTemplateContainsSchemeError, - DownstreamTemplateContainsHostError + DownstreamPathTemplateContainsSchemeError, + DownstreamPathNullOrEmptyError, + DownstreamSchemeNullOrEmptyError, + DownstreamHostNullOrEmptyError } } diff --git a/src/Ocelot/Values/DownstreamPath.cs b/src/Ocelot/Values/DownstreamPath.cs new file mode 100644 index 00000000..90f2e83e --- /dev/null +++ b/src/Ocelot/Values/DownstreamPath.cs @@ -0,0 +1,12 @@ +namespace Ocelot.Values +{ + public class DownstreamPath + { + public DownstreamPath(string value) + { + Value = value; + } + + public string Value { get; private set; } + } +} diff --git a/src/Ocelot/Values/DownstreamPathTemplate.cs b/src/Ocelot/Values/DownstreamPathTemplate.cs new file mode 100644 index 00000000..a4c720eb --- /dev/null +++ b/src/Ocelot/Values/DownstreamPathTemplate.cs @@ -0,0 +1,12 @@ +namespace Ocelot.Values +{ + public class DownstreamPathTemplate + { + public DownstreamPathTemplate(string value) + { + Value = value; + } + + public string Value { get; private set; } + } +} diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrl.cs b/src/Ocelot/Values/DownstreamUrl.cs similarity index 74% rename from src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrl.cs rename to src/Ocelot/Values/DownstreamUrl.cs index ea90179e..f809c84b 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrl.cs +++ b/src/Ocelot/Values/DownstreamUrl.cs @@ -1,4 +1,4 @@ -namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer +namespace Ocelot.Values { public class DownstreamUrl { @@ -9,4 +9,4 @@ public string Value { get; private set; } } -} +} \ No newline at end of file diff --git a/src/Ocelot/Values/HostAndPort.cs b/src/Ocelot/Values/HostAndPort.cs new file mode 100644 index 00000000..cd336dec --- /dev/null +++ b/src/Ocelot/Values/HostAndPort.cs @@ -0,0 +1,14 @@ +namespace Ocelot.Values +{ + public class HostAndPort + { + public HostAndPort(string downstreamHost, int downstreamPort) + { + DownstreamHost = downstreamHost; + DownstreamPort = downstreamPort; + } + + public string DownstreamHost { get; private set; } + public int DownstreamPort { get; private set; } + } +} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs index 874afe59..8b14f4f1 100644 --- a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs @@ -23,7 +23,11 @@ namespace Ocelot.AcceptanceTests private readonly Steps _steps; private IWebHost _identityServerBuilder; private string _identityServerRootUrl = "http://localhost:51888"; - private string _downstreamServiceRootUrl = "http://localhost:51876/"; + private string _downstreamServicePath = "/"; + private string _downstreamServiceHost = "localhost"; + private int _downstreamServicePort = 51876; + private string _downstreamServiceScheme = "http"; + private string _downstreamServiceUrl = "http://localhost:51876"; public AuthenticationTests() { @@ -39,7 +43,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions @@ -56,7 +63,7 @@ namespace Ocelot.AcceptanceTests }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceRootUrl, 201, string.Empty)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenThePostHasContent("postContent")) @@ -74,7 +81,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions @@ -91,7 +101,7 @@ namespace Ocelot.AcceptanceTests }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Reference)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceRootUrl, 201, string.Empty)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenThePostHasContent("postContent")) @@ -109,7 +119,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions @@ -126,7 +139,7 @@ namespace Ocelot.AcceptanceTests }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceRootUrl, 200, "Hello from Laura")) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura")) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) @@ -146,7 +159,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions @@ -163,7 +179,7 @@ namespace Ocelot.AcceptanceTests }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceRootUrl, 201, string.Empty)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) @@ -183,7 +199,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions @@ -200,7 +219,7 @@ namespace Ocelot.AcceptanceTests }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Reference)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceRootUrl, 201, string.Empty)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) diff --git a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs index 4ceb03f6..1f86c6ff 100644 --- a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs @@ -37,7 +37,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51876/", + DownstreamPathTemplate = "/", + DownstreamPort = 51876, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions @@ -91,7 +94,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51876/", + DownstreamPathTemplate = "/", + DownstreamPort = 51876, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions diff --git a/test/Ocelot.AcceptanceTests/CachingTests.cs b/test/Ocelot.AcceptanceTests/CachingTests.cs index 34f10b7a..e4e628af 100644 --- a/test/Ocelot.AcceptanceTests/CachingTests.cs +++ b/test/Ocelot.AcceptanceTests/CachingTests.cs @@ -31,7 +31,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", FileCacheOptions = new FileCacheOptions @@ -64,7 +67,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", FileCacheOptions = new FileCacheOptions diff --git a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs index 81a12381..81824602 100644 --- a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs @@ -30,7 +30,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get" } @@ -54,7 +57,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false @@ -79,7 +85,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -104,7 +113,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/PRODUCTS/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -129,7 +141,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -154,7 +169,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/PRODUCTS/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true diff --git a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs index 160686d5..08bbd968 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs @@ -51,7 +51,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:52876/", + DownstreamPathTemplate = "/", + DownstreamPort = 52876, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions diff --git a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs index 36583018..04dc25db 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs @@ -51,7 +51,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:57876/", + DownstreamPathTemplate = "/", + DownstreamPort = 57876, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions diff --git a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs index 1a267a98..f6f6de20 100644 --- a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -45,7 +45,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -79,7 +82,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -113,7 +119,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "41879/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -147,7 +156,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -181,7 +193,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -215,7 +230,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } diff --git a/test/Ocelot.AcceptanceTests/RequestIdTests.cs b/test/Ocelot.AcceptanceTests/RequestIdTests.cs index 512736ae..9334786b 100644 --- a/test/Ocelot.AcceptanceTests/RequestIdTests.cs +++ b/test/Ocelot.AcceptanceTests/RequestIdTests.cs @@ -33,7 +33,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", RequestIdKey = _steps.RequestIdKey @@ -58,7 +61,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", RequestIdKey = _steps.RequestIdKey @@ -85,7 +91,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } diff --git a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs index 902de662..81307781 100644 --- a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs +++ b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs @@ -29,7 +29,7 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:53876/", + DownstreamPathTemplate = "http://localhost:53876/", UpstreamTemplate = "/", UpstreamHttpMethod = "Get" } diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 84e00d5b..4f97114f 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -40,7 +40,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -56,6 +59,62 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_return_response_200_when_path_missing_forward_slash_as_first_char() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "api/products", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_when_host_has_trailing_slash() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products", + DownstreamScheme = "http", + DownstreamHost = "localhost/", + DownstreamPort = 51879, + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + [Fact] public void should_not_care_about_no_trailing() { @@ -65,7 +124,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/products", + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/products/", UpstreamHttpMethod = "Get", } @@ -90,7 +152,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/products", + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/products", UpstreamHttpMethod = "Get", } @@ -115,7 +180,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/products", + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } @@ -139,7 +207,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get" } @@ -164,7 +235,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamHost = "localhost", + DownstreamPort = 51879, + DownstreamScheme = "http", UpstreamTemplate = "/", UpstreamHttpMethod = "Post" } @@ -189,8 +263,11 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/newThing", + DownstreamPathTemplate = "/newThing", UpstreamTemplate = "/newThing", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamHttpMethod = "Get", } } diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index 984f851c..f28abefe 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamTemplate":"http://localhost:41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false}],"GlobalConfiguration":{"RequestIdKey":null}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamTemplate":"/","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":41879}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Address":null}}} \ No newline at end of file diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index 7b6b8535..a049d3ea 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -10,7 +10,6 @@ namespace Ocelot.ManualTest var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() .UseStartup() .Build(); diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index 15276d6e..f7e2bb75 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -1,7 +1,10 @@ { "ReRoutes": [ { - "DownstreamTemplate": "http://localhost:52876/", + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHost": "localhost", + "DownstreamPort": 52876, "UpstreamTemplate": "/identityserverexample", "UpstreamHttpMethod": "Get", "AuthenticationOptions": { @@ -38,108 +41,165 @@ "RequestIdKey": "OcRequestId" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts", + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}", + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Get" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}/comments", + "DownstreamPathTemplate": "/posts/{postId}/comments", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/{postId}/comments", "UpstreamHttpMethod": "Get" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/comments", + "DownstreamPathTemplate": "/comments", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/comments", "UpstreamHttpMethod": "Get" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts", + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts", "UpstreamHttpMethod": "Post" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}", + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Put" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}", + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Patch" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}", + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Delete" }, { - "DownstreamTemplate": "http://products20161126090340.azurewebsites.net/api/products", + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/products", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://products20161126090340.azurewebsites.net/api/products/{productId}", + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/products/{productId}", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://products20161126090340.azurewebsites.net/api/products", + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/products", "UpstreamHttpMethod": "Post", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://products20161126090340.azurewebsites.net/api/products/{productId}", + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/products/{productId}", "UpstreamHttpMethod": "Put", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://products20161126090340.azurewebsites.net/api/products/{productId}", + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/products/{productId}", "UpstreamHttpMethod": "Delete", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://customers20161126090811.azurewebsites.net/api/customers", + "DownstreamPathTemplate": "/api/customers", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/customers", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://customers20161126090811.azurewebsites.net/api/customers/{customerId}", + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://customers20161126090811.azurewebsites.net/api/customers", + "DownstreamPathTemplate": "/api/customers", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/customers", "UpstreamHttpMethod": "Post", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://customers20161126090811.azurewebsites.net/api/customers/{customerId}", + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Put", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://customers20161126090811.azurewebsites.net/api/customers/{customerId}", + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Delete", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts", + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } diff --git a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs index e3cccdc0..8822e6b2 100644 --- a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs @@ -67,7 +67,7 @@ namespace Ocelot.UnitTests.Claims { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("any old string") + .WithDownstreamPathTemplate("any old string") .WithClaimsToClaims(new List { new ClaimToThing("sub", "UserType", "|", 0) diff --git a/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs b/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs index 174ea8bc..8a3e24f9 100644 --- a/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs @@ -10,8 +10,8 @@ namespace Ocelot.UnitTests.Configuration { public class ConfigurationValidationTests { - private FileConfiguration _fileConfiguration; private readonly IConfigurationValidator _configurationValidator; + private FileConfiguration _fileConfiguration; private Response _result; public ConfigurationValidationTests() @@ -22,32 +22,13 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void configuration_is_invalid_if_scheme_in_downstream_template() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() + this.Given(x => x.GivenAConfiguration(new FileConfiguration { ReRoutes = new List { new FileReRoute { - DownstreamTemplate = "http://www.bbc.co.uk/api/products/{productId}", - UpstreamTemplate = "http://asdf.com" - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_if_host_in_downstream_template() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamTemplate = "www.bbc.co.uk/api/products/{productId}", + DownstreamPathTemplate = "http://www.bbc.co.uk/api/products/{productId}", UpstreamTemplate = "http://asdf.com" } } @@ -60,13 +41,13 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void configuration_is_valid_with_one_reroute() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() + this.Given(x => x.GivenAConfiguration(new FileConfiguration { ReRoutes = new List { new FileReRoute { - DownstreamTemplate = "/api/products/", + DownstreamPathTemplate = "/api/products/", UpstreamTemplate = "http://asdf.com" } } @@ -79,13 +60,13 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void configuration_is_valid_with_valid_authentication_provider() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() + this.Given(x => x.GivenAConfiguration(new FileConfiguration { ReRoutes = new List { new FileReRoute { - DownstreamTemplate = "/api/products/", + DownstreamPathTemplate = "/api/products/", UpstreamTemplate = "http://asdf.com", AuthenticationOptions = new FileAuthenticationOptions { @@ -102,13 +83,13 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void configuration_is_invalid_with_invalid_authentication_provider() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() + this.Given(x => x.GivenAConfiguration(new FileConfiguration { ReRoutes = new List { new FileReRoute { - DownstreamTemplate = "/api/products/", + DownstreamPathTemplate = "/api/products/", UpstreamTemplate = "http://asdf.com", AuthenticationOptions = new FileAuthenticationOptions { @@ -126,25 +107,25 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void configuration_is_not_valid_with_duplicate_reroutes() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() + this.Given(x => x.GivenAConfiguration(new FileConfiguration { ReRoutes = new List { new FileReRoute { - DownstreamTemplate = "/api/products/", + DownstreamPathTemplate = "/api/products/", UpstreamTemplate = "http://asdf.com" }, new FileReRoute { - DownstreamTemplate = "http://www.bbc.co.uk", + DownstreamPathTemplate = "http://www.bbc.co.uk", UpstreamTemplate = "http://asdf.com" } } })) .When(x => x.WhenIValidateTheConfiguration()) .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorIs()) + .And(x => x.ThenTheErrorIs()) .BDDfy(); } @@ -173,4 +154,4 @@ namespace Ocelot.UnitTests.Configuration _result.Data.Errors[0].ShouldBeOfType(); } } -} +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 4207a333..fc80a478 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -46,7 +46,7 @@ namespace Ocelot.UnitTests.Configuration { DownstreamHost = "127.0.0.1", UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } }, @@ -57,7 +57,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamHost("127.0.0.1") - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -76,7 +76,7 @@ namespace Ocelot.UnitTests.Configuration { DownstreamScheme = "https", UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } }, @@ -87,7 +87,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamScheme("https") - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -106,7 +106,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false, ServiceName = "ProductService" @@ -126,7 +126,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -149,7 +149,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false, } @@ -160,7 +160,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -182,7 +182,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false } @@ -193,7 +193,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -212,7 +212,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get" } } @@ -222,7 +222,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -241,7 +241,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -252,7 +252,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") @@ -271,7 +271,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -286,7 +286,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") @@ -306,7 +306,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -317,7 +317,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") @@ -332,7 +332,7 @@ namespace Ocelot.UnitTests.Configuration var expected = new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") @@ -355,7 +355,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, AuthenticationOptions = new FileAuthenticationOptions @@ -395,7 +395,7 @@ namespace Ocelot.UnitTests.Configuration var expected = new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") @@ -414,7 +414,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, AuthenticationOptions = new FileAuthenticationOptions @@ -446,7 +446,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}/variants/{variantId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -457,7 +457,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}/variants/{variantId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$") @@ -476,7 +476,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}/variants/{variantId}/", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -487,7 +487,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}/variants/{variantId}/") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$") @@ -506,7 +506,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/", - DownstreamTemplate = "/api/products/", + DownstreamPathTemplate = "/api/products/", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -517,7 +517,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/api/products/") + .WithDownstreamPathTemplate("/api/products/") .WithUpstreamTemplate("/") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/$") @@ -553,7 +553,7 @@ namespace Ocelot.UnitTests.Configuration var result = _config.Data.ReRoutes[i]; var expected = expectedReRoutes[i]; - result.DownstreamTemplate.ShouldBe(expected.DownstreamTemplate); + result.DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate.Value); result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); result.UpstreamTemplate.ShouldBe(expected.UpstreamTemplate); result.UpstreamTemplatePattern.ShouldBe(expected.UpstreamTemplatePattern); diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index 8f7af24e..ec46f914 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -44,7 +44,7 @@ namespace Ocelot.UnitTests.Configuration private void ThenTheConfigurationIsReturned() { - _getResult.Data.ReRoutes[0].DownstreamTemplate.ShouldBe("initial"); + _getResult.Data.ReRoutes[0].DownstreamPathTemplate.Value.ShouldBe("initial"); } private void WhenIGetTheConfiguration() @@ -75,16 +75,16 @@ namespace Ocelot.UnitTests.Configuration class FakeConfig : IOcelotConfiguration { - private readonly string _downstreamTemplate; + private readonly string _downstreamTemplatePath; - public FakeConfig(string downstreamTemplate) + public FakeConfig(string downstreamTemplatePath) { - _downstreamTemplate = downstreamTemplate; + _downstreamTemplatePath = downstreamTemplatePath; } public List ReRoutes => new List { - new ReRouteBuilder().WithDownstreamTemplate(_downstreamTemplate).Build() + new ReRouteBuilder().WithDownstreamPathTemplate(_downstreamTemplatePath).Build() }; } } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 22dfea80..0d5a6d48 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -61,7 +61,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder [Fact] public void should_call_scoped_data_repository_correctly() { - this.Given(x => x.GivenTheDownStreamRouteFinderReturns(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("any old string").Build()))) + this.Given(x => x.GivenTheDownStreamRouteFinderReturns(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("any old string").Build()))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index bb390d32..c0afca42 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -44,7 +44,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPath") + .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamTemplate("someUpstreamPath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("someUpstreamPath") @@ -57,7 +57,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .Then( x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPath") + .WithDownstreamPathTemplate("someDownstreamPath") .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -75,13 +75,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPath") + .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamTemplate("someUpstreamPath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("") .Build(), new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPathForAPost") + .WithDownstreamPathTemplate("someDownstreamPathForAPost") .WithUpstreamTemplate("someUpstreamPath") .WithUpstreamHttpMethod("Post") .WithUpstreamTemplatePattern("") @@ -94,7 +94,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .Then( x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPathForAPost") + .WithDownstreamPathTemplate("someDownstreamPathForAPost") .Build() ))) .BDDfy(); @@ -107,7 +107,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamTemplate("somPath") + .WithDownstreamPathTemplate("somPath") .WithUpstreamTemplate("somePath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("somePath") @@ -174,7 +174,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void ThenTheFollowingIsReturned(DownstreamRoute expected) { - _result.Data.ReRoute.DownstreamTemplate.ShouldBe(expected.ReRoute.DownstreamTemplate); + _result.Data.ReRoute.DownstreamPathTemplate.Value.ShouldBe(expected.ReRoute.DownstreamPathTemplate.Value); for (int i = 0; i < _result.Data.TemplatePlaceholderNameAndValues.Count; i++) { diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index 98bc5f0b..5581a32e 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -7,15 +7,18 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Moq; +using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.DownstreamUrlCreator; using Ocelot.DownstreamUrlCreator.Middleware; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Responses; +using Ocelot.Values; using TestStack.BDDfy; using Xunit; @@ -23,21 +26,23 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator { public class DownstreamUrlCreatorMiddlewareTests : IDisposable { - private readonly Mock _downstreamUrlTemplateVariableReplacer; + private readonly Mock _downstreamUrlTemplateVariableReplacer; private readonly Mock _scopedRepository; + private readonly Mock _urlBuilder; private readonly string _url; private readonly TestServer _server; private readonly HttpClient _client; private Response _downstreamRoute; private HttpResponseMessage _result; + private OkResponse _downstreamPath; private OkResponse _downstreamUrl; public DownstreamUrlCreatorMiddlewareTests() { _url = "http://localhost:51879"; - _downstreamUrlTemplateVariableReplacer = new Mock(); + _downstreamUrlTemplateVariableReplacer = new Mock(); _scopedRepository = new Mock(); - + _urlBuilder = new Mock(); var builder = new WebHostBuilder() .ConfigureServices(x => { @@ -45,6 +50,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator x.AddLogging(); x.AddSingleton(_downstreamUrlTemplateVariableReplacer.Object); x.AddSingleton(_scopedRepository.Object); + x.AddSingleton(_urlBuilder.Object); }) .UseUrls(_url) .UseKestrel() @@ -61,21 +67,30 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator } [Fact] - public void should_call_scoped_data_repository_correctly() + public void should_call_dependencies_correctly() { - this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("any old string").Build()))) - .And(x => x.TheUrlReplacerReturns("any old string")) + this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("any old string").Build()))) + .And(x => x.TheUrlReplacerReturns("/api/products/1")) + .And(x => x.TheUrlBuilderReturns("http://www.bbc.co.uk/api/products/1")) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); } + private void TheUrlBuilderReturns(string dsUrl) + { + _downstreamUrl = new OkResponse(new DownstreamUrl(dsUrl)); + _urlBuilder + .Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(_downstreamUrl); + } + private void TheUrlReplacerReturns(string downstreamUrl) { - _downstreamUrl = new OkResponse(new DownstreamUrl(downstreamUrl)); + _downstreamPath = new OkResponse(new DownstreamPath(downstreamUrl)); _downstreamUrlTemplateVariableReplacer - .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) - .Returns(_downstreamUrl); + .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) + .Returns(_downstreamPath); } private void ThenTheScopedDataRepositoryIsCalledCorrectly() diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs new file mode 100644 index 00000000..7e512798 --- /dev/null +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs @@ -0,0 +1,124 @@ +using System; +using Ocelot.Configuration; +using Ocelot.DownstreamUrlCreator; +using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; +using Ocelot.Responses; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.DownstreamUrlCreator +{ + public class UrlBuilderTests + { + private readonly IUrlBuilder _urlBuilder; + private string _dsPath; + private string _dsScheme; + private string _dsHost; + private int _dsPort; + + private Response _result; + + public UrlBuilderTests() + { + _urlBuilder = new UrlBuilder(); + } + + [Fact] + public void should_return_error_when_downstream_path_is_null() + { + this.Given(x => x.GivenADownstreamPath(null)) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenThereIsAnErrorOfType()) + .BDDfy(); + } + + [Fact] + public void should_return_error_when_downstream_scheme_is_null() + { + this.Given(x => x.GivenADownstreamScheme(null)) + .And(x => x.GivenADownstreamPath("test")) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenThereIsAnErrorOfType()) + .BDDfy(); + } + + [Fact] + public void should_return_error_when_downstream_host_is_null() + { + this.Given(x => x.GivenADownstreamScheme(null)) + .And(x => x.GivenADownstreamPath("test")) + .And(x => x.GivenADownstreamScheme("test")) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenThereIsAnErrorOfType()) + .BDDfy(); + } + + [Fact] + public void should_not_use_port_if_zero() + { + this.Given(x => x.GivenADownstreamPath("/api/products/1")) + .And(x => x.GivenADownstreamScheme("http")) + .And(x => x.GivenADownstreamHost("127.0.0.1")) + .And(x => x.GivenADownstreamPort(0)) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenTheUrlIsReturned("http://127.0.0.1/api/products/1")) + .And(x => x.ThenTheUrlIsWellFormed()) + .BDDfy(); + } + + [Fact] + public void should_build_well_formed_uri() + { + this.Given(x => x.GivenADownstreamPath("/api/products/1")) + .And(x => x.GivenADownstreamScheme("http")) + .And(x => x.GivenADownstreamHost("127.0.0.1")) + .And(x => x.GivenADownstreamPort(5000)) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenTheUrlIsReturned("http://127.0.0.1:5000/api/products/1")) + .And(x => x.ThenTheUrlIsWellFormed()) + .BDDfy(); + } + + private void ThenThereIsAnErrorOfType() + { + _result.Errors[0].ShouldBeOfType(); + } + + private void GivenADownstreamPath(string dsPath) + { + _dsPath = dsPath; + } + + private void GivenADownstreamScheme(string dsScheme) + { + _dsScheme = dsScheme; + } + + private void GivenADownstreamHost(string dsHost) + { + _dsHost = dsHost; + } + + private void GivenADownstreamPort(int dsPort) + { + _dsPort = dsPort; + } + + private void WhenIBuildTheUrl() + { + _result = _urlBuilder.Build(_dsPath, _dsScheme, new HostAndPort(_dsHost, _dsPort)); + } + + private void ThenTheUrlIsReturned(string expected) + { + _result.Data.Value.ShouldBe(expected); + } + + private void ThenTheUrlIsWellFormed() + { + Uri.IsWellFormedUriString(_result.Data.Value, UriKind.Absolute).ShouldBeTrue(); + } + } +} diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs index b1ad369b..a7a5a89b 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs @@ -4,6 +4,7 @@ using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Responses; +using Ocelot.Values; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -13,12 +14,12 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer public class UpstreamUrlPathTemplateVariableReplacerTests { private DownstreamRoute _downstreamRoute; - private Response _result; - private readonly IDownstreamUrlPathPlaceholderReplacer _downstreamUrlPathReplacer; + private Response _result; + private readonly IDownstreamPathPlaceholderReplacer _downstreamPathReplacer; public UpstreamUrlPathTemplateVariableReplacerTests() { - _downstreamUrlPathReplacer = new DownstreamUrlPathPlaceholderReplacer(); + _downstreamPathReplacer = new DownstreamTemplatePathPlaceholderReplacer(); } [Fact] @@ -33,7 +34,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_no_template_variables_with_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("/").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("/")) .BDDfy(); @@ -42,7 +43,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_no_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("api").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("api").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api")) .BDDfy(); @@ -51,7 +52,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_one_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("api/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("api/").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/")) .BDDfy(); @@ -60,7 +61,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_multiple_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("api/product/products/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("api/product/products/").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/product/products/")) .BDDfy(); @@ -74,7 +75,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new UrlPathPlaceholderNameAndValue("{productId}", "1") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamTemplate("productservice/products/{productId}/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/")) .BDDfy(); @@ -88,7 +89,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new UrlPathPlaceholderNameAndValue("{productId}", "1") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamTemplate("productservice/products/{productId}/variants").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/variants").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants")) .BDDfy(); @@ -103,7 +104,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new UrlPathPlaceholderNameAndValue("{variantId}", "12") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamTemplate("productservice/products/{productId}/variants/{variantId}").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/variants/{variantId}").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants/12")) .BDDfy(); @@ -119,7 +120,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new UrlPathPlaceholderNameAndValue("{categoryId}", "34") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/category/34/products/1/variants/12")) .BDDfy(); @@ -132,7 +133,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer private void WhenIReplaceTheTemplateVariables() { - _result = _downstreamUrlPathReplacer.Replace(_downstreamRoute.ReRoute.DownstreamTemplate, _downstreamRoute.TemplatePlaceholderNameAndValues); + _result = _downstreamPathReplacer.Replace(_downstreamRoute.ReRoute.DownstreamPathTemplate, _downstreamRoute.TemplatePlaceholderNameAndValues); } private void ThenTheDownstreamUrlPathIsReturned(string expected) diff --git a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs index b85802af..3516d26b 100644 --- a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs @@ -67,7 +67,7 @@ namespace Ocelot.UnitTests.Headers { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("any old string") + .WithDownstreamPathTemplate("any old string") .WithClaimsToHeaders(new List { new ClaimToThing("UserId", "Subject", "", 0) diff --git a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs index e4c7375e..39b32937 100644 --- a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs @@ -65,7 +65,7 @@ namespace Ocelot.UnitTests.QueryStrings { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("any old string") + .WithDownstreamPathTemplate("any old string") .WithClaimsToQueries(new List { new ClaimToThing("UserId", "Subject", "", 0) diff --git a/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs b/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs index 8c023783..543613a8 100644 --- a/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs @@ -71,7 +71,7 @@ namespace Ocelot.UnitTests.RequestId { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("any old string") + .WithDownstreamPathTemplate("any old string") .WithRequestIdKey("LSRequestId").Build()); var requestId = Guid.NewGuid().ToString(); @@ -88,7 +88,7 @@ namespace Ocelot.UnitTests.RequestId { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("any old string") + .WithDownstreamPathTemplate("any old string") .WithRequestIdKey("LSRequestId").Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) From d7ad6df582b48726870eeb80b2aef5c88f7b8860 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 21 Jan 2017 10:07:06 +0000 Subject: [PATCH 008/113] removed rc1 status as all dependencies out of pre --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 69ddc117..83110722 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,6 +7,6 @@ build_script: test_script: - run-tests.bat after_test: -- push-to-nuget.bat %appveyor_build_version%-rc1 %nugetApiKey% +- push-to-nuget.bat %appveyor_build_version% %nugetApiKey% cache: - '%USERPROFILE%\.nuget\packages' \ No newline at end of file From 6bf2d4677cfa2ccd44f0b72ea4df9de11b06204f Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 22 Jan 2017 20:22:04 +0000 Subject: [PATCH 009/113] started adding loadbalancers --- push-to-nuget.bat | 2 +- .../Ocelot.AcceptanceTests/configuration.json | 2 +- test/Ocelot.UnitTests/RoundRobinTests.cs | 81 +++++++++++++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 test/Ocelot.UnitTests/RoundRobinTests.cs diff --git a/push-to-nuget.bat b/push-to-nuget.bat index 7200d09c..73d3bf7e 100644 --- a/push-to-nuget.bat +++ b/push-to-nuget.bat @@ -4,7 +4,7 @@ echo Packing Ocelot Version %1 nuget pack .\Ocelot.nuspec -version %1 echo Publishing Ocelot - nuget push Ocelot.%1.nupkg -ApiKey %2 -Source https://www.nuget.org/api/v2/package +nuget push Ocelot.%1.nupkg -ApiKey %2 -Source https://www.nuget.org/api/v2/package diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index 984f851c..f28abefe 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamTemplate":"http://localhost:41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false}],"GlobalConfiguration":{"RequestIdKey":null}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamTemplate":"/","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":41879}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Address":null}}} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/RoundRobinTests.cs b/test/Ocelot.UnitTests/RoundRobinTests.cs new file mode 100644 index 00000000..82689b62 --- /dev/null +++ b/test/Ocelot.UnitTests/RoundRobinTests.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.Diagnostics; +using Ocelot.Values; +using Shouldly; +using Xunit; + +namespace Ocelot.UnitTests +{ + public class RoundRobinTests + { + private readonly RoundRobin _roundRobin; + private readonly List _hostAndPorts; + + public RoundRobinTests() + { + _hostAndPorts = new List + { + new HostAndPort("127.0.0.1", 5000), + new HostAndPort("127.0.0.1", 5001), + new HostAndPort("127.0.0.1", 5001) + }; + + _roundRobin = new RoundRobin(_hostAndPorts); + } + + [Fact] + public void should_get_next_address() + { + var address = _roundRobin.Next(); + address.ShouldBe(_hostAndPorts[0]); + address = _roundRobin.Next(); + address.ShouldBe(_hostAndPorts[1]); + address = _roundRobin.Next(); + address.ShouldBe(_hostAndPorts[2]); + } + + [Fact] + public void should_go_back_to_first_address_after_finished_last() + { + var stopWatch = Stopwatch.StartNew(); + + while (stopWatch.ElapsedMilliseconds < 1000) + { + var address = _roundRobin.Next(); + address.ShouldBe(_hostAndPorts[0]); + address = _roundRobin.Next(); + address.ShouldBe(_hostAndPorts[1]); + address = _roundRobin.Next(); + address.ShouldBe(_hostAndPorts[2]); + } + } + } + + public interface ILoadBalancer + { + HostAndPort Next(); + } + + public class RoundRobin : ILoadBalancer + { + private readonly List _hostAndPorts; + private int _last; + + public RoundRobin(List hostAndPorts) + { + _hostAndPorts = hostAndPorts; + } + + public HostAndPort Next() + { + if (_last >= _hostAndPorts.Count) + { + _last = 0; + } + + var next = _hostAndPorts[_last]; + _last++; + return next; + } + } +} From cdad892a9606191fb3cd68a90a01665dde00a624 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 23 Jan 2017 12:09:54 +0000 Subject: [PATCH 010/113] hacking away --- test/Ocelot.UnitTests/ServiceRegistryTests.cs | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 test/Ocelot.UnitTests/ServiceRegistryTests.cs diff --git a/test/Ocelot.UnitTests/ServiceRegistryTests.cs b/test/Ocelot.UnitTests/ServiceRegistryTests.cs new file mode 100644 index 00000000..8866f1ae --- /dev/null +++ b/test/Ocelot.UnitTests/ServiceRegistryTests.cs @@ -0,0 +1,143 @@ +using System.Collections.Generic; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests +{ + public class ServiceRegistryTests + { + private Service _service; + private List _services; + private ServiceRegistry _serviceRegistry; + private ServiceRepository _serviceRepository; + + public ServiceRegistryTests() + { + _serviceRepository = new ServiceRepository(); + _serviceRegistry = new ServiceRegistry(_serviceRepository); + } + + [Fact] + public void should_register_service() + { + this.Given(x => x.GivenAServiceToRegister("product", "localhost:5000")) + .When(x => x.WhenIRegisterTheService()) + .Then(x => x.ThenTheServiceIsRegistered()) + .BDDfy(); + } + + public void should_lookup_service() + { + this.Given(x => x.GivenAServiceIsRegistered("product", "localhost:600")) + .When(x => x.WhenILookupTheService("product")) + .Then(x => x.ThenTheServiceDetailsAreReturned()) + .BDDfy(); + } + + private void ThenTheServiceDetailsAreReturned() + { + _services[0].Address.ShouldBe(_service.Address); + _services[0].Name.ShouldBe(_service.Name); + } + + private void WhenILookupTheService(string name) + { + _services = _serviceRegistry.Lookup(name); + } + + private void GivenAServiceIsRegistered(string name, string address) + { + _service = new Service(name, address); + _serviceRepository.Set(_service); + } + + private void GivenAServiceToRegister(string name, string address) + { + _service = new Service(name, address); + } + + private void WhenIRegisterTheService() + { + _serviceRegistry.Register(_service); + } + + private void ThenTheServiceIsRegistered() + { + var serviceNameAndAddress = _serviceRepository.Get(_service.Name); + serviceNameAndAddress[0].Address.ShouldBe(_service.Address); + serviceNameAndAddress[0].Name.ShouldBe(_service.Name); + } + } + + public interface IServiceRegistry + { + void Register(Service serviceNameAndAddress); + List Lookup(string name); + } + + public class ServiceRegistry : IServiceRegistry + { + private readonly IServiceRepository _repository; + public ServiceRegistry(IServiceRepository repository) + { + _repository = repository; + } + public void Register(Service serviceNameAndAddress) + { + _repository.Set(serviceNameAndAddress); + } + + public List Lookup(string name) + { + return _repository.Get(name); + } + } + + public class Service + { + public Service(string name, string address) + { + Name = name; + Address = address; + } + public string Name {get; private set;} + public string Address {get; private set;} + } + + public interface IServiceRepository + { + List Get(string serviceName); + void Set(Service serviceNameAndAddress); + } + + public class ServiceRepository : IServiceRepository + { + private Dictionary> _registeredServices; + + public ServiceRepository() + { + _registeredServices = new Dictionary>(); + } + + public List Get(string serviceName) + { + return _registeredServices[serviceName]; + } + + public void Set(Service serviceNameAndAddress) + { + List services; + if(_registeredServices.TryGetValue(serviceNameAndAddress.Name, out services)) + { + services.Add(serviceNameAndAddress); + _registeredServices[serviceNameAndAddress.Name] = services; + } + else + { + _registeredServices[serviceNameAndAddress.Name] = new List(){ serviceNameAndAddress }; + } + + } + } +} \ No newline at end of file From c3a47f66c88f9af98685becea2b70b4092fa921b Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 23 Jan 2017 12:13:24 +0000 Subject: [PATCH 011/113] merge --- .DS_Store | Bin 0 -> 6148 bytes Ocelot.sln | 2 + README.md | 11 +- appveyor.yml | 2 +- configuration-explanation.txt | 19 ++- .../Configuration/Builder/ReRouteBuilder.cs | 18 ++- .../Creator/FileOcelotConfigurationCreator.cs | 11 +- src/Ocelot/Configuration/File/FileReRoute.cs | 3 +- src/Ocelot/Configuration/ReRoute.cs | 13 +- .../DownstreamPathTemplateAlreadyUsedError.cs | 11 ++ ...wnstreamPathTemplateContainsSchemeError.cs | 12 ++ .../DownstreamTemplateAlreadyUsedError.cs | 11 -- .../DownstreamTemplateContainsHostError.cs | 12 -- .../DownstreamTemplateContainsSchemeError.cs | 12 -- .../Validator/FileConfigurationValidator.cs | 16 +-- .../ServiceCollectionExtensions.cs | 4 +- .../DownstreamRouteFinderMiddleware.cs | 2 +- .../DownstreamHostNullOrEmptyError.cs | 12 ++ .../DownstreamPathNullOrEmptyError.cs | 12 ++ .../DownstreamSchemeNullOrEmptyError.cs | 12 ++ .../DownstreamUrlCreator/IUrlBuilder.cs | 12 ++ .../DownstreamUrlCreatorMiddleware.cs | 39 ++++-- src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs | 47 +++++++ .../DownstreamUrlTemplateVariableReplacer.cs | 14 +- ...wnstreamUrlPathTemplateVariableReplacer.cs | 5 +- src/Ocelot/Errors/OcelotErrorCode.cs | 8 +- src/Ocelot/Values/DownstreamPath.cs | 12 ++ src/Ocelot/Values/DownstreamPathTemplate.cs | 12 ++ .../DownstreamUrl.cs | 4 +- src/Ocelot/Values/HostAndPort.cs | 14 ++ test/.DS_Store | Bin 0 -> 6148 bytes .../AuthenticationTests.cs | 41 ++++-- .../AuthorisationTests.cs | 10 +- test/Ocelot.AcceptanceTests/CachingTests.cs | 10 +- .../CaseSensitiveRoutingTests.cs | 30 ++++- .../ClaimsToHeadersForwardingTests.cs | 5 +- .../ClaimsToQueryStringForwardingTests.cs | 5 +- .../CustomMiddlewareTests.cs | 30 ++++- test/Ocelot.AcceptanceTests/RequestIdTests.cs | 15 ++- .../ReturnsErrorTests.cs | 2 +- test/Ocelot.AcceptanceTests/RoutingTests.cs | 91 ++++++++++++- test/Ocelot.ManualTest/Program.cs | 1 - test/Ocelot.ManualTest/configuration.json | 100 +++++++++++--- .../Claims/ClaimsBuilderMiddlewareTests.cs | 2 +- .../ConfigurationValidationTests.cs | 47 ++----- .../FileConfigurationCreatorTests.cs | 58 ++++---- .../InMemoryConfigurationRepositoryTests.cs | 10 +- .../DownstreamRouteFinderMiddlewareTests.cs | 2 +- .../DownstreamRouteFinderTests.cs | 14 +- .../DownstreamUrlCreatorMiddlewareTests.cs | 33 +++-- .../DownstreamUrlCreator/UrlBuilderTests.cs | 124 ++++++++++++++++++ ...eamUrlPathTemplateVariableReplacerTests.cs | 25 ++-- ...ttpRequestHeadersBuilderMiddlewareTests.cs | 2 +- .../QueryStringBuilderMiddlewareTests.cs | 2 +- .../RequestId/RequestIdMiddlewareTests.cs | 4 +- 55 files changed, 767 insertions(+), 258 deletions(-) create mode 100644 .DS_Store create mode 100644 src/Ocelot/Configuration/Validator/DownstreamPathTemplateAlreadyUsedError.cs create mode 100644 src/Ocelot/Configuration/Validator/DownstreamPathTemplateContainsSchemeError.cs delete mode 100644 src/Ocelot/Configuration/Validator/DownstreamTemplateAlreadyUsedError.cs delete mode 100644 src/Ocelot/Configuration/Validator/DownstreamTemplateContainsHostError.cs delete mode 100644 src/Ocelot/Configuration/Validator/DownstreamTemplateContainsSchemeError.cs create mode 100644 src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs create mode 100644 src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs create mode 100644 src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs create mode 100644 src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs create mode 100644 src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs create mode 100644 src/Ocelot/Values/DownstreamPath.cs create mode 100644 src/Ocelot/Values/DownstreamPathTemplate.cs rename src/Ocelot/{DownstreamUrlCreator/UrlTemplateReplacer => Values}/DownstreamUrl.cs (74%) create mode 100644 src/Ocelot/Values/HostAndPort.cs create mode 100644 test/.DS_Store create mode 100644 test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a038c8775e5005f62d3b7c1adf4590d7305cf4fe GIT binary patch literal 6148 zcmeHKOHRWu5S^h}6tU@&rLWK%L{&IJE`XGvvS^#Cq+RnSr=s=fB0APXTBp6FA zAwI#dG>nSSKv-RY>dIDPu)1SCm|bZY71f>CiVwET-^B~3?1&$dIdQ4zy))np3>lc{ zbSC%z13sB-kw0|tnKR%F{4)l)sOoBgP1)T#+Me9C0qqe@MC_srAQ%sR0x*$t(l9DY6j@L1K>ra)gm~u+oPmKacPc|U literal 0 HcmV?d00001 diff --git a/Ocelot.sln b/Ocelot.sln index 5ebd0660..0165beb0 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -17,8 +17,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Ocelot.nuspec = Ocelot.nuspec push-to-nuget.bat = push-to-nuget.bat README.md = README.md + run-acceptance-tests.bat = run-acceptance-tests.bat run-benchmarks.bat = run-benchmarks.bat run-tests.bat = run-tests.bat + run-unit-tests.bat = run-unit-tests.bat EndProjectSection EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot", "src\Ocelot\Ocelot.xproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}" diff --git a/README.md b/README.md index dd5f1043..249a99a3 100644 --- a/README.md +++ b/README.md @@ -136,17 +136,20 @@ In order to set up a ReRoute you need to add one to the json array called ReRout the following. { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}", + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamPort": 80, + "DownstreamHost" "localhost" "UpstreamTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Put" } -The DownstreamTemplate is the URL that this request will be forwarded to. +The DownstreamPathTemplate,Scheme, Port and Host make the URL that this request will be forwarded to. The UpstreamTemplate is the URL that Ocelot will use to identity which -DownstreamTemplate to use for a given request. Finally the UpstreamHttpMethod is used so +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 DownstreamTemplate and UpstreamTemplate. If it is +The placeholder needs to be in both the DownstreamPathTemplate and UpstreamTemplate. If it is Ocelot will attempt to replace the placeholder with the correct variable value from the Upstream URL when the request comes in. diff --git a/appveyor.yml b/appveyor.yml index 69ddc117..83110722 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,6 +7,6 @@ build_script: test_script: - run-tests.bat after_test: -- push-to-nuget.bat %appveyor_build_version%-rc1 %nugetApiKey% +- push-to-nuget.bat %appveyor_build_version% %nugetApiKey% cache: - '%USERPROFILE%\.nuget\packages' \ No newline at end of file diff --git a/configuration-explanation.txt b/configuration-explanation.txt index 898be89f..ad020469 100644 --- a/configuration-explanation.txt +++ b/configuration-explanation.txt @@ -1,11 +1,20 @@ { "ReRoutes": [ { - # The url we are forwarding the request to, ocelot will not add a trailing slash - "DownstreamTemplate": "http://somehost.com/identityserverexample", - # The path we are listening on for this re route, Ocelot will add a trailing slash to - # this property. Then when a request is made Ocelot makes sure a trailing slash is added - # to that so everything matches + # The downstream path we are forwarding the request to, ocelot will not add a trailing slash. + # Ocelot replaces any placeholders {etc} with matched values from the incoming request. + "DownstreamPathTemplate": "/identityserverexample/{someid}/something", + # The scheme you want Ocelot to use when making the downstream request + "DownstreamScheme": "https", + # The port you want Ocelot to use when making the downstream request, will default to + # scheme if nothing set + "DownstreamPort": 80, + # The host address of the downstream service, should not have a trailing slash or scheme + # if there is a trailing slash Ocelot will remove it. + "DownstreamHost" "localhost" + # The path template we are listening on for this re route, Ocelot will add a trailing + # slash to this property. Then when a request is made Ocelot makes sure a trailing + # slash is added, so everything matches "UpstreamTemplate": "/identityserverexample", # The method we are listening for on this re route "UpstreamHttpMethod": "Get", diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 3f5ef40b..1e06a440 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using Ocelot.Values; namespace Ocelot.Configuration.Builder { public class ReRouteBuilder { - private string _downstreamTemplate; + private string _downstreamPathTemplate; private string _upstreamTemplate; private string _upstreamTemplatePattern; private string _upstreamHttpMethod; @@ -30,6 +31,7 @@ namespace Ocelot.Configuration.Builder private string _serviceDiscoveryAddress; private string _downstreamScheme; private string _downstreamHost; + private int _dsPort; public ReRouteBuilder() { @@ -72,9 +74,9 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithDownstreamTemplate(string input) + public ReRouteBuilder WithDownstreamPathTemplate(string input) { - _downstreamTemplate = input; + _downstreamPathTemplate = input; return this; } @@ -184,11 +186,17 @@ namespace Ocelot.Configuration.Builder return this; } + public ReRouteBuilder WithDownstreamPort(int port) + { + _dsPort = port; + return this; + } + public ReRoute Build() { - Func downstreamHostFunc = () => { return _downstreamHost; }; + Func downstreamHostFunc = () => new HostAndPort(_downstreamHost, _dsPort); - return new ReRoute(_downstreamTemplate, _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, + return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _serviceName, diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index c71aba23..8884f0d9 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -8,6 +8,7 @@ using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; using Ocelot.Responses; using Ocelot.Utilities; +using Ocelot.Values; namespace Ocelot.Configuration.Creator { @@ -96,7 +97,7 @@ namespace Ocelot.Configuration.Creator && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); - Func downstreamHostFunc = () => { return reRoute.DownstreamHost; }; + Func downstreamHostAndPortFunc = () => new HostAndPort(reRoute.DownstreamHost.Trim('/'), reRoute.DownstreamPort); if (isAuthenticated) { @@ -109,22 +110,22 @@ namespace Ocelot.Configuration.Creator var claimsToClaims = GetAddThingsToRequest(reRoute.AddClaimsToRequest); var claimsToQueries = GetAddThingsToRequest(reRoute.AddQueriesToRequest); - return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, + return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, authOptionsForRoute, claimsToHeaders, claimsToClaims, reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostFunc, reRoute.DownstreamScheme); + globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme); } - return new ReRoute(reRoute.DownstreamTemplate, reRoute.UpstreamTemplate, + return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate, reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, null, new List(), new List(), reRoute.RouteClaimsRequirement, isAuthorised, new List(), requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostFunc, reRoute.DownstreamScheme); + globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme); } private string BuildUpstreamTemplate(FileReRoute reRoute) diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 3afa03ce..a653224a 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -14,7 +14,7 @@ namespace Ocelot.Configuration.File FileCacheOptions = new FileCacheOptions(); } - public string DownstreamTemplate { get; set; } + public string DownstreamPathTemplate { get; set; } public string UpstreamTemplate { get; set; } public string UpstreamHttpMethod { get; set; } public FileAuthenticationOptions AuthenticationOptions { get; set; } @@ -28,5 +28,6 @@ namespace Ocelot.Configuration.File public string ServiceName { get; set; } public string DownstreamScheme {get;set;} public string DownstreamHost {get;set;} + public int DownstreamPort { get; set; } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 8778e5f7..960374cc 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -1,17 +1,18 @@ using System; using System.Collections.Generic; +using Ocelot.Values; namespace Ocelot.Configuration { public class ReRoute { - public ReRoute(string downstreamTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, + public ReRoute(DownstreamPathTemplate downstreamPathTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries, string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery, - string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func downstreamHost, string downstreamScheme) + string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func downstreamHostAndPort, string downstreamScheme) { - DownstreamTemplate = downstreamTemplate; + DownstreamPathTemplate = downstreamPathTemplate; UpstreamTemplate = upstreamTemplate; UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; @@ -32,11 +33,11 @@ namespace Ocelot.Configuration UseServiceDiscovery = useServiceDiscovery; ServiceDiscoveryProvider = serviceDiscoveryProvider; ServiceDiscoveryAddress = serviceDiscoveryAddress; - DownstreamHost = downstreamHost; + DownstreamHostAndPort = downstreamHostAndPort; DownstreamScheme = downstreamScheme; } - public string DownstreamTemplate { get; private set; } + public DownstreamPathTemplate DownstreamPathTemplate { get; private set; } public string UpstreamTemplate { get; private set; } public string UpstreamTemplatePattern { get; private set; } public string UpstreamHttpMethod { get; private set; } @@ -54,7 +55,7 @@ namespace Ocelot.Configuration public bool UseServiceDiscovery { get; private set;} public string ServiceDiscoveryProvider { get; private set;} public string ServiceDiscoveryAddress { get; private set;} - public Func DownstreamHost {get;private set;} + public Func DownstreamHostAndPort {get;private set;} public string DownstreamScheme {get;private set;} } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Validator/DownstreamPathTemplateAlreadyUsedError.cs b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateAlreadyUsedError.cs new file mode 100644 index 00000000..e350753c --- /dev/null +++ b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateAlreadyUsedError.cs @@ -0,0 +1,11 @@ +using Ocelot.Errors; + +namespace Ocelot.Configuration.Validator +{ + public class DownstreamPathTemplateAlreadyUsedError : Error + { + public DownstreamPathTemplateAlreadyUsedError(string message) : base(message, OcelotErrorCode.DownstreampathTemplateAlreadyUsedError) + { + } + } +} diff --git a/src/Ocelot/Configuration/Validator/DownstreamPathTemplateContainsSchemeError.cs b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateContainsSchemeError.cs new file mode 100644 index 00000000..a3dfa309 --- /dev/null +++ b/src/Ocelot/Configuration/Validator/DownstreamPathTemplateContainsSchemeError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.Configuration.Validator +{ + public class DownstreamPathTemplateContainsSchemeError : Error + { + public DownstreamPathTemplateContainsSchemeError(string message) + : base(message, OcelotErrorCode.DownstreamPathTemplateContainsSchemeError) + { + } + } +} diff --git a/src/Ocelot/Configuration/Validator/DownstreamTemplateAlreadyUsedError.cs b/src/Ocelot/Configuration/Validator/DownstreamTemplateAlreadyUsedError.cs deleted file mode 100644 index b836b1eb..00000000 --- a/src/Ocelot/Configuration/Validator/DownstreamTemplateAlreadyUsedError.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.Configuration.Validator -{ - public class DownstreamTemplateAlreadyUsedError : Error - { - public DownstreamTemplateAlreadyUsedError(string message) : base(message, OcelotErrorCode.DownstreamTemplateAlreadyUsedError) - { - } - } -} diff --git a/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsHostError.cs b/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsHostError.cs deleted file mode 100644 index 8d9dba92..00000000 --- a/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsHostError.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.Configuration.Validator -{ - public class DownstreamTemplateContainsHostError : Error - { - public DownstreamTemplateContainsHostError(string message) - : base(message, OcelotErrorCode.DownstreamTemplateContainsHostError) - { - } - } -} diff --git a/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsSchemeError.cs b/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsSchemeError.cs deleted file mode 100644 index 1901fc88..00000000 --- a/src/Ocelot/Configuration/Validator/DownstreamTemplateContainsSchemeError.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.Configuration.Validator -{ - public class DownstreamTemplateContainsSchemeError : Error - { - public DownstreamTemplateContainsSchemeError(string message) - : base(message, OcelotErrorCode.DownstreamTemplateContainsSchemeError) - { - } - } -} diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs index 3a675d41..412613eb 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs @@ -26,7 +26,7 @@ namespace Ocelot.Configuration.Validator return new OkResponse(result); } - result = CheckForReRoutesContainingDownstreamScheme(configuration); + result = CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(configuration); if (result.IsError) { @@ -70,25 +70,25 @@ namespace Ocelot.Configuration.Validator return Enum.TryParse(provider, true, out supportedProvider); } - private ConfigurationValidationResult CheckForReRoutesContainingDownstreamScheme(FileConfiguration configuration) + private ConfigurationValidationResult CheckForReRoutesContainingDownstreamSchemeInDownstreamPathTemplate(FileConfiguration configuration) { var errors = new List(); foreach(var reRoute in configuration.ReRoutes) { - if(reRoute.DownstreamTemplate.Contains("https://") - || reRoute.DownstreamTemplate.Contains("http://")) + if(reRoute.DownstreamPathTemplate.Contains("https://") + || reRoute.DownstreamPathTemplate.Contains("http://")) { - errors.Add(new DownstreamTemplateContainsSchemeError($"{reRoute.DownstreamTemplate} contains scheme")); + errors.Add(new DownstreamPathTemplateContainsSchemeError($"{reRoute.DownstreamPathTemplate} contains scheme")); } } if(errors.Any()) { - return new ConfigurationValidationResult(false, errors); + return new ConfigurationValidationResult(true, errors); } - return new ConfigurationValidationResult(true, errors); + return new ConfigurationValidationResult(false, errors); } private ConfigurationValidationResult CheckForDupliateReRoutes(FileConfiguration configuration) @@ -105,7 +105,7 @@ namespace Ocelot.Configuration.Validator .Where(x => x.Skip(1).Any()); var errors = dupes - .Select(d => new DownstreamTemplateAlreadyUsedError(string.Format("Duplicate DownstreamTemplate: {0}", d.Key.UpstreamTemplate))) + .Select(d => new DownstreamPathTemplateAlreadyUsedError(string.Format("Duplicate DownstreamPath: {0}", d.Key.UpstreamTemplate))) .Cast() .ToList(); diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 04839abb..9f40b009 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -18,6 +18,7 @@ using Ocelot.Configuration.Repository; using Ocelot.Configuration.Validator; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.DownstreamUrlCreator; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Headers; using Ocelot.Infrastructure.Claims.Parser; @@ -59,6 +60,7 @@ namespace Ocelot.DependencyInjection services.AddMvcCore().AddJsonFormatters(); services.AddLogging(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -69,7 +71,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index a74b6316..f445b46b 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -44,7 +44,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware return; } - _logger.LogDebug("downstream template is {downstreamRoute.Data.ReRoute.DownstreamTemplate}", downstreamRoute.Data.ReRoute.DownstreamTemplate); + _logger.LogDebug("downstream template is {downstreamRoute.Data.ReRoute.DownstreamPath}", downstreamRoute.Data.ReRoute.DownstreamPathTemplate); SetDownstreamRouteForThisRequest(downstreamRoute.Data); diff --git a/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs b/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs new file mode 100644 index 00000000..8978f665 --- /dev/null +++ b/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.DownstreamUrlCreator +{ + public class DownstreamHostNullOrEmptyError : Error + { + public DownstreamHostNullOrEmptyError() + : base("downstream host was null or empty", OcelotErrorCode.DownstreamHostNullOrEmptyError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs b/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs new file mode 100644 index 00000000..fbc1a5f5 --- /dev/null +++ b/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.DownstreamUrlCreator +{ + public class DownstreamPathNullOrEmptyError : Error + { + public DownstreamPathNullOrEmptyError() + : base("downstream path was null or empty", OcelotErrorCode.DownstreamPathNullOrEmptyError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs b/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs new file mode 100644 index 00000000..e52d3488 --- /dev/null +++ b/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.DownstreamUrlCreator +{ + public class DownstreamSchemeNullOrEmptyError : Error + { + public DownstreamSchemeNullOrEmptyError() + : base("downstream scheme was null or empty", OcelotErrorCode.DownstreamSchemeNullOrEmptyError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs new file mode 100644 index 00000000..18683e62 --- /dev/null +++ b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs @@ -0,0 +1,12 @@ +using Ocelot.Configuration; +using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.DownstreamUrlCreator +{ + public interface IUrlBuilder + { + Response Build(string downstreamPath, string downstreamScheme, HostAndPort downstreamHostAndPort); + } +} diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 8d0af0bd..8144b42b 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using Ocelot.Configuration; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; @@ -11,17 +12,20 @@ namespace Ocelot.DownstreamUrlCreator.Middleware public class DownstreamUrlCreatorMiddleware : OcelotMiddleware { private readonly RequestDelegate _next; - private readonly IDownstreamUrlPathPlaceholderReplacer _urlReplacer; + private readonly IDownstreamPathPlaceholderReplacer _replacer; private readonly IOcelotLogger _logger; + private readonly IUrlBuilder _urlBuilder; public DownstreamUrlCreatorMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, - IDownstreamUrlPathPlaceholderReplacer urlReplacer, - IRequestScopedDataRepository requestScopedDataRepository) + IDownstreamPathPlaceholderReplacer replacer, + IRequestScopedDataRepository requestScopedDataRepository, + IUrlBuilder urlBuilder) :base(requestScopedDataRepository) { _next = next; - _urlReplacer = urlReplacer; + _replacer = replacer; + _urlBuilder = urlBuilder; _logger = loggerFactory.CreateLogger(); } @@ -29,19 +33,34 @@ namespace Ocelot.DownstreamUrlCreator.Middleware { _logger.LogDebug("started calling downstream url creator middleware"); - var downstreamUrl = _urlReplacer.Replace(DownstreamRoute.ReRoute.DownstreamTemplate, DownstreamRoute.TemplatePlaceholderNameAndValues); + var dsPath = _replacer + .Replace(DownstreamRoute.ReRoute.DownstreamPathTemplate, DownstreamRoute.TemplatePlaceholderNameAndValues); - if (downstreamUrl.IsError) + if (dsPath.IsError) { - _logger.LogDebug("IDownstreamUrlPathPlaceholderReplacer returned an error, setting pipeline error"); + _logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error"); - SetPipelineError(downstreamUrl.Errors); + SetPipelineError(dsPath.Errors); return; } - _logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", downstreamUrl.Data.Value); + var dsScheme = DownstreamRoute.ReRoute.DownstreamScheme; - SetDownstreamUrlForThisRequest(downstreamUrl.Data.Value); + var dsHostAndPort = DownstreamRoute.ReRoute.DownstreamHostAndPort(); + + var dsUrl = _urlBuilder.Build(dsPath.Data.Value, dsScheme, dsHostAndPort); + + if (dsUrl.IsError) + { + _logger.LogDebug("IUrlBuilder returned an error, setting pipeline error"); + + SetPipelineError(dsUrl.Errors); + return; + } + + _logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", dsUrl.Data.Value); + + SetDownstreamUrlForThisRequest(dsUrl.Data.Value); _logger.LogDebug("calling next middleware"); diff --git a/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs new file mode 100644 index 00000000..2124ce3b --- /dev/null +++ b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using Ocelot.Configuration; +using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; +using Ocelot.Errors; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.DownstreamUrlCreator +{ + public class UrlBuilder : IUrlBuilder + { + public Response Build(string downstreamPath, string downstreamScheme, HostAndPort downstreamHostAndPort) + { + if (string.IsNullOrEmpty(downstreamPath)) + { + return new ErrorResponse(new List {new DownstreamPathNullOrEmptyError()}); + } + + if (string.IsNullOrEmpty(downstreamScheme)) + { + return new ErrorResponse(new List { new DownstreamSchemeNullOrEmptyError() }); + } + + if (string.IsNullOrEmpty(downstreamHostAndPort.DownstreamHost)) + { + return new ErrorResponse(new List { new DownstreamHostNullOrEmptyError() }); + } + + var builder = new UriBuilder + { + Host = downstreamHostAndPort.DownstreamHost, + Path = downstreamPath, + Scheme = downstreamScheme + }; + + if (downstreamHostAndPort.DownstreamPort > 0) + { + builder.Port = downstreamHostAndPort.DownstreamPort; + } + + var url = builder.Uri.ToString(); + + return new OkResponse(new DownstreamUrl(url)); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs index 9c19f2f9..9e925631 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs @@ -1,25 +1,25 @@ using System.Collections.Generic; using System.Text; -using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Responses; +using Ocelot.Values; namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer { - public class DownstreamUrlPathPlaceholderReplacer : IDownstreamUrlPathPlaceholderReplacer + public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer { - public Response Replace(string downstreamTemplate, List urlPathPlaceholderNameAndValues) + public Response Replace(DownstreamPathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues) { - var upstreamUrl = new StringBuilder(); + var downstreamPath = new StringBuilder(); - upstreamUrl.Append(downstreamTemplate); + downstreamPath.Append(downstreamPathTemplate.Value); foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues) { - upstreamUrl.Replace(placeholderVariableAndValue.TemplateVariableName, placeholderVariableAndValue.TemplateVariableValue); + downstreamPath.Replace(placeholderVariableAndValue.TemplateVariableName, placeholderVariableAndValue.TemplateVariableValue); } - return new OkResponse(new DownstreamUrl(upstreamUrl.ToString())); + return new OkResponse(new DownstreamPath(downstreamPath.ToString())); } } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs index 164c42ef..72d5d4b6 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs @@ -1,11 +1,12 @@ using System.Collections.Generic; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Responses; +using Ocelot.Values; namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer { - public interface IDownstreamUrlPathPlaceholderReplacer + public interface IDownstreamPathPlaceholderReplacer { - Response Replace(string downstreamTemplate, List urlPathPlaceholderNameAndValues); + Response Replace(DownstreamPathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues); } } \ No newline at end of file diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index f0a336f0..5de770cd 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -4,7 +4,7 @@ { UnauthenticatedError, UnknownError, - DownstreamTemplateAlreadyUsedError, + DownstreampathTemplateAlreadyUsedError, UnableToFindDownstreamRouteError, CannotAddDataError, CannotFindDataError, @@ -18,7 +18,9 @@ UnauthorizedError, ClaimValueNotAuthorisedError, UserDoesNotHaveClaimError, - DownstreamTemplateContainsSchemeError, - DownstreamTemplateContainsHostError + DownstreamPathTemplateContainsSchemeError, + DownstreamPathNullOrEmptyError, + DownstreamSchemeNullOrEmptyError, + DownstreamHostNullOrEmptyError } } diff --git a/src/Ocelot/Values/DownstreamPath.cs b/src/Ocelot/Values/DownstreamPath.cs new file mode 100644 index 00000000..90f2e83e --- /dev/null +++ b/src/Ocelot/Values/DownstreamPath.cs @@ -0,0 +1,12 @@ +namespace Ocelot.Values +{ + public class DownstreamPath + { + public DownstreamPath(string value) + { + Value = value; + } + + public string Value { get; private set; } + } +} diff --git a/src/Ocelot/Values/DownstreamPathTemplate.cs b/src/Ocelot/Values/DownstreamPathTemplate.cs new file mode 100644 index 00000000..a4c720eb --- /dev/null +++ b/src/Ocelot/Values/DownstreamPathTemplate.cs @@ -0,0 +1,12 @@ +namespace Ocelot.Values +{ + public class DownstreamPathTemplate + { + public DownstreamPathTemplate(string value) + { + Value = value; + } + + public string Value { get; private set; } + } +} diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrl.cs b/src/Ocelot/Values/DownstreamUrl.cs similarity index 74% rename from src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrl.cs rename to src/Ocelot/Values/DownstreamUrl.cs index ea90179e..f809c84b 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrl.cs +++ b/src/Ocelot/Values/DownstreamUrl.cs @@ -1,4 +1,4 @@ -namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer +namespace Ocelot.Values { public class DownstreamUrl { @@ -9,4 +9,4 @@ public string Value { get; private set; } } -} +} \ No newline at end of file diff --git a/src/Ocelot/Values/HostAndPort.cs b/src/Ocelot/Values/HostAndPort.cs new file mode 100644 index 00000000..cd336dec --- /dev/null +++ b/src/Ocelot/Values/HostAndPort.cs @@ -0,0 +1,14 @@ +namespace Ocelot.Values +{ + public class HostAndPort + { + public HostAndPort(string downstreamHost, int downstreamPort) + { + DownstreamHost = downstreamHost; + DownstreamPort = downstreamPort; + } + + public string DownstreamHost { get; private set; } + public int DownstreamPort { get; private set; } + } +} \ No newline at end of file diff --git a/test/.DS_Store b/test/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e73160dc2e49db945fa202355c459521f733d7f8 GIT binary patch literal 6148 zcmeHKJxc>o5S-N%0h=o=-!BmS2Su<>k}2FfE1W2aGu-F`~MC7hxva>(oPCUfq$idE!La$imz0?b@p=JYa9K6?lqru rH?D)i5bc;4?U);H$Cpu*b x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceRootUrl, 201, string.Empty)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenThePostHasContent("postContent")) @@ -74,7 +81,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions @@ -91,7 +101,7 @@ namespace Ocelot.AcceptanceTests }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Reference)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceRootUrl, 201, string.Empty)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenThePostHasContent("postContent")) @@ -109,7 +119,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions @@ -126,7 +139,7 @@ namespace Ocelot.AcceptanceTests }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceRootUrl, 200, "Hello from Laura")) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 200, "Hello from Laura")) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) @@ -146,7 +159,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions @@ -163,7 +179,7 @@ namespace Ocelot.AcceptanceTests }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Jwt)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceRootUrl, 201, string.Empty)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) @@ -183,7 +199,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = _downstreamServiceRootUrl, + DownstreamPathTemplate = _downstreamServicePath, + DownstreamPort = _downstreamServicePort, + DownstreamHost = _downstreamServiceHost, + DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions @@ -200,7 +219,7 @@ namespace Ocelot.AcceptanceTests }; this.Given(x => x.GivenThereIsAnIdentityServerOn(_identityServerRootUrl, "api", AccessTokenType.Reference)) - .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceRootUrl, 201, string.Empty)) + .And(x => x.GivenThereIsAServiceRunningOn(_downstreamServiceUrl, 201, string.Empty)) .And(x => _steps.GivenIHaveAToken(_identityServerRootUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) diff --git a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs index 4ceb03f6..1f86c6ff 100644 --- a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs @@ -37,7 +37,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51876/", + DownstreamPathTemplate = "/", + DownstreamPort = 51876, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions @@ -91,7 +94,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51876/", + DownstreamPathTemplate = "/", + DownstreamPort = 51876, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions diff --git a/test/Ocelot.AcceptanceTests/CachingTests.cs b/test/Ocelot.AcceptanceTests/CachingTests.cs index 34f10b7a..e4e628af 100644 --- a/test/Ocelot.AcceptanceTests/CachingTests.cs +++ b/test/Ocelot.AcceptanceTests/CachingTests.cs @@ -31,7 +31,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", FileCacheOptions = new FileCacheOptions @@ -64,7 +67,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", FileCacheOptions = new FileCacheOptions diff --git a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs index 81a12381..81824602 100644 --- a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs @@ -30,7 +30,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get" } @@ -54,7 +57,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false @@ -79,7 +85,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -104,7 +113,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/PRODUCTS/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -129,7 +141,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -154,7 +169,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/PRODUCTS/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true diff --git a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs index 160686d5..08bbd968 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs @@ -51,7 +51,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:52876/", + DownstreamPathTemplate = "/", + DownstreamPort = 52876, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions diff --git a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs index 36583018..04dc25db 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs @@ -51,7 +51,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:57876/", + DownstreamPathTemplate = "/", + DownstreamPort = 57876, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions diff --git a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs index 1a267a98..f6f6de20 100644 --- a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -45,7 +45,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -79,7 +82,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -113,7 +119,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "41879/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -147,7 +156,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -181,7 +193,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -215,7 +230,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:41879/", + DownstreamPathTemplate = "/", + DownstreamPort = 41879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } diff --git a/test/Ocelot.AcceptanceTests/RequestIdTests.cs b/test/Ocelot.AcceptanceTests/RequestIdTests.cs index 512736ae..9334786b 100644 --- a/test/Ocelot.AcceptanceTests/RequestIdTests.cs +++ b/test/Ocelot.AcceptanceTests/RequestIdTests.cs @@ -33,7 +33,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", RequestIdKey = _steps.RequestIdKey @@ -58,7 +61,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", RequestIdKey = _steps.RequestIdKey @@ -85,7 +91,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } diff --git a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs index 902de662..81307781 100644 --- a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs +++ b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs @@ -29,7 +29,7 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:53876/", + DownstreamPathTemplate = "http://localhost:53876/", UpstreamTemplate = "/", UpstreamHttpMethod = "Get" } diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 84e00d5b..4f97114f 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -40,7 +40,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/", UpstreamHttpMethod = "Get", } @@ -56,6 +59,62 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_return_response_200_when_path_missing_forward_slash_as_first_char() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "api/products", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_when_host_has_trailing_slash() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/products", + DownstreamScheme = "http", + DownstreamHost = "localhost/", + DownstreamPort = 51879, + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/products", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + [Fact] public void should_not_care_about_no_trailing() { @@ -65,7 +124,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/products", + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/products/", UpstreamHttpMethod = "Get", } @@ -90,7 +152,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/products", + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/products", UpstreamHttpMethod = "Get", } @@ -115,7 +180,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/products", + DownstreamPathTemplate = "/products", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } @@ -139,7 +207,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/api/products/{productId}", + DownstreamPathTemplate = "/api/products/{productId}", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get" } @@ -164,7 +235,10 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/", + DownstreamPathTemplate = "/", + DownstreamHost = "localhost", + DownstreamPort = 51879, + DownstreamScheme = "http", UpstreamTemplate = "/", UpstreamHttpMethod = "Post" } @@ -189,8 +263,11 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamTemplate = "http://localhost:51879/newThing", + DownstreamPathTemplate = "/newThing", UpstreamTemplate = "/newThing", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, UpstreamHttpMethod = "Get", } } diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index 7b6b8535..a049d3ea 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -10,7 +10,6 @@ namespace Ocelot.ManualTest var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() .UseStartup() .Build(); diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index 15276d6e..f7e2bb75 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -1,7 +1,10 @@ { "ReRoutes": [ { - "DownstreamTemplate": "http://localhost:52876/", + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHost": "localhost", + "DownstreamPort": 52876, "UpstreamTemplate": "/identityserverexample", "UpstreamHttpMethod": "Get", "AuthenticationOptions": { @@ -38,108 +41,165 @@ "RequestIdKey": "OcRequestId" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts", + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}", + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Get" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}/comments", + "DownstreamPathTemplate": "/posts/{postId}/comments", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/{postId}/comments", "UpstreamHttpMethod": "Get" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/comments", + "DownstreamPathTemplate": "/comments", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/comments", "UpstreamHttpMethod": "Get" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts", + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts", "UpstreamHttpMethod": "Post" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}", + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Put" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}", + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Patch" }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts/{postId}", + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Delete" }, { - "DownstreamTemplate": "http://products20161126090340.azurewebsites.net/api/products", + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/products", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://products20161126090340.azurewebsites.net/api/products/{productId}", + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/products/{productId}", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://products20161126090340.azurewebsites.net/api/products", + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/products", "UpstreamHttpMethod": "Post", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://products20161126090340.azurewebsites.net/api/products/{productId}", + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/products/{productId}", "UpstreamHttpMethod": "Put", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://products20161126090340.azurewebsites.net/api/products/{productId}", + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/products/{productId}", "UpstreamHttpMethod": "Delete", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://customers20161126090811.azurewebsites.net/api/customers", + "DownstreamPathTemplate": "/api/customers", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/customers", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://customers20161126090811.azurewebsites.net/api/customers/{customerId}", + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://customers20161126090811.azurewebsites.net/api/customers", + "DownstreamPathTemplate": "/api/customers", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/customers", "UpstreamHttpMethod": "Post", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://customers20161126090811.azurewebsites.net/api/customers/{customerId}", + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Put", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://customers20161126090811.azurewebsites.net/api/customers/{customerId}", + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, "UpstreamTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Delete", "FileCacheOptions": { "TtlSeconds": 15 } }, { - "DownstreamTemplate": "http://jsonplaceholder.typicode.com/posts", + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, "UpstreamTemplate": "/posts/", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } diff --git a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs index e3cccdc0..8822e6b2 100644 --- a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs @@ -67,7 +67,7 @@ namespace Ocelot.UnitTests.Claims { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("any old string") + .WithDownstreamPathTemplate("any old string") .WithClaimsToClaims(new List { new ClaimToThing("sub", "UserType", "|", 0) diff --git a/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs b/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs index 174ea8bc..8a3e24f9 100644 --- a/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs @@ -10,8 +10,8 @@ namespace Ocelot.UnitTests.Configuration { public class ConfigurationValidationTests { - private FileConfiguration _fileConfiguration; private readonly IConfigurationValidator _configurationValidator; + private FileConfiguration _fileConfiguration; private Response _result; public ConfigurationValidationTests() @@ -22,32 +22,13 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void configuration_is_invalid_if_scheme_in_downstream_template() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() + this.Given(x => x.GivenAConfiguration(new FileConfiguration { ReRoutes = new List { new FileReRoute { - DownstreamTemplate = "http://www.bbc.co.uk/api/products/{productId}", - UpstreamTemplate = "http://asdf.com" - } - } - })) - .When(x => x.WhenIValidateTheConfiguration()) - .Then(x => x.ThenTheResultIsNotValid()) - .BDDfy(); - } - - [Fact] - public void configuration_is_invalid_if_host_in_downstream_template() - { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamTemplate = "www.bbc.co.uk/api/products/{productId}", + DownstreamPathTemplate = "http://www.bbc.co.uk/api/products/{productId}", UpstreamTemplate = "http://asdf.com" } } @@ -60,13 +41,13 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void configuration_is_valid_with_one_reroute() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() + this.Given(x => x.GivenAConfiguration(new FileConfiguration { ReRoutes = new List { new FileReRoute { - DownstreamTemplate = "/api/products/", + DownstreamPathTemplate = "/api/products/", UpstreamTemplate = "http://asdf.com" } } @@ -79,13 +60,13 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void configuration_is_valid_with_valid_authentication_provider() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() + this.Given(x => x.GivenAConfiguration(new FileConfiguration { ReRoutes = new List { new FileReRoute { - DownstreamTemplate = "/api/products/", + DownstreamPathTemplate = "/api/products/", UpstreamTemplate = "http://asdf.com", AuthenticationOptions = new FileAuthenticationOptions { @@ -102,13 +83,13 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void configuration_is_invalid_with_invalid_authentication_provider() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() + this.Given(x => x.GivenAConfiguration(new FileConfiguration { ReRoutes = new List { new FileReRoute { - DownstreamTemplate = "/api/products/", + DownstreamPathTemplate = "/api/products/", UpstreamTemplate = "http://asdf.com", AuthenticationOptions = new FileAuthenticationOptions { @@ -126,25 +107,25 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void configuration_is_not_valid_with_duplicate_reroutes() { - this.Given(x => x.GivenAConfiguration(new FileConfiguration() + this.Given(x => x.GivenAConfiguration(new FileConfiguration { ReRoutes = new List { new FileReRoute { - DownstreamTemplate = "/api/products/", + DownstreamPathTemplate = "/api/products/", UpstreamTemplate = "http://asdf.com" }, new FileReRoute { - DownstreamTemplate = "http://www.bbc.co.uk", + DownstreamPathTemplate = "http://www.bbc.co.uk", UpstreamTemplate = "http://asdf.com" } } })) .When(x => x.WhenIValidateTheConfiguration()) .Then(x => x.ThenTheResultIsNotValid()) - .And(x => x.ThenTheErrorIs()) + .And(x => x.ThenTheErrorIs()) .BDDfy(); } @@ -173,4 +154,4 @@ namespace Ocelot.UnitTests.Configuration _result.Data.Errors[0].ShouldBeOfType(); } } -} +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 4207a333..fc80a478 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -46,7 +46,7 @@ namespace Ocelot.UnitTests.Configuration { DownstreamHost = "127.0.0.1", UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } }, @@ -57,7 +57,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamHost("127.0.0.1") - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -76,7 +76,7 @@ namespace Ocelot.UnitTests.Configuration { DownstreamScheme = "https", UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } }, @@ -87,7 +87,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamScheme("https") - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -106,7 +106,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false, ServiceName = "ProductService" @@ -126,7 +126,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -149,7 +149,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false, } @@ -160,7 +160,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -182,7 +182,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false } @@ -193,7 +193,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -212,7 +212,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get" } } @@ -222,7 +222,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") @@ -241,7 +241,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -252,7 +252,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") @@ -271,7 +271,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -286,7 +286,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") @@ -306,7 +306,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -317,7 +317,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") @@ -332,7 +332,7 @@ namespace Ocelot.UnitTests.Configuration var expected = new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") @@ -355,7 +355,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, AuthenticationOptions = new FileAuthenticationOptions @@ -395,7 +395,7 @@ namespace Ocelot.UnitTests.Configuration var expected = new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") @@ -414,7 +414,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, AuthenticationOptions = new FileAuthenticationOptions @@ -446,7 +446,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}/variants/{variantId}", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -457,7 +457,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}/variants/{variantId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$") @@ -476,7 +476,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/api/products/{productId}/variants/{variantId}/", - DownstreamTemplate = "/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -487,7 +487,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/products/{productId}") + .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamTemplate("/api/products/{productId}/variants/{variantId}/") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$") @@ -506,7 +506,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { UpstreamTemplate = "/", - DownstreamTemplate = "/api/products/", + DownstreamPathTemplate = "/api/products/", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -517,7 +517,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() - .WithDownstreamTemplate("/api/products/") + .WithDownstreamPathTemplate("/api/products/") .WithUpstreamTemplate("/") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/$") @@ -553,7 +553,7 @@ namespace Ocelot.UnitTests.Configuration var result = _config.Data.ReRoutes[i]; var expected = expectedReRoutes[i]; - result.DownstreamTemplate.ShouldBe(expected.DownstreamTemplate); + result.DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate.Value); result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); result.UpstreamTemplate.ShouldBe(expected.UpstreamTemplate); result.UpstreamTemplatePattern.ShouldBe(expected.UpstreamTemplatePattern); diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index 8f7af24e..ec46f914 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -44,7 +44,7 @@ namespace Ocelot.UnitTests.Configuration private void ThenTheConfigurationIsReturned() { - _getResult.Data.ReRoutes[0].DownstreamTemplate.ShouldBe("initial"); + _getResult.Data.ReRoutes[0].DownstreamPathTemplate.Value.ShouldBe("initial"); } private void WhenIGetTheConfiguration() @@ -75,16 +75,16 @@ namespace Ocelot.UnitTests.Configuration class FakeConfig : IOcelotConfiguration { - private readonly string _downstreamTemplate; + private readonly string _downstreamTemplatePath; - public FakeConfig(string downstreamTemplate) + public FakeConfig(string downstreamTemplatePath) { - _downstreamTemplate = downstreamTemplate; + _downstreamTemplatePath = downstreamTemplatePath; } public List ReRoutes => new List { - new ReRouteBuilder().WithDownstreamTemplate(_downstreamTemplate).Build() + new ReRouteBuilder().WithDownstreamPathTemplate(_downstreamTemplatePath).Build() }; } } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 22dfea80..0d5a6d48 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -61,7 +61,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder [Fact] public void should_call_scoped_data_repository_correctly() { - this.Given(x => x.GivenTheDownStreamRouteFinderReturns(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("any old string").Build()))) + this.Given(x => x.GivenTheDownStreamRouteFinderReturns(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("any old string").Build()))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index bb390d32..c0afca42 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -44,7 +44,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPath") + .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamTemplate("someUpstreamPath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("someUpstreamPath") @@ -57,7 +57,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .Then( x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPath") + .WithDownstreamPathTemplate("someDownstreamPath") .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -75,13 +75,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPath") + .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamTemplate("someUpstreamPath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("") .Build(), new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPathForAPost") + .WithDownstreamPathTemplate("someDownstreamPathForAPost") .WithUpstreamTemplate("someUpstreamPath") .WithUpstreamHttpMethod("Post") .WithUpstreamTemplatePattern("") @@ -94,7 +94,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .Then( x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("someDownstreamPathForAPost") + .WithDownstreamPathTemplate("someDownstreamPathForAPost") .Build() ))) .BDDfy(); @@ -107,7 +107,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() - .WithDownstreamTemplate("somPath") + .WithDownstreamPathTemplate("somPath") .WithUpstreamTemplate("somePath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("somePath") @@ -174,7 +174,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void ThenTheFollowingIsReturned(DownstreamRoute expected) { - _result.Data.ReRoute.DownstreamTemplate.ShouldBe(expected.ReRoute.DownstreamTemplate); + _result.Data.ReRoute.DownstreamPathTemplate.Value.ShouldBe(expected.ReRoute.DownstreamPathTemplate.Value); for (int i = 0; i < _result.Data.TemplatePlaceholderNameAndValues.Count; i++) { diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index 98bc5f0b..5581a32e 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -7,15 +7,18 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Moq; +using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.DownstreamUrlCreator; using Ocelot.DownstreamUrlCreator.Middleware; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Responses; +using Ocelot.Values; using TestStack.BDDfy; using Xunit; @@ -23,21 +26,23 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator { public class DownstreamUrlCreatorMiddlewareTests : IDisposable { - private readonly Mock _downstreamUrlTemplateVariableReplacer; + private readonly Mock _downstreamUrlTemplateVariableReplacer; private readonly Mock _scopedRepository; + private readonly Mock _urlBuilder; private readonly string _url; private readonly TestServer _server; private readonly HttpClient _client; private Response _downstreamRoute; private HttpResponseMessage _result; + private OkResponse _downstreamPath; private OkResponse _downstreamUrl; public DownstreamUrlCreatorMiddlewareTests() { _url = "http://localhost:51879"; - _downstreamUrlTemplateVariableReplacer = new Mock(); + _downstreamUrlTemplateVariableReplacer = new Mock(); _scopedRepository = new Mock(); - + _urlBuilder = new Mock(); var builder = new WebHostBuilder() .ConfigureServices(x => { @@ -45,6 +50,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator x.AddLogging(); x.AddSingleton(_downstreamUrlTemplateVariableReplacer.Object); x.AddSingleton(_scopedRepository.Object); + x.AddSingleton(_urlBuilder.Object); }) .UseUrls(_url) .UseKestrel() @@ -61,21 +67,30 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator } [Fact] - public void should_call_scoped_data_repository_correctly() + public void should_call_dependencies_correctly() { - this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("any old string").Build()))) - .And(x => x.TheUrlReplacerReturns("any old string")) + this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("any old string").Build()))) + .And(x => x.TheUrlReplacerReturns("/api/products/1")) + .And(x => x.TheUrlBuilderReturns("http://www.bbc.co.uk/api/products/1")) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); } + private void TheUrlBuilderReturns(string dsUrl) + { + _downstreamUrl = new OkResponse(new DownstreamUrl(dsUrl)); + _urlBuilder + .Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(_downstreamUrl); + } + private void TheUrlReplacerReturns(string downstreamUrl) { - _downstreamUrl = new OkResponse(new DownstreamUrl(downstreamUrl)); + _downstreamPath = new OkResponse(new DownstreamPath(downstreamUrl)); _downstreamUrlTemplateVariableReplacer - .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) - .Returns(_downstreamUrl); + .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) + .Returns(_downstreamPath); } private void ThenTheScopedDataRepositoryIsCalledCorrectly() diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs new file mode 100644 index 00000000..7e512798 --- /dev/null +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlBuilderTests.cs @@ -0,0 +1,124 @@ +using System; +using Ocelot.Configuration; +using Ocelot.DownstreamUrlCreator; +using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; +using Ocelot.Responses; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.DownstreamUrlCreator +{ + public class UrlBuilderTests + { + private readonly IUrlBuilder _urlBuilder; + private string _dsPath; + private string _dsScheme; + private string _dsHost; + private int _dsPort; + + private Response _result; + + public UrlBuilderTests() + { + _urlBuilder = new UrlBuilder(); + } + + [Fact] + public void should_return_error_when_downstream_path_is_null() + { + this.Given(x => x.GivenADownstreamPath(null)) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenThereIsAnErrorOfType()) + .BDDfy(); + } + + [Fact] + public void should_return_error_when_downstream_scheme_is_null() + { + this.Given(x => x.GivenADownstreamScheme(null)) + .And(x => x.GivenADownstreamPath("test")) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenThereIsAnErrorOfType()) + .BDDfy(); + } + + [Fact] + public void should_return_error_when_downstream_host_is_null() + { + this.Given(x => x.GivenADownstreamScheme(null)) + .And(x => x.GivenADownstreamPath("test")) + .And(x => x.GivenADownstreamScheme("test")) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenThereIsAnErrorOfType()) + .BDDfy(); + } + + [Fact] + public void should_not_use_port_if_zero() + { + this.Given(x => x.GivenADownstreamPath("/api/products/1")) + .And(x => x.GivenADownstreamScheme("http")) + .And(x => x.GivenADownstreamHost("127.0.0.1")) + .And(x => x.GivenADownstreamPort(0)) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenTheUrlIsReturned("http://127.0.0.1/api/products/1")) + .And(x => x.ThenTheUrlIsWellFormed()) + .BDDfy(); + } + + [Fact] + public void should_build_well_formed_uri() + { + this.Given(x => x.GivenADownstreamPath("/api/products/1")) + .And(x => x.GivenADownstreamScheme("http")) + .And(x => x.GivenADownstreamHost("127.0.0.1")) + .And(x => x.GivenADownstreamPort(5000)) + .When(x => x.WhenIBuildTheUrl()) + .Then(x => x.ThenTheUrlIsReturned("http://127.0.0.1:5000/api/products/1")) + .And(x => x.ThenTheUrlIsWellFormed()) + .BDDfy(); + } + + private void ThenThereIsAnErrorOfType() + { + _result.Errors[0].ShouldBeOfType(); + } + + private void GivenADownstreamPath(string dsPath) + { + _dsPath = dsPath; + } + + private void GivenADownstreamScheme(string dsScheme) + { + _dsScheme = dsScheme; + } + + private void GivenADownstreamHost(string dsHost) + { + _dsHost = dsHost; + } + + private void GivenADownstreamPort(int dsPort) + { + _dsPort = dsPort; + } + + private void WhenIBuildTheUrl() + { + _result = _urlBuilder.Build(_dsPath, _dsScheme, new HostAndPort(_dsHost, _dsPort)); + } + + private void ThenTheUrlIsReturned(string expected) + { + _result.Data.Value.ShouldBe(expected); + } + + private void ThenTheUrlIsWellFormed() + { + Uri.IsWellFormedUriString(_result.Data.Value, UriKind.Absolute).ShouldBeTrue(); + } + } +} diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs index b1ad369b..a7a5a89b 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs @@ -4,6 +4,7 @@ using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Responses; +using Ocelot.Values; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -13,12 +14,12 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer public class UpstreamUrlPathTemplateVariableReplacerTests { private DownstreamRoute _downstreamRoute; - private Response _result; - private readonly IDownstreamUrlPathPlaceholderReplacer _downstreamUrlPathReplacer; + private Response _result; + private readonly IDownstreamPathPlaceholderReplacer _downstreamPathReplacer; public UpstreamUrlPathTemplateVariableReplacerTests() { - _downstreamUrlPathReplacer = new DownstreamUrlPathPlaceholderReplacer(); + _downstreamPathReplacer = new DownstreamTemplatePathPlaceholderReplacer(); } [Fact] @@ -33,7 +34,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_no_template_variables_with_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("/").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("/")) .BDDfy(); @@ -42,7 +43,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_no_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("api").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("api").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api")) .BDDfy(); @@ -51,7 +52,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_one_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("api/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("api/").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/")) .BDDfy(); @@ -60,7 +61,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_multiple_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamTemplate("api/product/products/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("api/product/products/").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/product/products/")) .BDDfy(); @@ -74,7 +75,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new UrlPathPlaceholderNameAndValue("{productId}", "1") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamTemplate("productservice/products/{productId}/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/")) .BDDfy(); @@ -88,7 +89,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new UrlPathPlaceholderNameAndValue("{productId}", "1") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamTemplate("productservice/products/{productId}/variants").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/variants").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants")) .BDDfy(); @@ -103,7 +104,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new UrlPathPlaceholderNameAndValue("{variantId}", "12") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamTemplate("productservice/products/{productId}/variants/{variantId}").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/variants/{variantId}").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants/12")) .BDDfy(); @@ -119,7 +120,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new UrlPathPlaceholderNameAndValue("{categoryId}", "34") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}").Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/category/34/products/1/variants/12")) .BDDfy(); @@ -132,7 +133,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer private void WhenIReplaceTheTemplateVariables() { - _result = _downstreamUrlPathReplacer.Replace(_downstreamRoute.ReRoute.DownstreamTemplate, _downstreamRoute.TemplatePlaceholderNameAndValues); + _result = _downstreamPathReplacer.Replace(_downstreamRoute.ReRoute.DownstreamPathTemplate, _downstreamRoute.TemplatePlaceholderNameAndValues); } private void ThenTheDownstreamUrlPathIsReturned(string expected) diff --git a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs index b85802af..3516d26b 100644 --- a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs @@ -67,7 +67,7 @@ namespace Ocelot.UnitTests.Headers { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("any old string") + .WithDownstreamPathTemplate("any old string") .WithClaimsToHeaders(new List { new ClaimToThing("UserId", "Subject", "", 0) diff --git a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs index e4c7375e..39b32937 100644 --- a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs @@ -65,7 +65,7 @@ namespace Ocelot.UnitTests.QueryStrings { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("any old string") + .WithDownstreamPathTemplate("any old string") .WithClaimsToQueries(new List { new ClaimToThing("UserId", "Subject", "", 0) diff --git a/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs b/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs index 8c023783..543613a8 100644 --- a/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs @@ -71,7 +71,7 @@ namespace Ocelot.UnitTests.RequestId { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("any old string") + .WithDownstreamPathTemplate("any old string") .WithRequestIdKey("LSRequestId").Build()); var requestId = Guid.NewGuid().ToString(); @@ -88,7 +88,7 @@ namespace Ocelot.UnitTests.RequestId { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithDownstreamTemplate("any old string") + .WithDownstreamPathTemplate("any old string") .WithRequestIdKey("LSRequestId").Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) From 2cd69d1908b7009637c915ed2d6e6c5edd06f3b9 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Tue, 24 Jan 2017 21:11:15 +0000 Subject: [PATCH 012/113] #20 - added cake build to do same functionality as batch scripts. Also does semver versioning of assemblies, if running in AppVeyor, generates release notes and publishes packages to appveyor. --- .gitignore | 1 + Ocelot.sln | 2 + build.cake | 218 +++++++++++++++++++++++ build.ps1 | 189 ++++++++++++++++++++ src/Ocelot/project.json | 2 +- test/Ocelot.AcceptanceTests/project.json | 8 +- test/Ocelot.Benchmarks/project.json | 4 +- test/Ocelot.ManualTest/project.json | 4 +- test/Ocelot.UnitTests/project.json | 4 +- version.ps1 | 1 + 10 files changed, 422 insertions(+), 11 deletions(-) create mode 100644 build.cake create mode 100644 build.ps1 create mode 100644 version.ps1 diff --git a/.gitignore b/.gitignore index 61b1c211..1a7759c2 100644 --- a/.gitignore +++ b/.gitignore @@ -235,3 +235,4 @@ _Pvt_Extensions # FAKE - F# Make .fake/ +tools/ diff --git a/Ocelot.sln b/Ocelot.sln index 0165beb0..c1f647af 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -11,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution appveyor.yml = appveyor.yml build-and-run-tests.bat = build-and-run-tests.bat build.bat = build.bat + build.cake = build.cake + build.ps1 = build.ps1 configuration-explanation.txt = configuration-explanation.txt global.json = global.json LICENSE.md = LICENSE.md diff --git a/build.cake b/build.cake new file mode 100644 index 00000000..513f99dd --- /dev/null +++ b/build.cake @@ -0,0 +1,218 @@ +#tool "nuget:?package=GitVersion.CommandLine" +#tool "nuget:?package=OpenCover" +#tool "nuget:?package=ReportGenerator" +#tool "nuget:?package=GitReleaseNotes" +#addin nuget:?package=Cake.DoInDirectory + +var target = Argument("target", "Default"); +var artifactsDir = Directory("artifacts"); + +Information("target is " +target); + +// versioning +var committedVersion = "0.0.0-dev"; +var buildVersion = committedVersion; + +//compile +var compileConfig = Argument("configuration", "Release"); +Information("Build configuration is " + compileConfig); + +// unit testing +var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests"); +var unitTestAssemblies = @"./test/Ocelot.UnitTests"; + +// acceptance testing +var artifactsForAcceptanceTestsDir = artifactsDir + Directory("AcceptanceTests"); +var acceptanceTestAssemblies = @"./test/Ocelot.AcceptanceTests"; + +//benchmark testing +var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests"); +var benchmarkTestAssemblies = @"./test/Ocelot.Benchmarks"; + +// packaging +var packagesDir = artifactsDir + Directory("Packages"); +var projectJson = "./src/Ocelot/project.json"; + +// release notes +var releaseNotesFile = packagesDir + File("releasenotes.md"); + +Task("Default") + .IsDependentOn("RunTests") + .IsDependentOn("Package") + .Does(() => + { + }); + +Task("Clean") + .Does(() => + { + if (DirectoryExists(artifactsDir)) + { + DeleteDirectory(artifactsDir, recursive:true); + } + CreateDirectory(artifactsDir); + }); + +Task("Version") + .Does(() => + { + var nugetVersion = GetVersion(); + Information("SemVer version number: " + nugetVersion); + + if (AppVeyor.IsRunningOnAppVeyor) + { + Information("Persisting version number..."); + PersistVersion(nugetVersion); + buildVersion = nugetVersion; + } + else + { + Information("We are not running on build server, so we won't persist the version number."); + } + }); + +Task("Restore") + .IsDependentOn("Clean") + .IsDependentOn("Version") + .Does(() => + { + DotNetCoreRestore("./src"); + DotNetCoreRestore("./test"); + }); + +Task("RunUnitTests") + .IsDependentOn("Restore") + .Does(() => + { + var buildSettings = new DotNetCoreTestSettings + { + Configuration = compileConfig, + }; + + EnsureDirectoryExists(artifactsForUnitTestsDir); + DotNetCoreTest(unitTestAssemblies, buildSettings); + }); + +Task("RunAcceptanceTests") + .IsDependentOn("Restore") + .Does(() => + { + var buildSettings = new DotNetCoreTestSettings + { + Configuration = "Debug", //acceptance test config is hard-coded for debug + }; + + EnsureDirectoryExists(artifactsForAcceptanceTestsDir); + + DoInDirectory("test/Ocelot.AcceptanceTests", () => + { + DotNetCoreTest(".", buildSettings); + }); + + }); + +Task("RunBenchmarkTests") + .IsDependentOn("Restore") + .Does(() => + { + var buildSettings = new DotNetCoreRunSettings + { + Configuration = compileConfig, + }; + + EnsureDirectoryExists(artifactsForBenchmarkTestsDir); + + DoInDirectory(benchmarkTestAssemblies, () => + { + DotNetCoreRun(".", "--args", buildSettings); + }); + }); + +Task("RunTests") + .IsDependentOn("RunUnitTests") + .IsDependentOn("RunAcceptanceTests") + .Does(() => + { + }); + +Task("Package") + .Does(() => + { + EnsureDirectoryExists(packagesDir); + + GenerateReleaseNotes(); + + var settings = new DotNetCorePackSettings + { + OutputDirectory = packagesDir, + NoBuild = true + }; + + DotNetCorePack(projectJson, settings); + + System.IO.File.WriteAllLines(packagesDir + File("artifacts"), new[]{ + "nuget:Ocelot." + buildVersion + ".nupkg", + "nugetSymbols:Ocelot." + buildVersion + ".symbols.nupkg", + "releaseNotes:releasenotes.md" + }); + + if (AppVeyor.IsRunningOnAppVeyor) + { + var path = packagesDir.ToString() + @"/**/*"; + + foreach (var file in GetFiles(path)) + { + AppVeyor.UploadArtifact(file.FullPath); + } + } + }); + +RunTarget(target); + +private string GetVersion() +{ + GitVersion(new GitVersionSettings{ + UpdateAssemblyInfo = false, + OutputType = GitVersionOutput.BuildServer + }); + + var versionInfo = GitVersion(new GitVersionSettings{ OutputType = GitVersionOutput.Json }); + return versionInfo.NuGetVersion; +} + +private void PersistVersion(string version) +{ + Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, version)); + var projectJsonFiles = GetFiles("./**/project.json"); + + foreach(var projectJsonFile in projectJsonFiles) + { + var file = projectJsonFile.ToString(); + + Information(string.Format("Updating {0}...", file)); + + var updatedProjectJson = System.IO.File.ReadAllText(file) + .Replace(committedVersion, version); + + System.IO.File.WriteAllText(file, updatedProjectJson); + } +} + +private void GenerateReleaseNotes() +{ + Information("Generating release notes at " + releaseNotesFile); + + var releaseNotesExitCode = StartProcess( + @"tools/GitReleaseNotes/tools/gitreleasenotes.exe", + new ProcessSettings { Arguments = ". /o " + releaseNotesFile }); + + if (string.IsNullOrEmpty(System.IO.File.ReadAllText(releaseNotesFile))) + { + System.IO.File.WriteAllText(releaseNotesFile, "No issues closed since last release"); + } + + if (releaseNotesExitCode != 0) + { + throw new Exception("Failed to generate release notes"); + } +} \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 00000000..44de5793 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,189 @@ +########################################################################## +# This is the Cake bootstrapper script for PowerShell. +# This file was downloaded from https://github.com/cake-build/resources +# Feel free to change this file to fit your needs. +########################################################################## + +<# + +.SYNOPSIS +This is a Powershell script to bootstrap a Cake build. + +.DESCRIPTION +This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) +and execute your Cake build script with the parameters you provide. + +.PARAMETER Script +The build script to execute. +.PARAMETER Target +The build script target to run. +.PARAMETER Configuration +The build configuration to use. +.PARAMETER Verbosity +Specifies the amount of information to be displayed. +.PARAMETER Experimental +Tells Cake to use the latest Roslyn release. +.PARAMETER WhatIf +Performs a dry run of the build script. +No tasks will be executed. +.PARAMETER Mono +Tells Cake to use the Mono scripting engine. +.PARAMETER SkipToolPackageRestore +Skips restoring of packages. +.PARAMETER ScriptArgs +Remaining arguments are added here. + +.LINK +http://cakebuild.net + +#> + +[CmdletBinding()] +Param( + [string]$Script = "build.cake", + [string]$Target = "Default", + [ValidateSet("Release", "Debug")] + [string]$Configuration = "Release", + [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] + [string]$Verbosity = "Verbose", + [switch]$Experimental, + [Alias("DryRun","Noop")] + [switch]$WhatIf, + [switch]$Mono, + [switch]$SkipToolPackageRestore, + [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [string[]]$ScriptArgs +) + +[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null +function MD5HashFile([string] $filePath) +{ + if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) + { + return $null + } + + [System.IO.Stream] $file = $null; + [System.Security.Cryptography.MD5] $md5 = $null; + try + { + $md5 = [System.Security.Cryptography.MD5]::Create() + $file = [System.IO.File]::OpenRead($filePath) + return [System.BitConverter]::ToString($md5.ComputeHash($file)) + } + finally + { + if ($file -ne $null) + { + $file.Dispose() + } + } +} + +Write-Host "Preparing to run build script..." + +if(!$PSScriptRoot){ + $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +} + +$TOOLS_DIR = Join-Path $PSScriptRoot "tools" +$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" +$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" +$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" +$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" +$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" + +# Should we use mono? +$UseMono = ""; +if($Mono.IsPresent) { + Write-Verbose -Message "Using the Mono based scripting engine." + $UseMono = "-mono" +} + +# Should we use the new Roslyn? +$UseExperimental = ""; +if($Experimental.IsPresent -and !($Mono.IsPresent)) { + Write-Verbose -Message "Using experimental version of Roslyn." + $UseExperimental = "-experimental" +} + +# Is this a dry run? +$UseDryRun = ""; +if($WhatIf.IsPresent) { + $UseDryRun = "-dryrun" +} + +# Make sure tools folder exists +if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { + Write-Verbose -Message "Creating tools directory..." + New-Item -Path $TOOLS_DIR -Type directory | out-null +} + +# Make sure that packages.config exist. +if (!(Test-Path $PACKAGES_CONFIG)) { + Write-Verbose -Message "Downloading packages.config..." + try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { + Throw "Could not download packages.config." + } +} + +# Try find NuGet.exe in path if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Trying to find nuget.exe in PATH..." + $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_) } + $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 + if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { + Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." + $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName + } +} + +# Try download NuGet.exe if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Downloading NuGet.exe..." + try { + (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE) + } catch { + Throw "Could not download NuGet.exe." + } +} + +# Save nuget.exe path to environment to be available to child processed +$ENV:NUGET_EXE = $NUGET_EXE + +# Restore tools from NuGet? +if(-Not $SkipToolPackageRestore.IsPresent) { + Push-Location + Set-Location $TOOLS_DIR + + # Check for changes in packages.config and remove installed tools if true. + [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) + if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or + ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { + Write-Verbose -Message "Missing or changed package.config hash..." + Remove-Item * -Recurse -Exclude packages.config,nuget.exe + } + + Write-Verbose -Message "Restoring tools from NuGet..." + $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occured while restoring NuGet tools." + } + else + { + $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" + } + Write-Verbose -Message ($NuGetOutput | out-string) + Pop-Location +} + +# Make sure that Cake has been installed. +if (!(Test-Path $CAKE_EXE)) { + Throw "Could not find Cake.exe at $CAKE_EXE" +} + +# Start Cake +Write-Host "Running build script..." +Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs" +exit $LASTEXITCODE \ No newline at end of file diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 85008568..8d259469 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-*", + "version": "0.0.0-dev", "dependencies": { "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index 4b364510..17f35a3c 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-*", + "version": "0.0.0-dev", "buildOptions": { "copyToOutput": { @@ -22,10 +22,10 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", "Microsoft.AspNetCore.Http": "1.1.0", "Microsoft.DotNet.InternalAbstractions": "1.0.0", - "Ocelot": "1.0.0-*", + "Ocelot": "0.0.0-dev", "xunit": "2.2.0-beta2-build3300", "dotnet-test-xunit": "2.2.0-preview2-build1029", - "Ocelot.ManualTest": "1.0.0-*", + "Ocelot.ManualTest": "0.0.0-dev", "Microsoft.AspNetCore.TestHost": "1.1.0", "IdentityServer4": "1.0.1", "Microsoft.AspNetCore.Mvc": "1.1.0", @@ -36,7 +36,7 @@ }, "runtimes": { "win10-x64": {}, - "osx.10.11-x64":{}, + "osx.10.11-x64": {}, "win7-x64": {} }, "frameworks": { diff --git a/test/Ocelot.Benchmarks/project.json b/test/Ocelot.Benchmarks/project.json index da310ddd..5f7a4987 100644 --- a/test/Ocelot.Benchmarks/project.json +++ b/test/Ocelot.Benchmarks/project.json @@ -1,11 +1,11 @@ { - "version": "1.0.0-*", + "version": "0.0.0-dev", "buildOptions": { "emitEntryPoint": true }, "dependencies": { - "Ocelot": "1.0.0-*", + "Ocelot": "0.0.0-dev", "BenchmarkDotNet": "0.10.1" }, "runtimes": { diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index 181bdb07..3ae09ccb 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-*", + "version": "0.0.0-dev", "dependencies": { "Microsoft.AspNetCore.Http": "1.1.0", @@ -10,7 +10,7 @@ "Microsoft.Extensions.Logging.Console": "1.1.0", "Microsoft.Extensions.Logging.Debug": "1.1.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", - "Ocelot": "1.0.0-*", + "Ocelot": "0.0.0-dev", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", "Microsoft.NETCore.App": "1.1.0" }, diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index 605b25d6..ab3e6cb1 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-*", + "version": "0.0.0-dev", "testRunner": "xunit", @@ -13,7 +13,7 @@ "Microsoft.Extensions.Logging.Debug": "1.1.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", "Microsoft.AspNetCore.Http": "1.1.0", - "Ocelot": "1.0.0-*", + "Ocelot": "0.0.0-dev", "xunit": "2.2.0-beta2-build3300", "dotnet-test-xunit": "2.2.0-preview2-build1029", "Moq": "4.6.38-alpha", diff --git a/version.ps1 b/version.ps1 new file mode 100644 index 00000000..621201b6 --- /dev/null +++ b/version.ps1 @@ -0,0 +1 @@ +.\tools\GitVersion.CommandLine\tools\GitVersion.exe \ No newline at end of file From 93ebb6a0d369c62c215f33b9ead1dabbe13fb7e6 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Tue, 24 Jan 2017 21:27:25 +0000 Subject: [PATCH 013/113] #20 - bump commit --- test.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 test.txt diff --git a/test.txt b/test.txt new file mode 100644 index 00000000..30d74d25 --- /dev/null +++ b/test.txt @@ -0,0 +1 @@ +test \ No newline at end of file From 4a43accc4688012bbb6d07ee6d24a1dcf8789214 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sun, 29 Jan 2017 09:41:05 +0000 Subject: [PATCH 014/113] implementing load balancers --- .../DownstreamUrlCreatorMiddleware.cs | 8 + src/Ocelot/Errors/OcelotErrorCode.cs | 4 +- src/Ocelot/Values/HostAndPort.cs | 5 + test/Ocelot.UnitTests/LeastConnectionTests.cs | 362 ++++++++++++++++++ test/Ocelot.UnitTests/RoundRobinTests.cs | 50 ++- test/Ocelot.UnitTests/ServiceRegistryTests.cs | 25 +- 6 files changed, 427 insertions(+), 27 deletions(-) create mode 100644 test/Ocelot.UnitTests/LeastConnectionTests.cs diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 8144b42b..b4d73c7c 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -46,12 +46,18 @@ namespace Ocelot.DownstreamUrlCreator.Middleware var dsScheme = DownstreamRoute.ReRoute.DownstreamScheme; + //here we could have a lb factory that takes stuff or we could just get the load balancer from the reRoute? + //returns the lb for this request + + //lease the next address from the lb + var dsHostAndPort = DownstreamRoute.ReRoute.DownstreamHostAndPort(); var dsUrl = _urlBuilder.Build(dsPath.Data.Value, dsScheme, dsHostAndPort); if (dsUrl.IsError) { + //todo - release the lb connection? _logger.LogDebug("IUrlBuilder returned an error, setting pipeline error"); SetPipelineError(dsUrl.Errors); @@ -66,6 +72,8 @@ namespace Ocelot.DownstreamUrlCreator.Middleware await _next.Invoke(context); + //todo - release the lb connection? + _logger.LogDebug("succesfully called next middleware"); } } diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index 5de770cd..85c3e097 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -21,6 +21,8 @@ DownstreamPathTemplateContainsSchemeError, DownstreamPathNullOrEmptyError, DownstreamSchemeNullOrEmptyError, - DownstreamHostNullOrEmptyError + DownstreamHostNullOrEmptyError, + ServicesAreNullError, + ServicesAreEmptyError } } diff --git a/src/Ocelot/Values/HostAndPort.cs b/src/Ocelot/Values/HostAndPort.cs index cd336dec..f8769743 100644 --- a/src/Ocelot/Values/HostAndPort.cs +++ b/src/Ocelot/Values/HostAndPort.cs @@ -10,5 +10,10 @@ public string DownstreamHost { get; private set; } public int DownstreamPort { get; private set; } + + public override string ToString() + { + return $"{DownstreamHost}:{DownstreamPort}"; + } } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/LeastConnectionTests.cs b/test/Ocelot.UnitTests/LeastConnectionTests.cs new file mode 100644 index 00000000..758d1917 --- /dev/null +++ b/test/Ocelot.UnitTests/LeastConnectionTests.cs @@ -0,0 +1,362 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Ocelot.Errors; +using Ocelot.Responses; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests +{ + public class LeastConnectionTests + { + private HostAndPort _hostAndPort; + private Response _result; + private LeastConnection _leastConnection; + private List _services; + + public LeastConnectionTests() + { + } + + [Fact] + public void should_get_next_url() + { + var serviceName = "products"; + + var hostAndPort = new HostAndPort("localhost", 80); + + var availableServices = new List + { + new Service(serviceName, hostAndPort) + }; + + this.Given(x => x.GivenAHostAndPort(hostAndPort)) + .And(x => x.GivenTheLoadBalancerStarts(availableServices, serviceName)) + .When(x => x.WhenIGetTheNextHostAndPort()) + .Then(x => x.ThenTheNextHostAndPortIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_serve_from_service_with_least_connections() + { + var serviceName = "products"; + + var availableServices = new List + { + new Service(serviceName, new HostAndPort("127.0.0.1", 80)), + new Service(serviceName, new HostAndPort("127.0.0.2", 80)), + new Service(serviceName, new HostAndPort("127.0.0.3", 80)) + }; + + _services = availableServices; + _leastConnection = new LeastConnection(() => _services, serviceName); + + var response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[2].HostAndPort.DownstreamHost); + } + + [Fact] + public void should_build_connections_per_service() + { + var serviceName = "products"; + + var availableServices = new List + { + new Service(serviceName, new HostAndPort("127.0.0.1", 80)), + new Service(serviceName, new HostAndPort("127.0.0.2", 80)), + }; + + _services = availableServices; + _leastConnection = new LeastConnection(() => _services, serviceName); + + var response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); + } + + [Fact] + public void should_release_connection() + { + var serviceName = "products"; + + var availableServices = new List + { + new Service(serviceName, new HostAndPort("127.0.0.1", 80)), + new Service(serviceName, new HostAndPort("127.0.0.2", 80)), + }; + + _services = availableServices; + _leastConnection = new LeastConnection(() => _services, serviceName); + + var response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); + + response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); + + //release this so 2 should have 1 connection and we should get 2 back as our next host and port + _leastConnection.Release(availableServices[1].HostAndPort); + + response = _leastConnection.Lease(); + + response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); + } + + [Fact] + public void should_return_error_if_services_are_null() + { + var serviceName = "products"; + + var hostAndPort = new HostAndPort("localhost", 80); + this.Given(x => x.GivenAHostAndPort(hostAndPort)) + .And(x => x.GivenTheLoadBalancerStarts(null, serviceName)) + .When(x => x.WhenIGetTheNextHostAndPort()) + .Then(x => x.ThenServiceAreNullErrorIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_services_are_empty() + { + var serviceName = "products"; + + var hostAndPort = new HostAndPort("localhost", 80); + this.Given(x => x.GivenAHostAndPort(hostAndPort)) + .And(x => x.GivenTheLoadBalancerStarts(new List(), serviceName)) + .When(x => x.WhenIGetTheNextHostAndPort()) + .Then(x => x.ThenServiceAreEmptyErrorIsReturned()) + .BDDfy(); + } + + private void ThenServiceAreNullErrorIsReturned() + { + _result.IsError.ShouldBeTrue(); + _result.Errors[0].ShouldBeOfType(); + } + + private void ThenServiceAreEmptyErrorIsReturned() + { + _result.IsError.ShouldBeTrue(); + _result.Errors[0].ShouldBeOfType(); + } + + private void GivenTheLoadBalancerStarts(List services, string serviceName) + { + _services = services; + _leastConnection = new LeastConnection(() => _services, serviceName); + } + + private void WhenTheLoadBalancerStarts(List services, string serviceName) + { + GivenTheLoadBalancerStarts(services, serviceName); + } + + private void GivenAHostAndPort(HostAndPort hostAndPort) + { + _hostAndPort = hostAndPort; + } + + private void WhenIGetTheNextHostAndPort() + { + _result = _leastConnection.Lease(); + } + + private void ThenTheNextHostAndPortIsReturned() + { + _result.Data.DownstreamHost.ShouldBe(_hostAndPort.DownstreamHost); + _result.Data.DownstreamPort.ShouldBe(_hostAndPort.DownstreamPort); + } + } + + public class LeastConnection : ILoadBalancer + { + private Func> _services; + private List _leases; + private string _serviceName; + + public LeastConnection(Func> services, string serviceName) + { + _services = services; + _serviceName = serviceName; + _leases = new List(); + } + + public Response Lease() + { + var services = _services(); + + if(services == null) + { + return new ErrorResponse(new List(){ new ServicesAreNullError($"services were null for {_serviceName}")}); + } + + if(!services.Any()) + { + return new ErrorResponse(new List(){ new ServicesAreEmptyError($"services were empty for {_serviceName}")}); + } + + //todo - maybe this should be moved somewhere else...? Maybe on a repeater on seperate thread? loop every second and update or something? + UpdateServices(services); + + var leaseWithLeastConnections = GetLeaseWithLeastConnections(); + + _leases.Remove(leaseWithLeastConnections); + + leaseWithLeastConnections = AddConnection(leaseWithLeastConnections); + + _leases.Add(leaseWithLeastConnections); + + return new OkResponse(new HostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort)); + } + + public Response Release(HostAndPort hostAndPort) + { + var matchingLease = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == hostAndPort.DownstreamHost + && l.HostAndPort.DownstreamPort == hostAndPort.DownstreamPort); + + if(matchingLease != null) + { + var replacementLease = new Lease(hostAndPort, matchingLease.Connections - 1); + + _leases.Remove(matchingLease); + + _leases.Add(replacementLease); + } + + return new OkResponse(); + } + + private Lease AddConnection(Lease lease) + { + return new Lease(lease.HostAndPort, lease.Connections + 1); + } + + private Lease GetLeaseWithLeastConnections() + { + //now get the service with the least connections? + Lease leaseWithLeastConnections = null; + + for(var i = 0; i < _leases.Count; i++) + { + if(i == 0) + { + leaseWithLeastConnections = _leases[i]; + } + else + { + if(_leases[i].Connections < leaseWithLeastConnections.Connections) + { + leaseWithLeastConnections = _leases[i]; + } + } + } + + return leaseWithLeastConnections; + } + + private Response UpdateServices(List services) + { + if(_leases.Count > 0) + { + var leasesToRemove = new List(); + + foreach(var lease in _leases) + { + var match = services.FirstOrDefault(s => s.HostAndPort.DownstreamHost == lease.HostAndPort.DownstreamHost + && s.HostAndPort.DownstreamPort == lease.HostAndPort.DownstreamPort); + + if(match == null) + { + leasesToRemove.Add(lease); + } + } + + foreach(var lease in leasesToRemove) + { + _leases.Remove(lease); + } + + foreach(var service in services) + { + var exists = _leases.FirstOrDefault(l => l.HostAndPort.ToString() == service.HostAndPort.ToString()); + + if(exists == null) + { + _leases.Add(new Lease(service.HostAndPort, 0)); + } + } + } + else + { + foreach(var service in services) + { + _leases.Add(new Lease(service.HostAndPort, 0)); + } + } + + return new OkResponse(); + } + } + + public class Lease + { + public Lease(HostAndPort hostAndPort, int connections) + { + HostAndPort = hostAndPort; + Connections = connections; + } + public HostAndPort HostAndPort {get;private set;} + public int Connections {get;private set;} + } + + public class ServicesAreNullError : Error + { + public ServicesAreNullError(string message) + : base(message, OcelotErrorCode.ServicesAreNullError) + { + } + } + + public class ServicesAreEmptyError : Error + { + public ServicesAreEmptyError(string message) + : base(message, OcelotErrorCode.ServicesAreEmptyError) + { + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/RoundRobinTests.cs b/test/Ocelot.UnitTests/RoundRobinTests.cs index 82689b62..5b7da070 100644 --- a/test/Ocelot.UnitTests/RoundRobinTests.cs +++ b/test/Ocelot.UnitTests/RoundRobinTests.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using System.Diagnostics; +using Ocelot.Responses; using Ocelot.Values; using Shouldly; +using TestStack.BDDfy; using Xunit; namespace Ocelot.UnitTests @@ -10,6 +12,7 @@ namespace Ocelot.UnitTests { private readonly RoundRobin _roundRobin; private readonly List _hostAndPorts; + private Response _hostAndPort; public RoundRobinTests() { @@ -26,12 +29,13 @@ namespace Ocelot.UnitTests [Fact] public void should_get_next_address() { - var address = _roundRobin.Next(); - address.ShouldBe(_hostAndPorts[0]); - address = _roundRobin.Next(); - address.ShouldBe(_hostAndPorts[1]); - address = _roundRobin.Next(); - address.ShouldBe(_hostAndPorts[2]); + this.Given(x => x.GivenIGetTheNextAddress()) + .Then(x => x.ThenTheNextAddressIndexIs(0)) + .Given(x => x.GivenIGetTheNextAddress()) + .Then(x => x.ThenTheNextAddressIndexIs(1)) + .Given(x => x.GivenIGetTheNextAddress()) + .Then(x => x.ThenTheNextAddressIndexIs(2)) + .BDDfy(); } [Fact] @@ -41,19 +45,30 @@ namespace Ocelot.UnitTests while (stopWatch.ElapsedMilliseconds < 1000) { - var address = _roundRobin.Next(); - address.ShouldBe(_hostAndPorts[0]); - address = _roundRobin.Next(); - address.ShouldBe(_hostAndPorts[1]); - address = _roundRobin.Next(); - address.ShouldBe(_hostAndPorts[2]); + var address = _roundRobin.Lease(); + address.Data.ShouldBe(_hostAndPorts[0]); + address = _roundRobin.Lease(); + address.Data.ShouldBe(_hostAndPorts[1]); + address = _roundRobin.Lease(); + address.Data.ShouldBe(_hostAndPorts[2]); } } + + private void GivenIGetTheNextAddress() + { + _hostAndPort = _roundRobin.Lease(); + } + + private void ThenTheNextAddressIndexIs(int index) + { + _hostAndPort.Data.ShouldBe(_hostAndPorts[index]); + } } public interface ILoadBalancer { - HostAndPort Next(); + Response Lease(); + Response Release(HostAndPort hostAndPort); } public class RoundRobin : ILoadBalancer @@ -66,7 +81,7 @@ namespace Ocelot.UnitTests _hostAndPorts = hostAndPorts; } - public HostAndPort Next() + public Response Lease() { if (_last >= _hostAndPorts.Count) { @@ -75,7 +90,12 @@ namespace Ocelot.UnitTests var next = _hostAndPorts[_last]; _last++; - return next; + return new OkResponse(next); + } + + public Response Release(HostAndPort hostAndPort) + { + return new OkResponse(); } } } diff --git a/test/Ocelot.UnitTests/ServiceRegistryTests.cs b/test/Ocelot.UnitTests/ServiceRegistryTests.cs index 8866f1ae..27987d42 100644 --- a/test/Ocelot.UnitTests/ServiceRegistryTests.cs +++ b/test/Ocelot.UnitTests/ServiceRegistryTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Ocelot.Values; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -21,7 +22,7 @@ namespace Ocelot.UnitTests [Fact] public void should_register_service() { - this.Given(x => x.GivenAServiceToRegister("product", "localhost:5000")) + this.Given(x => x.GivenAServiceToRegister("product", "localhost:5000", 80)) .When(x => x.WhenIRegisterTheService()) .Then(x => x.ThenTheServiceIsRegistered()) .BDDfy(); @@ -29,7 +30,7 @@ namespace Ocelot.UnitTests public void should_lookup_service() { - this.Given(x => x.GivenAServiceIsRegistered("product", "localhost:600")) + this.Given(x => x.GivenAServiceIsRegistered("product", "localhost:600", 80)) .When(x => x.WhenILookupTheService("product")) .Then(x => x.ThenTheServiceDetailsAreReturned()) .BDDfy(); @@ -37,7 +38,8 @@ namespace Ocelot.UnitTests private void ThenTheServiceDetailsAreReturned() { - _services[0].Address.ShouldBe(_service.Address); + _services[0].HostAndPort.DownstreamHost.ShouldBe(_service.HostAndPort.DownstreamHost); + _services[0].HostAndPort.DownstreamPort.ShouldBe(_service.HostAndPort.DownstreamPort); _services[0].Name.ShouldBe(_service.Name); } @@ -46,15 +48,15 @@ namespace Ocelot.UnitTests _services = _serviceRegistry.Lookup(name); } - private void GivenAServiceIsRegistered(string name, string address) + private void GivenAServiceIsRegistered(string name, string address, int port) { - _service = new Service(name, address); + _service = new Service(name, new HostAndPort(address, port)); _serviceRepository.Set(_service); } - private void GivenAServiceToRegister(string name, string address) + private void GivenAServiceToRegister(string name, string address, int port) { - _service = new Service(name, address); + _service = new Service(name, new HostAndPort(address, port)); } private void WhenIRegisterTheService() @@ -65,7 +67,8 @@ namespace Ocelot.UnitTests private void ThenTheServiceIsRegistered() { var serviceNameAndAddress = _serviceRepository.Get(_service.Name); - serviceNameAndAddress[0].Address.ShouldBe(_service.Address); + serviceNameAndAddress[0].HostAndPort.DownstreamHost.ShouldBe(_service.HostAndPort.DownstreamHost); + serviceNameAndAddress[0].HostAndPort.DownstreamPort.ShouldBe(_service.HostAndPort.DownstreamPort); serviceNameAndAddress[0].Name.ShouldBe(_service.Name); } } @@ -96,13 +99,13 @@ namespace Ocelot.UnitTests public class Service { - public Service(string name, string address) + public Service(string name, HostAndPort hostAndPort) { Name = name; - Address = address; + HostAndPort = hostAndPort; } public string Name {get; private set;} - public string Address {get; private set;} + public HostAndPort HostAndPort {get; private set;} } public interface IServiceRepository From e747d3386a63bb935eef6d25bb9554b2caf09995 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 29 Jan 2017 13:00:50 +0000 Subject: [PATCH 015/113] #20 - add release scripts --- Ocelot.sln | 2 + build.cake | 141 ++++++++++++++++++++++++++++++++++++++++++++++++---- release.ps1 | 1 + 3 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 release.ps1 diff --git a/Ocelot.sln b/Ocelot.sln index c1f647af..3bc1f60a 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -19,6 +19,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Ocelot.nuspec = Ocelot.nuspec push-to-nuget.bat = push-to-nuget.bat README.md = README.md + release.cake = release.cake + release.ps1 = release.ps1 run-acceptance-tests.bat = run-acceptance-tests.bat run-benchmarks.bat = run-benchmarks.bat run-tests.bat = run-tests.bat diff --git a/build.cake b/build.cake index 513f99dd..73dbae18 100644 --- a/build.cake +++ b/build.cake @@ -3,6 +3,7 @@ #tool "nuget:?package=ReportGenerator" #tool "nuget:?package=GitReleaseNotes" #addin nuget:?package=Cake.DoInDirectory +#addin "Cake.Json" var target = Argument("target", "Default"); var artifactsDir = Directory("artifacts"); @@ -25,20 +26,32 @@ var unitTestAssemblies = @"./test/Ocelot.UnitTests"; var artifactsForAcceptanceTestsDir = artifactsDir + Directory("AcceptanceTests"); var acceptanceTestAssemblies = @"./test/Ocelot.AcceptanceTests"; -//benchmark testing +// benchmark testing var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests"); var benchmarkTestAssemblies = @"./test/Ocelot.Benchmarks"; // packaging -var packagesDir = artifactsDir + Directory("Packages"); var projectJson = "./src/Ocelot/project.json"; - -// release notes +var packagesDir = artifactsDir + Directory("Packages"); var releaseNotesFile = packagesDir + File("releasenotes.md"); +var artifactsFile = packagesDir + File("artifacts.txt"); + +//unstable releases +var publishUnstableBuilds = true; +var nugetFeedUnstableKey = EnvironmentVariable("nuget-apikey-unstable"); +var nugetFeedUnstableUploadUrl = "https://www.myget.org/F/ocelot-unstable/api/v2/package"; +var nugetFeedUnstableSymbolsUploadUrl = "https://www.myget.org/F/ocelot-unstable/symbols/api/v2/package"; + +//stable releases +var releaseTag = ""; +var nugetFeedStableKey = EnvironmentVariable("nuget-apikey-stable"); +var nugetFeedStableUploadUrl = "https://www.myget.org/F/ocelot-stable/api/v2/package"; +var nugetFeedStableSymbolsUploadUrl = "https://www.myget.org/F/ocelot-stable/symbols/api/v2/package"; Task("Default") .IsDependentOn("RunTests") - .IsDependentOn("Package") + .IsDependentOn("CreatePackages") + .IsDependentOn("ReleasePackagesToUnstableFeed") .Does(() => { }); @@ -56,7 +69,7 @@ Task("Clean") Task("Version") .Does(() => { - var nugetVersion = GetVersion(); + var nugetVersion = GetNuGetVersionForCommit(); Information("SemVer version number: " + nugetVersion); if (AppVeyor.IsRunningOnAppVeyor) @@ -135,7 +148,7 @@ Task("RunTests") { }); -Task("Package") +Task("CreatePackages") .Does(() => { EnsureDirectoryExists(packagesDir); @@ -150,7 +163,7 @@ Task("Package") DotNetCorePack(projectJson, settings); - System.IO.File.WriteAllLines(packagesDir + File("artifacts"), new[]{ + System.IO.File.WriteAllLines(artifactsFile, new[]{ "nuget:Ocelot." + buildVersion + ".nupkg", "nugetSymbols:Ocelot." + buildVersion + ".symbols.nupkg", "releaseNotes:releasenotes.md" @@ -167,9 +180,71 @@ Task("Package") } }); +Task("ReleasePackagesToUnstableFeed") + .IsDependentOn("CreatePackages") + .Does(() => + { + PublishPackages(nugetFeedUnstableKey, nugetFeedUnstableUploadUrl, nugetFeedUnstableSymbolsUploadUrl); + }); + +Task("EnsureStableReleaseRequirements") + .Does(() => + { + if (!AppVeyor.IsRunningOnAppVeyor) + { + throw new Exception("Stable release should happen via appveyor"); + } + + var isTag = + AppVeyor.Environment.Repository.Tag.IsTag && + !string.IsNullOrWhiteSpace(AppVeyor.Environment.Repository.Tag.Name); + + if (!isTag) + { + throw new Exception("Stable release should happen from a published GitHub release"); + } + }); + +Task("UpdateVersionInfo") + .IsDependentOn("EnsureStableReleaseRequirements") + .Does(() => + { + releaseTag = AppVeyor.Environment.Repository.Tag.Name; + AppVeyor.UpdateBuildVersion(releaseTag); + }); + +Task("DownloadGitHubReleaseArtifacts") + .IsDependentOn("UpdateVersionInfo") + .Does(() => + { + EnsureDirectoryExists(packagesDir); + + var assets_url = ParseJson(GetResource("https://api.github.com/repos/binarymash/pipelinetesting/releases/tags/" + releaseTag)) + .GetValue("assets_url") + .Value(); + + foreach(var asset in DeserializeJson(GetResource(assets_url))) + { + var file = packagesDir + File(asset.Value("name")); + Information("Downloading " + file); + DownloadFile(asset.Value("browser_download_url"), file); + } + }); + +Task("ReleasePackagesToStableFeed") + .IsDependentOn("DownloadGitHubReleaseArtifacts") + .Does(() => + { + PublishPackages(nugetFeedStableKey, nugetFeedStableUploadUrl, nugetFeedStableSymbolsUploadUrl); + }); + +Task("Release") + .IsDependentOn("ReleasePackagesToStableFeed"); + RunTarget(target); -private string GetVersion() +/// Gets nuique nuget version for this commit +private string GetNuGetVersionForCommit() { GitVersion(new GitVersionSettings{ UpdateAssemblyInfo = false, @@ -180,9 +255,11 @@ private string GetVersion() return versionInfo.NuGetVersion; } +/// Updates project version in all of our projects private void PersistVersion(string version) { Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, version)); + var projectJsonFiles = GetFiles("./**/project.json"); foreach(var projectJsonFile in projectJsonFiles) @@ -198,6 +275,7 @@ private void PersistVersion(string version) } } +/// generates release notes based on issues closed in GitHub since the last release private void GenerateReleaseNotes() { Information("Generating release notes at " + releaseNotesFile); @@ -215,4 +293,49 @@ private void GenerateReleaseNotes() { throw new Exception("Failed to generate release notes"); } +} + +/// Publishes code and symbols packages to nuget feed, based on contents of artifacts file +private void PublishPackages(string feedApiKey, string codeFeedUrl, string symbolFeedUrl) +{ + var artifacts = System.IO.File + .ReadAllLines(artifactsFile) + .Select(l => l.Split(':')) + .ToDictionary(v => v[0], v => v[1]); + + var codePackage = packagesDir + File(artifacts["nuget"]); + var symbolsPackage = packagesDir + File(artifacts["nugetSymbols"]); + + NuGetPush( + codePackage, + new NuGetPushSettings { + ApiKey = feedApiKey, + Source = codeFeedUrl + }); + + NuGetPush( + symbolsPackage, + new NuGetPushSettings { + ApiKey = feedApiKey, + Source = symbolFeedUrl + }); + +} + +/// gets the resource from the specified url +private string GetResource(string url) +{ + Information("Getting resource from " + url); + + var assetsRequest = System.Net.WebRequest.CreateHttp(url); + assetsRequest.Method = "GET"; + assetsRequest.Accept = "application/vnd.github.v3+json"; + assetsRequest.UserAgent = "BuildScript"; + + using (var assetsResponse = assetsRequest.GetResponse()) + { + var assetsStream = assetsResponse.GetResponseStream(); + var assetsReader = new StreamReader(assetsStream); + return assetsReader.ReadToEnd(); + } } \ No newline at end of file diff --git a/release.ps1 b/release.ps1 new file mode 100644 index 00000000..6cf4c66b --- /dev/null +++ b/release.ps1 @@ -0,0 +1 @@ +./build.ps1 -target Release \ No newline at end of file From b50d2b1acae5ea8c420b155fc87023b7c38e7afa Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 29 Jan 2017 13:17:57 +0000 Subject: [PATCH 016/113] #20 - remove obsolete file --- Ocelot.sln | 1 - 1 file changed, 1 deletion(-) diff --git a/Ocelot.sln b/Ocelot.sln index 3bc1f60a..282c7120 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -19,7 +19,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Ocelot.nuspec = Ocelot.nuspec push-to-nuget.bat = push-to-nuget.bat README.md = README.md - release.cake = release.cake release.ps1 = release.ps1 run-acceptance-tests.bat = run-acceptance-tests.bat run-benchmarks.bat = run-benchmarks.bat From 0172950dab730150b86336f525ffffa3265d321c Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 29 Jan 2017 13:47:59 +0000 Subject: [PATCH 017/113] #20 - added release notes to repo --- ReleaseNotes.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 ReleaseNotes.md diff --git a/ReleaseNotes.md b/ReleaseNotes.md new file mode 100644 index 00000000..a647c6c3 --- /dev/null +++ b/ReleaseNotes.md @@ -0,0 +1 @@ +No issues closed since last release \ No newline at end of file From e4a22e57d715506b1f55e073d0b01ba5578c4e48 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 29 Jan 2017 14:10:49 +0000 Subject: [PATCH 018/113] #20 - fix download of github release tags --- build.cake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.cake b/build.cake index 73dbae18..0781fcfc 100644 --- a/build.cake +++ b/build.cake @@ -43,6 +43,7 @@ var nugetFeedUnstableUploadUrl = "https://www.myget.org/F/ocelot-unstable/api/v2 var nugetFeedUnstableSymbolsUploadUrl = "https://www.myget.org/F/ocelot-unstable/symbols/api/v2/package"; //stable releases +var tagsUrl = "https://api.github.com/repos/binarymash/ocelot/releases/tags/"; var releaseTag = ""; var nugetFeedStableKey = EnvironmentVariable("nuget-apikey-stable"); var nugetFeedStableUploadUrl = "https://www.myget.org/F/ocelot-stable/api/v2/package"; @@ -219,7 +220,8 @@ Task("DownloadGitHubReleaseArtifacts") { EnsureDirectoryExists(packagesDir); - var assets_url = ParseJson(GetResource("https://api.github.com/repos/binarymash/pipelinetesting/releases/tags/" + releaseTag)) + var releaseUrl = tagsUrl + releaseTag; + var assets_url = ParseJson(GetResource(releaseUrl)) .GetValue("assets_url") .Value(); From 33854067d44c24937cf1f4b633bf8fd397e59c5a Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 29 Jan 2017 15:14:31 +0000 Subject: [PATCH 019/113] #20 - Tidy up. Update command line scripts. Fix running benchmarks. --- Ocelot.sln | 10 +++--- build-and-release-unstable.ps1 | 1 + build-and-run-tests.bat | 2 -- build-and-run-tests.ps1 | 1 + build.bat | 8 ----- build.cake | 56 ++++++++++++++++++---------------- run-acceptance-tests.bat | 8 ----- run-acceptance-tests.ps1 | 1 + run-benchmarks.bat | 15 --------- run-benchmarks.ps1 | 1 + run-tests.bat | 2 -- run-unit-tests.bat | 8 ----- run-unit-tests.ps1 | 1 + test.txt | 1 - 14 files changed, 39 insertions(+), 76 deletions(-) create mode 100644 build-and-release-unstable.ps1 delete mode 100755 build-and-run-tests.bat create mode 100644 build-and-run-tests.ps1 delete mode 100755 build.bat delete mode 100755 run-acceptance-tests.bat create mode 100644 run-acceptance-tests.ps1 delete mode 100644 run-benchmarks.bat create mode 100644 run-benchmarks.ps1 delete mode 100755 run-tests.bat delete mode 100755 run-unit-tests.bat create mode 100644 run-unit-tests.ps1 delete mode 100644 test.txt diff --git a/Ocelot.sln b/Ocelot.sln index 282c7120..2f8fd206 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -9,21 +9,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .gitignore = .gitignore appveyor.yml = appveyor.yml - build-and-run-tests.bat = build-and-run-tests.bat - build.bat = build.bat + build-and-run-tests.ps1 = build-and-run-tests.ps1 build.cake = build.cake build.ps1 = build.ps1 configuration-explanation.txt = configuration-explanation.txt global.json = global.json LICENSE.md = LICENSE.md Ocelot.nuspec = Ocelot.nuspec - push-to-nuget.bat = push-to-nuget.bat README.md = README.md release.ps1 = release.ps1 - run-acceptance-tests.bat = run-acceptance-tests.bat + run-acceptance-tests.ps1 = run-acceptance-tests.ps1 run-benchmarks.bat = run-benchmarks.bat - run-tests.bat = run-tests.bat - run-unit-tests.bat = run-unit-tests.bat + run-benchmarks.ps1 = run-benchmarks.ps1 + run-unit-tests.ps1 = run-unit-tests.ps1 EndProjectSection EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot", "src\Ocelot\Ocelot.xproj", "{D6DF4206-0DBA-41D8-884D-C3E08290FDBB}" diff --git a/build-and-release-unstable.ps1 b/build-and-release-unstable.ps1 new file mode 100644 index 00000000..9a29f95f --- /dev/null +++ b/build-and-release-unstable.ps1 @@ -0,0 +1 @@ +./build.ps1 -target build-full \ No newline at end of file diff --git a/build-and-run-tests.bat b/build-and-run-tests.bat deleted file mode 100755 index 764682b6..00000000 --- a/build-and-run-tests.bat +++ /dev/null @@ -1,2 +0,0 @@ -./run-tests.bat -./build.bat \ No newline at end of file diff --git a/build-and-run-tests.ps1 b/build-and-run-tests.ps1 new file mode 100644 index 00000000..f82502e5 --- /dev/null +++ b/build-and-run-tests.ps1 @@ -0,0 +1 @@ +./build.ps1 -target RunTests \ No newline at end of file diff --git a/build.bat b/build.bat deleted file mode 100755 index 656515d4..00000000 --- a/build.bat +++ /dev/null @@ -1,8 +0,0 @@ -echo ------------------------- - -echo Building Ocelot -dotnet restore src/Ocelot -dotnet build src/Ocelot -c Release - - - diff --git a/build.cake b/build.cake index 0781fcfc..3fbeeaf7 100644 --- a/build.cake +++ b/build.cake @@ -2,21 +2,15 @@ #tool "nuget:?package=OpenCover" #tool "nuget:?package=ReportGenerator" #tool "nuget:?package=GitReleaseNotes" -#addin nuget:?package=Cake.DoInDirectory -#addin "Cake.Json" +#addin "nuget:?package=Cake.DoInDirectory" +#addin "nuget:?package=Cake.Json" -var target = Argument("target", "Default"); -var artifactsDir = Directory("artifacts"); - -Information("target is " +target); - -// versioning -var committedVersion = "0.0.0-dev"; -var buildVersion = committedVersion; - -//compile +// compile var compileConfig = Argument("configuration", "Release"); -Information("Build configuration is " + compileConfig); +var projectJson = "./src/Ocelot/project.json"; + +// build artifacts +var artifactsDir = Directory("artifacts"); // unit testing var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests"); @@ -31,32 +25,42 @@ var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests"); var benchmarkTestAssemblies = @"./test/Ocelot.Benchmarks"; // packaging -var projectJson = "./src/Ocelot/project.json"; var packagesDir = artifactsDir + Directory("Packages"); var releaseNotesFile = packagesDir + File("releasenotes.md"); var artifactsFile = packagesDir + File("artifacts.txt"); -//unstable releases -var publishUnstableBuilds = true; +// unstable releases var nugetFeedUnstableKey = EnvironmentVariable("nuget-apikey-unstable"); var nugetFeedUnstableUploadUrl = "https://www.myget.org/F/ocelot-unstable/api/v2/package"; var nugetFeedUnstableSymbolsUploadUrl = "https://www.myget.org/F/ocelot-unstable/symbols/api/v2/package"; -//stable releases +// stable releases var tagsUrl = "https://api.github.com/repos/binarymash/ocelot/releases/tags/"; -var releaseTag = ""; var nugetFeedStableKey = EnvironmentVariable("nuget-apikey-stable"); var nugetFeedStableUploadUrl = "https://www.myget.org/F/ocelot-stable/api/v2/package"; var nugetFeedStableSymbolsUploadUrl = "https://www.myget.org/F/ocelot-stable/symbols/api/v2/package"; -Task("Default") - .IsDependentOn("RunTests") - .IsDependentOn("CreatePackages") - .IsDependentOn("ReleasePackagesToUnstableFeed") - .Does(() => - { - }); +// internal build variables - don't change these. +var releaseTag = ""; +var buildVersion = committedVersion; +var committedVersion = "0.0.0-dev"; +var target = Argument("target", "Default"); + +Information("target is " +target); +Information("Build configuration is " + compileConfig); + +Task("Default") + .IsDependentOn("Build"); + +Task("Build") + .IsDependentOn("RunTests") + .IsDependentOn("CreatePackages"); + +Task("BuildAndReleaseUnstable") + .IsDependentOn("Build") + .IsDependentOn("ReleasePackagesToUnstableFeed"); + Task("Clean") .Does(() => { @@ -138,7 +142,7 @@ Task("RunBenchmarkTests") DoInDirectory(benchmarkTestAssemblies, () => { - DotNetCoreRun(".", "--args", buildSettings); + DotNetCoreRun(".", "", buildSettings); }); }); diff --git a/run-acceptance-tests.bat b/run-acceptance-tests.bat deleted file mode 100755 index ba8a3489..00000000 --- a/run-acceptance-tests.bat +++ /dev/null @@ -1,8 +0,0 @@ -echo Running Ocelot.AcceptanceTests -cd test/Ocelot.AcceptanceTests/ -dotnet restore -dotnet test -cd ../../ - -echo Restoring Ocelot.ManualTest -dotnet restore test/Ocelot.ManualTest/ \ No newline at end of file diff --git a/run-acceptance-tests.ps1 b/run-acceptance-tests.ps1 new file mode 100644 index 00000000..480e1d4c --- /dev/null +++ b/run-acceptance-tests.ps1 @@ -0,0 +1 @@ +./build -target RunAcceptanceTests \ No newline at end of file diff --git a/run-benchmarks.bat b/run-benchmarks.bat deleted file mode 100644 index 1376f17a..00000000 --- a/run-benchmarks.bat +++ /dev/null @@ -1,15 +0,0 @@ -echo ------------------------- - -echo Running Ocelot.Benchmarks - -cd test/Ocelot.Benchmarks - -dotnet restore - -dotnet run - -cd ../../ - - - - diff --git a/run-benchmarks.ps1 b/run-benchmarks.ps1 new file mode 100644 index 00000000..e05490fd --- /dev/null +++ b/run-benchmarks.ps1 @@ -0,0 +1 @@ +./build.ps1 -target RunBenchmarkTests \ No newline at end of file diff --git a/run-tests.bat b/run-tests.bat deleted file mode 100755 index 39532229..00000000 --- a/run-tests.bat +++ /dev/null @@ -1,2 +0,0 @@ -./run-unit-tests.bat -./run-acceptance-tests.bat \ No newline at end of file diff --git a/run-unit-tests.bat b/run-unit-tests.bat deleted file mode 100755 index 9ad6a4f2..00000000 --- a/run-unit-tests.bat +++ /dev/null @@ -1,8 +0,0 @@ -echo ------------------------- - -echo Restoring Ocelot -dotnet restore src/Ocelot - -echo Running Ocelot.UnitTests -dotnet restore test/Ocelot.UnitTests/ -dotnet test test/Ocelot.UnitTests/ diff --git a/run-unit-tests.ps1 b/run-unit-tests.ps1 new file mode 100644 index 00000000..0e6a91bd --- /dev/null +++ b/run-unit-tests.ps1 @@ -0,0 +1 @@ +./build.ps1 -target RunUnitTests \ No newline at end of file diff --git a/test.txt b/test.txt deleted file mode 100644 index 30d74d25..00000000 --- a/test.txt +++ /dev/null @@ -1 +0,0 @@ -test \ No newline at end of file From 17dac3d3a2066fb496ada79fb81f0cdeb0a5ebdb Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 29 Jan 2017 16:16:08 +0000 Subject: [PATCH 020/113] #20 - added readme for build and release process. --- build.readme.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 build.readme.md diff --git a/build.readme.md b/build.readme.md new file mode 100644 index 00000000..fccff4d5 --- /dev/null +++ b/build.readme.md @@ -0,0 +1,22 @@ +#1. Overview + +This document summarises the build and release process for the project. The build scripts are written using [Cake](http://cakebuild.net/), and are defined in `./build.cake`. The scripts have been designed to be run by either developers locally or by a build server (currently [AppVeyor](https://www.appveyor.com/)), with minimal logic defined in the build server itself. + +#2. Building + * You'll generally want to run the `./build.ps1` script. This will compile, run unit and acceptance tests and build the output packages locally. Output will got to the `./artifacts` directory. + * You can view the current commit's [SemVer](http://semver.org/) build information by running `./version.ps1`. + * The other `./*.ps1` scripts perform subsets of the build process, if you don't want to run the full build. + * The release process works best with GitFlow branching; this allows us to publish every development commit to an unstable feed with a unique SemVer version, and then choose when to release to a stable feed. + +#3. Release process +This section defines the release process for the maintainers of the project. + * Merge pull requests to the `release` branch. + * Every commit pushed to the Origin repo will kick off the [ocelot-build](https://ci.appveyor.com/project/binarymash/ocelot) project in AppVeyor. This performs the same tasks as the command line build, and in addition pushes the packages to the unstable nuget feed. + * When you're ready for a release, create a release branch. You'll probably want to update the committed `./ReleaseNotes.md` based on the contents of the equivalent file in the `./artifacts` directory. + * When the `release` branch has built successfully in Appveyor, select the build and then Deploy to the `GitHub Release` environment. This will create a new release in GitHub. + * In Github, navigate to the [release](https://github.com/binarymash/Ocelot/releases). Modify the release name and tag as desired. + * When you're ready, publish the release. This will tag the commit with the specified release number. + * The [ocelot-release](https://ci.appveyor.com/project/binarymash/ocelot-wtaj9) project will detect the newly created tag and kick off the release process. This will download the artifacts from GitHub, and publish the packages to the stable nuget feed. + * When you have a final stable release build, merge the `release` branch into `master` and `develop`. Deploy the master branch to github and following the full release process as described above. Don't forget to uncheck the "This is a pre-release" checkbox in GitHub before publishing. + * Note - because the release builds are initiated by tagging a commit, if for some reason a release build fails in AppVeyor you'll need to delete the tag from the repo and republish the release in GitHub. + From 727dbb5f7d23c26ea594cca83da3b1c2315a9125 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 29 Jan 2017 18:19:16 +0000 Subject: [PATCH 021/113] #20 - add gitversion config --- GitVersion.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 GitVersion.yml diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 00000000..05e9ac41 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,4 @@ +mode: ContinuousDelivery +branches: {} +ignore: + sha: [] From 0e92976df8204f76c25712409966025bc352efb5 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 1 Feb 2017 06:40:29 +0000 Subject: [PATCH 022/113] playing around with lb factory --- .../Configuration/Builder/ReRouteBuilder.cs | 9 +- .../Creator/FileOcelotConfigurationCreator.cs | 13 ++- src/Ocelot/Configuration/File/FileReRoute.cs | 1 + src/Ocelot/Configuration/ReRoute.cs | 6 +- .../DownstreamUrlCreatorMiddleware.cs | 2 + .../LoadBalancerFactoryTests.cs | 95 +++++++++++++++++++ test/Ocelot.UnitTests/RoundRobinTests.cs | 8 +- 7 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 1e06a440..ecc0b67f 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -32,12 +32,19 @@ namespace Ocelot.Configuration.Builder private string _downstreamScheme; private string _downstreamHost; private int _dsPort; + private string _loadBalancer; public ReRouteBuilder() { _additionalScopes = new List(); } + public ReRouteBuilder WithLoadBalancer(string loadBalancer) + { + _loadBalancer = loadBalancer; + return this; + } + public ReRouteBuilder WithDownstreamScheme(string downstreamScheme) { _downstreamScheme = downstreamScheme; @@ -200,7 +207,7 @@ namespace Ocelot.Configuration.Builder _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _serviceName, - _useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, downstreamHostFunc, _downstreamScheme); + _useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, downstreamHostFunc, _downstreamScheme, _loadBalancer); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 8884f0d9..d09d738c 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -97,6 +97,13 @@ namespace Ocelot.Configuration.Creator && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); + //can we do the logic in this func to get the host and port from the load balancer? + //lBfactory.GetLbForThisDownstreamTemplate + //use it in the func to get the next host and port? + //how do we release it? cant callback, could access the lb and release later? + + //ideal world we would get the host and port, then make the request using it, then release the connection to the lb + Func downstreamHostAndPortFunc = () => new HostAndPort(reRoute.DownstreamHost.Trim('/'), reRoute.DownstreamPort); if (isAuthenticated) @@ -116,7 +123,8 @@ namespace Ocelot.Configuration.Creator reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme); + globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme, + reRoute.LoadBalancer); } return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate, @@ -125,7 +133,8 @@ namespace Ocelot.Configuration.Creator reRoute.RouteClaimsRequirement, isAuthorised, new List(), requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme); + globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme, + reRoute.LoadBalancer); } private string BuildUpstreamTemplate(FileReRoute reRoute) diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index a653224a..0d0fc7bd 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -29,5 +29,6 @@ namespace Ocelot.Configuration.File public string DownstreamScheme {get;set;} public string DownstreamHost {get;set;} public int DownstreamPort { get; set; } + public string LoadBalancer {get;set;} } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 960374cc..9faab4ab 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -10,8 +10,10 @@ namespace Ocelot.Configuration bool isAuthenticated, AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries, string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery, - string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func downstreamHostAndPort, string downstreamScheme) + string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func downstreamHostAndPort, + string downstreamScheme, string loadBalancer) { + LoadBalancer = loadBalancer; DownstreamPathTemplate = downstreamPathTemplate; UpstreamTemplate = upstreamTemplate; UpstreamHttpMethod = upstreamHttpMethod; @@ -36,7 +38,6 @@ namespace Ocelot.Configuration DownstreamHostAndPort = downstreamHostAndPort; DownstreamScheme = downstreamScheme; } - public DownstreamPathTemplate DownstreamPathTemplate { get; private set; } public string UpstreamTemplate { get; private set; } public string UpstreamTemplatePattern { get; private set; } @@ -57,5 +58,6 @@ namespace Ocelot.Configuration public string ServiceDiscoveryAddress { get; private set;} public Func DownstreamHostAndPort {get;private set;} public string DownstreamScheme {get;private set;} + public string LoadBalancer {get;private set;} } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index b4d73c7c..957acb8e 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -51,6 +51,8 @@ namespace Ocelot.DownstreamUrlCreator.Middleware //lease the next address from the lb + //this could return the load balancer which you call next on, that gives you the host and port then you can call release in a try catch + //and if the call works? var dsHostAndPort = DownstreamRoute.ReRoute.DownstreamHostAndPort(); var dsUrl = _urlBuilder.Build(dsPath.Data.Value, dsScheme, dsHostAndPort); diff --git a/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs new file mode 100644 index 00000000..6d2a3a59 --- /dev/null +++ b/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs @@ -0,0 +1,95 @@ +using System; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Responses; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests +{ + public class LoadBalancerFactoryTests + { + private ReRoute _reRoute; + private LoadBalancerFactory _factory; + private ILoadBalancer _result; + + public LoadBalancerFactoryTests() + { + _factory = new LoadBalancerFactory(); + } + + [Fact] + public void should_return_no_load_balancer() + { + var reRoute = new ReRouteBuilder() + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_round_robin_load_balancer() + { + var reRoute = new ReRouteBuilder() + .WithLoadBalancer("RoundRobin") + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + private void GivenAReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGetTheLoadBalancer() + { + _result = _factory.Get(_reRoute); + } + + private void ThenTheLoadBalancerIsReturned() + { + _result.ShouldBeOfType(); + } + } + + public class NoLoadBalancer : ILoadBalancer + { + Response ILoadBalancer.Lease() + { + throw new NotImplementedException(); + } + + Response ILoadBalancer.Release(HostAndPort hostAndPort) + { + throw new NotImplementedException(); + } + } + + public interface ILoadBalancerFactory + { + ILoadBalancer Get(ReRoute reRoute); + } + + public class LoadBalancerFactory : ILoadBalancerFactory + { + public ILoadBalancer Get(ReRoute reRoute) + { + switch (reRoute.LoadBalancer) + { + case "RoundRobin": + return new RoundRobinLoadBalancer(null); + default: + return new NoLoadBalancer(); + } + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/RoundRobinTests.cs b/test/Ocelot.UnitTests/RoundRobinTests.cs index 5b7da070..f1271460 100644 --- a/test/Ocelot.UnitTests/RoundRobinTests.cs +++ b/test/Ocelot.UnitTests/RoundRobinTests.cs @@ -10,7 +10,7 @@ namespace Ocelot.UnitTests { public class RoundRobinTests { - private readonly RoundRobin _roundRobin; + private readonly RoundRobinLoadBalancer _roundRobin; private readonly List _hostAndPorts; private Response _hostAndPort; @@ -23,7 +23,7 @@ namespace Ocelot.UnitTests new HostAndPort("127.0.0.1", 5001) }; - _roundRobin = new RoundRobin(_hostAndPorts); + _roundRobin = new RoundRobinLoadBalancer(_hostAndPorts); } [Fact] @@ -71,12 +71,12 @@ namespace Ocelot.UnitTests Response Release(HostAndPort hostAndPort); } - public class RoundRobin : ILoadBalancer + public class RoundRobinLoadBalancer : ILoadBalancer { private readonly List _hostAndPorts; private int _last; - public RoundRobin(List hostAndPorts) + public RoundRobinLoadBalancer(List hostAndPorts) { _hostAndPorts = hostAndPorts; } From 24dbb958e34da4f040b150b0b95097b93da54d13 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 1 Feb 2017 19:34:55 +0000 Subject: [PATCH 023/113] plying around with service providers --- .../Creator/FileOcelotConfigurationCreator.cs | 18 ++- .../ServiceDiscovery/IServiceProvider.cs | 10 ++ .../IServiceProviderFactory.cs | 9 ++ .../ServiceDiscovery/NoServiceProvider.cs | 20 +++ .../ServiceProviderFactory.cs | 13 ++ src/Ocelot/Values/Service.cs | 13 ++ test/Ocelot.UnitTests/LeastConnectionTests.cs | 14 +-- .../LoadBalancerFactoryTests.cs | 114 ++++++++++++++++-- .../NoServiceProviderTests.cs | 55 +++++++++ test/Ocelot.UnitTests/RoundRobinTests.cs | 32 ++--- .../ServiceProviderFactoryTests.cs | 50 ++++++++ test/Ocelot.UnitTests/ServiceRegistryTests.cs | 12 +- 12 files changed, 314 insertions(+), 46 deletions(-) create mode 100644 src/Ocelot/ServiceDiscovery/IServiceProvider.cs create mode 100644 src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs create mode 100644 src/Ocelot/ServiceDiscovery/NoServiceProvider.cs create mode 100644 src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs create mode 100644 src/Ocelot/Values/Service.cs create mode 100644 test/Ocelot.UnitTests/NoServiceProviderTests.cs create mode 100644 test/Ocelot.UnitTests/ServiceProviderFactoryTests.cs diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index d09d738c..24c4db72 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -7,6 +7,7 @@ using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; using Ocelot.Responses; +using Ocelot.ServiceDiscovery; using Ocelot.Utilities; using Ocelot.Values; @@ -104,7 +105,22 @@ namespace Ocelot.Configuration.Creator //ideal world we would get the host and port, then make the request using it, then release the connection to the lb - Func downstreamHostAndPortFunc = () => new HostAndPort(reRoute.DownstreamHost.Trim('/'), reRoute.DownstreamPort); + Func downstreamHostAndPortFunc = () => { + + //service provider factory takes the reRoute + //can return no service provider (just use ocelot config) + //can return consol service provider + //returns a service provider + //we call get on the service provider + //could reutrn services from consol or just configuration.json + //this returns a list of services and we take the first one + var hostAndPort = new HostAndPort(reRoute.DownstreamHost.Trim('/'), reRoute.DownstreamPort); + var services = new List(); + var serviceProvider = new NoServiceProvider(services); + var service = serviceProvider.Get(); + var firstHostAndPort = service[0].HostAndPort; + return firstHostAndPort; + }; if (isAuthenticated) { diff --git a/src/Ocelot/ServiceDiscovery/IServiceProvider.cs b/src/Ocelot/ServiceDiscovery/IServiceProvider.cs new file mode 100644 index 00000000..60e428c8 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/IServiceProvider.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Ocelot.Values; + +namespace Ocelot.ServiceDiscovery +{ + public interface IServiceProvider + { + List Get(); + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs new file mode 100644 index 00000000..0043edaa --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration; + +namespace Ocelot.ServiceDiscovery +{ + public interface IServiceProviderFactory + { + Ocelot.ServiceDiscovery.IServiceProvider Get(ReRoute reRoute); + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/NoServiceProvider.cs b/src/Ocelot/ServiceDiscovery/NoServiceProvider.cs new file mode 100644 index 00000000..9f8b0239 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/NoServiceProvider.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Ocelot.Values; + +namespace Ocelot.ServiceDiscovery +{ + public class NoServiceProvider : IServiceProvider + { + private List _services; + + public NoServiceProvider(List services) + { + _services = services; + } + + public List Get() + { + return _services; + } + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs new file mode 100644 index 00000000..583774e7 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs @@ -0,0 +1,13 @@ +using System; +using Ocelot.Configuration; + +namespace Ocelot.ServiceDiscovery +{ + public class ServiceProviderFactory : IServiceProviderFactory + { + public Ocelot.ServiceDiscovery.IServiceProvider Get(ReRoute reRoute) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Values/Service.cs b/src/Ocelot/Values/Service.cs new file mode 100644 index 00000000..104fbc09 --- /dev/null +++ b/src/Ocelot/Values/Service.cs @@ -0,0 +1,13 @@ +namespace Ocelot.Values +{ + public class Service + { + public Service(string name, HostAndPort hostAndPort) + { + Name = name; + HostAndPort = hostAndPort; + } + public string Name {get; private set;} + public HostAndPort HostAndPort {get; private set;} + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/LeastConnectionTests.cs b/test/Ocelot.UnitTests/LeastConnectionTests.cs index 758d1917..ede2105f 100644 --- a/test/Ocelot.UnitTests/LeastConnectionTests.cs +++ b/test/Ocelot.UnitTests/LeastConnectionTests.cs @@ -14,7 +14,7 @@ namespace Ocelot.UnitTests { private HostAndPort _hostAndPort; private Response _result; - private LeastConnection _leastConnection; + private LeastConnectionLoadBalancer _leastConnection; private List _services; public LeastConnectionTests() @@ -53,7 +53,7 @@ namespace Ocelot.UnitTests }; _services = availableServices; - _leastConnection = new LeastConnection(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => _services, serviceName); var response = _leastConnection.Lease(); @@ -80,7 +80,7 @@ namespace Ocelot.UnitTests }; _services = availableServices; - _leastConnection = new LeastConnection(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => _services, serviceName); var response = _leastConnection.Lease(); @@ -111,7 +111,7 @@ namespace Ocelot.UnitTests }; _services = availableServices; - _leastConnection = new LeastConnection(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => _services, serviceName); var response = _leastConnection.Lease(); @@ -178,7 +178,7 @@ namespace Ocelot.UnitTests private void GivenTheLoadBalancerStarts(List services, string serviceName) { _services = services; - _leastConnection = new LeastConnection(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => _services, serviceName); } private void WhenTheLoadBalancerStarts(List services, string serviceName) @@ -203,13 +203,13 @@ namespace Ocelot.UnitTests } } - public class LeastConnection : ILoadBalancer + public class LeastConnectionLoadBalancer : ILoadBalancer { private Func> _services; private List _leases; private string _serviceName; - public LeastConnection(Func> services, string serviceName) + public LeastConnectionLoadBalancer(Func> services, string serviceName) { _services = services; _serviceName = serviceName; diff --git a/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs index 6d2a3a59..12f59ceb 100644 --- a/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancerFactoryTests.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Linq; +using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Responses; @@ -14,10 +17,12 @@ namespace Ocelot.UnitTests private ReRoute _reRoute; private LoadBalancerFactory _factory; private ILoadBalancer _result; - + private Mock _serviceProvider; + public LoadBalancerFactoryTests() { - _factory = new LoadBalancerFactory(); + _serviceProvider = new Mock(); + _factory = new LoadBalancerFactory(_serviceProvider.Object); } [Fact] @@ -36,8 +41,8 @@ namespace Ocelot.UnitTests public void should_return_round_robin_load_balancer() { var reRoute = new ReRouteBuilder() - .WithLoadBalancer("RoundRobin") - .Build(); + .WithLoadBalancer("RoundRobin") + .Build(); this.Given(x => x.GivenAReRoute(reRoute)) .When(x => x.WhenIGetTheLoadBalancer()) @@ -45,6 +50,38 @@ namespace Ocelot.UnitTests .BDDfy(); } + [Fact] + public void should_return_round_least_connection_balancer() + { + var reRoute = new ReRouteBuilder() + .WithLoadBalancer("LeastConnection") + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheLoadBalancerIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_call_service_provider() + { + var reRoute = new ReRouteBuilder() + .WithLoadBalancer("RoundRobin") + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheLoadBalancer()) + .Then(x => x.ThenTheServiceProviderIsCalledCorrectly(reRoute)) + .BDDfy(); + } + + private void ThenTheServiceProviderIsCalledCorrectly(ReRoute reRoute) + { + _serviceProvider + .Verify(x => x.Get(), Times.Once); + } + private void GivenAReRoute(ReRoute reRoute) { _reRoute = reRoute; @@ -61,16 +98,62 @@ namespace Ocelot.UnitTests } } - public class NoLoadBalancer : ILoadBalancer + public class NoLoadBalancerTests { - Response ILoadBalancer.Lease() + private List _services; + private NoLoadBalancer _loadBalancer; + private Response _result; + + [Fact] + public void should_return_host_and_port() { - throw new NotImplementedException(); + var hostAndPort = new HostAndPort("127.0.0.1", 80); + + var services = new List + { + new Service("product", hostAndPort) + }; + this.Given(x => x.GivenServices(services)) + .When(x => x.WhenIGetTheNextHostAndPort()) + .Then(x => x.ThenTheHostAndPortIs(hostAndPort)) + .BDDfy(); } - Response ILoadBalancer.Release(HostAndPort hostAndPort) + private void GivenServices(List services) { - throw new NotImplementedException(); + _services = services; + } + + private void WhenIGetTheNextHostAndPort() + { + _loadBalancer = new NoLoadBalancer(_services); + _result = _loadBalancer.Lease(); + } + + private void ThenTheHostAndPortIs(HostAndPort expected) + { + _result.Data.ShouldBe(expected); + } + } + + public class NoLoadBalancer : ILoadBalancer + { + private List _services; + + public NoLoadBalancer(List services) + { + _services = services; + } + + public Response Lease() + { + var service = _services.FirstOrDefault(); + return new OkResponse(service.HostAndPort); + } + + public Response Release(HostAndPort hostAndPort) + { + return new OkResponse(); } } @@ -81,14 +164,23 @@ namespace Ocelot.UnitTests public class LoadBalancerFactory : ILoadBalancerFactory { + private Ocelot.ServiceDiscovery.IServiceProvider _serviceProvider; + + public LoadBalancerFactory(Ocelot.ServiceDiscovery.IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + public ILoadBalancer Get(ReRoute reRoute) { switch (reRoute.LoadBalancer) { case "RoundRobin": - return new RoundRobinLoadBalancer(null); + return new RoundRobinLoadBalancer(_serviceProvider.Get()); + case "LeastConnection": + return new LeastConnectionLoadBalancer(() => _serviceProvider.Get(), reRoute.ServiceName); default: - return new NoLoadBalancer(); + return new NoLoadBalancer(_serviceProvider.Get()); } } } diff --git a/test/Ocelot.UnitTests/NoServiceProviderTests.cs b/test/Ocelot.UnitTests/NoServiceProviderTests.cs new file mode 100644 index 00000000..f85ef13c --- /dev/null +++ b/test/Ocelot.UnitTests/NoServiceProviderTests.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using Ocelot.Configuration; +using Ocelot.ServiceDiscovery; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests +{ + public class NoServiceProviderTests + { + private NoServiceProvider _serviceProvider; + private HostAndPort _hostAndPort; + private List _result; + private List _expected; + + [Fact] + public void should_return_services() + { + var hostAndPort = new HostAndPort("127.0.0.1", 80); + + var services = new List + { + new Service("product", hostAndPort) + }; + + this.Given(x => x.GivenAHostAndPort(services)) + .When(x => x.WhenIGetTheService()) + .Then(x => x.ThenTheFollowingIsReturned(services)) + .BDDfy(); + } + + private void GivenAHostAndPort(List services) + { + _expected = services; + } + + private void WhenIGetTheService() + { + _serviceProvider = new NoServiceProvider(_expected); + _result = _serviceProvider.Get(); + } + + private void ThenTheFollowingIsReturned(List services) + { + _result[0].HostAndPort.DownstreamHost.ShouldBe(services[0].HostAndPort.DownstreamHost); + + _result[0].HostAndPort.DownstreamPort.ShouldBe(services[0].HostAndPort.DownstreamPort); + + _result[0].Name.ShouldBe(services[0].Name); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/RoundRobinTests.cs b/test/Ocelot.UnitTests/RoundRobinTests.cs index f1271460..39d33441 100644 --- a/test/Ocelot.UnitTests/RoundRobinTests.cs +++ b/test/Ocelot.UnitTests/RoundRobinTests.cs @@ -11,19 +11,19 @@ namespace Ocelot.UnitTests public class RoundRobinTests { private readonly RoundRobinLoadBalancer _roundRobin; - private readonly List _hostAndPorts; + private readonly List _services; private Response _hostAndPort; public RoundRobinTests() { - _hostAndPorts = new List + _services = new List { - new HostAndPort("127.0.0.1", 5000), - new HostAndPort("127.0.0.1", 5001), - new HostAndPort("127.0.0.1", 5001) + new Service("product", new HostAndPort("127.0.0.1", 5000)), + new Service("product", new HostAndPort("127.0.0.1", 5001)), + new Service("product", new HostAndPort("127.0.0.1", 5001)) }; - _roundRobin = new RoundRobinLoadBalancer(_hostAndPorts); + _roundRobin = new RoundRobinLoadBalancer(_services); } [Fact] @@ -46,11 +46,11 @@ namespace Ocelot.UnitTests while (stopWatch.ElapsedMilliseconds < 1000) { var address = _roundRobin.Lease(); - address.Data.ShouldBe(_hostAndPorts[0]); + address.Data.ShouldBe(_services[0].HostAndPort); address = _roundRobin.Lease(); - address.Data.ShouldBe(_hostAndPorts[1]); + address.Data.ShouldBe(_services[1].HostAndPort); address = _roundRobin.Lease(); - address.Data.ShouldBe(_hostAndPorts[2]); + address.Data.ShouldBe(_services[2].HostAndPort); } } @@ -61,7 +61,7 @@ namespace Ocelot.UnitTests private void ThenTheNextAddressIndexIs(int index) { - _hostAndPort.Data.ShouldBe(_hostAndPorts[index]); + _hostAndPort.Data.ShouldBe(_services[index].HostAndPort); } } @@ -73,24 +73,24 @@ namespace Ocelot.UnitTests public class RoundRobinLoadBalancer : ILoadBalancer { - private readonly List _hostAndPorts; + private readonly List _services; private int _last; - public RoundRobinLoadBalancer(List hostAndPorts) + public RoundRobinLoadBalancer(List services) { - _hostAndPorts = hostAndPorts; + _services = services; } public Response Lease() { - if (_last >= _hostAndPorts.Count) + if (_last >= _services.Count) { _last = 0; } - var next = _hostAndPorts[_last]; + var next = _services[_last]; _last++; - return new OkResponse(next); + return new OkResponse(next.HostAndPort); } public Response Release(HostAndPort hostAndPort) diff --git a/test/Ocelot.UnitTests/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceProviderFactoryTests.cs new file mode 100644 index 00000000..05609816 --- /dev/null +++ b/test/Ocelot.UnitTests/ServiceProviderFactoryTests.cs @@ -0,0 +1,50 @@ +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.ServiceDiscovery; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests +{ + public class ServiceProviderFactoryTests + { + private ReRoute _reRote; + private IServiceProvider _result; + private ServiceProviderFactory _factory; + + public ServiceProviderFactoryTests() + { + _factory = new ServiceProviderFactory(); + } + + [Fact] + public void should_return_no_service_provider() + { + var reRoute = new ReRouteBuilder() + .WithDownstreamHost("127.0.0.1") + .WithDownstreamPort(80) + .Build(); + + this.Given(x => x.GivenTheReRoute(reRoute)) + .When(x => x.WhenIGetTheServiceProvider()) + .Then(x => x.ThenTheServiceProviderIs()) + .BDDfy(); + } + + private void GivenTheReRoute(ReRoute reRoute) + { + _reRote = reRoute; + } + + private void WhenIGetTheServiceProvider() + { + _result = _factory.Get(_reRote); + } + + private void ThenTheServiceProviderIs() + { + _result.ShouldBeOfType(); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/ServiceRegistryTests.cs b/test/Ocelot.UnitTests/ServiceRegistryTests.cs index 27987d42..09012213 100644 --- a/test/Ocelot.UnitTests/ServiceRegistryTests.cs +++ b/test/Ocelot.UnitTests/ServiceRegistryTests.cs @@ -86,6 +86,7 @@ namespace Ocelot.UnitTests { _repository = repository; } + public void Register(Service serviceNameAndAddress) { _repository.Set(serviceNameAndAddress); @@ -97,17 +98,6 @@ namespace Ocelot.UnitTests } } - public class Service - { - public Service(string name, HostAndPort hostAndPort) - { - Name = name; - HostAndPort = hostAndPort; - } - public string Name {get; private set;} - public HostAndPort HostAndPort {get; private set;} - } - public interface IServiceRepository { List Get(string serviceName); From 3642aac20defade863750fd82a671544443682f3 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Wed, 1 Feb 2017 20:43:39 +0000 Subject: [PATCH 024/113] Removed push-to-nuget build script and changed cake nuget feed urls --- build.cake | 10 +++++----- push-to-nuget.bat | 11 ----------- 2 files changed, 5 insertions(+), 16 deletions(-) delete mode 100644 push-to-nuget.bat diff --git a/build.cake b/build.cake index 3fbeeaf7..1d798d74 100644 --- a/build.cake +++ b/build.cake @@ -31,14 +31,14 @@ var artifactsFile = packagesDir + File("artifacts.txt"); // unstable releases var nugetFeedUnstableKey = EnvironmentVariable("nuget-apikey-unstable"); -var nugetFeedUnstableUploadUrl = "https://www.myget.org/F/ocelot-unstable/api/v2/package"; -var nugetFeedUnstableSymbolsUploadUrl = "https://www.myget.org/F/ocelot-unstable/symbols/api/v2/package"; +var nugetFeedUnstableUploadUrl = "https://www.nuget.org/api/v2/package"; +var nugetFeedUnstableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; // stable releases -var tagsUrl = "https://api.github.com/repos/binarymash/ocelot/releases/tags/"; +var tagsUrl = "https://api.github.com/repos/tompallister/ocelot/releases/tags/"; var nugetFeedStableKey = EnvironmentVariable("nuget-apikey-stable"); -var nugetFeedStableUploadUrl = "https://www.myget.org/F/ocelot-stable/api/v2/package"; -var nugetFeedStableSymbolsUploadUrl = "https://www.myget.org/F/ocelot-stable/symbols/api/v2/package"; +var nugetFeedStableUploadUrl = "https://www.nuget.org/api/v2/package"; +var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; // internal build variables - don't change these. var releaseTag = ""; diff --git a/push-to-nuget.bat b/push-to-nuget.bat deleted file mode 100644 index 7200d09c..00000000 --- a/push-to-nuget.bat +++ /dev/null @@ -1,11 +0,0 @@ -echo ------------------------- - -echo Packing Ocelot Version %1 -nuget pack .\Ocelot.nuspec -version %1 - -echo Publishing Ocelot - nuget push Ocelot.%1.nupkg -ApiKey %2 -Source https://www.nuget.org/api/v2/package - - - - From 9cc5c2c9648161de54dcc12da03a8ad0dad75090 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Wed, 1 Feb 2017 20:45:27 +0000 Subject: [PATCH 025/113] Call build.ps1 from AppVeyor --- appveyor.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 83110722..6e8b16f6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,10 +3,6 @@ configuration: - Release platform: Any CPU build_script: -- build.bat -test_script: -- run-tests.bat -after_test: -- push-to-nuget.bat %appveyor_build_version% %nugetApiKey% +- build.ps1 cache: - '%USERPROFILE%\.nuget\packages' \ No newline at end of file From 92f492f0ad37d212f4f9307be96d9e25bf0dfac5 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Wed, 1 Feb 2017 20:51:59 +0000 Subject: [PATCH 026/113] Call build.ps1 in same manner as mr binary mash --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 6e8b16f6..b5cd7c0c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,6 +3,6 @@ configuration: - Release platform: Any CPU build_script: -- build.ps1 +- ./build.ps1 cache: - '%USERPROFILE%\.nuget\packages' \ No newline at end of file From 074ae4d609ff785aa79e02d45d0572848bc1c82f Mon Sep 17 00:00:00 2001 From: TomPallister Date: Wed, 1 Feb 2017 22:30:28 +0000 Subject: [PATCH 027/113] started adding a load balancer house (terrible name?) --- .../LoadBalancers/ILoadBalancer.cs | 1 + .../LoadBalancers/ILoadBalancerHouse.cs | 10 ++ .../LoadBalancers/LoadBalancerHouse.cs | 29 +++++ .../LoadBalancer/LoadBalancerHouseTests.cs | 121 ++++++++++++++++++ 4 files changed, 161 insertions(+) create mode 100644 src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs create mode 100644 src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs create mode 100644 test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs index fe4e5baf..100ee6f0 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs @@ -1,3 +1,4 @@ +using System; using Ocelot.Responses; using Ocelot.Values; diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs new file mode 100644 index 00000000..065ae2ac --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerHouse.cs @@ -0,0 +1,10 @@ +using Ocelot.Responses; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public interface ILoadBalancerHouse + { + Response Get(string key); + Response Add(string key, ILoadBalancer loadBalancer); + } +} \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs new file mode 100644 index 00000000..2bb8b966 --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Responses; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class LoadBalancerHouse + { + private readonly Dictionary _loadBalancers; + + public LoadBalancerHouse() + { + _loadBalancers = new Dictionary(); + } + + public Response Get(string key) + { + return new OkResponse(_loadBalancers[key]); + } + + public Response Add(string key, ILoadBalancer loadBalancer) + { + _loadBalancers[key] = loadBalancer; + return new OkResponse(); + } + } +} diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs new file mode 100644 index 00000000..2fd15ee7 --- /dev/null +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -0,0 +1,121 @@ +using System; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Responses; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.LoadBalancer +{ + public class LoadBalancerHouseTests + { + private ILoadBalancer _loadBalancer; + private readonly LoadBalancerHouse _loadBalancerHouse; + private Response _addResult; + private Response _getResult; + private string _key; + + public LoadBalancerHouseTests() + { + _loadBalancerHouse = new LoadBalancerHouse(); + } + + [Fact] + public void should_store_load_balancer() + { + var key = "test"; + + this.Given(x => x.GivenThereIsALoadBalancer(key, new FakeLoadBalancer())) + .When(x => x.WhenIAddTheLoadBalancer()) + .Then(x => x.ThenItIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_get_load_balancer() + { + var key = "test"; + + this.Given(x => x.GivenThereIsALoadBalancer(key, new FakeLoadBalancer())) + .When(x => x.WhenWeGetThatLoadBalancer(key)) + .Then(x => x.ThenItIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_store_load_balancers_by_key() + { + var key = "test"; + var keyTwo = "testTwo"; + + this.Given(x => x.GivenThereIsALoadBalancer(key, new FakeLoadBalancer())) + .And(x => x.GivenThereIsALoadBalancer(keyTwo, new FakeRoundRobinLoadBalancer())) + .When(x => x.WhenWeGetThatLoadBalancer(key)) + .Then(x => x.ThenTheLoadBalancerIs()) + .When(x => x.WhenWeGetThatLoadBalancer(keyTwo)) + .Then(x => x.ThenTheLoadBalancerIs()) + .BDDfy(); + } + + private void ThenTheLoadBalancerIs() + { + _getResult.Data.ShouldBeOfType(); + } + + private void ThenItIsAdded() + { + _addResult.IsError.ShouldBe(false); + _addResult.ShouldBeOfType(); + } + + private void WhenIAddTheLoadBalancer() + { + _addResult = _loadBalancerHouse.Add(_key, _loadBalancer); + } + + + private void GivenThereIsALoadBalancer(string key, ILoadBalancer loadBalancer) + { + _key = key; + _loadBalancer = loadBalancer; + WhenIAddTheLoadBalancer(); + } + + private void WhenWeGetThatLoadBalancer(string key) + { + _getResult = _loadBalancerHouse.Get(key); + } + + private void ThenItIsReturned() + { + _getResult.Data.ShouldBe(_loadBalancer); + } + + class FakeLoadBalancer : ILoadBalancer + { + public Response Lease() + { + throw new NotImplementedException(); + } + + public Response Release(HostAndPort hostAndPort) + { + throw new NotImplementedException(); + } + } + + class FakeRoundRobinLoadBalancer : ILoadBalancer + { + public Response Lease() + { + throw new NotImplementedException(); + } + + public Response Release(HostAndPort hostAndPort) + { + throw new NotImplementedException(); + } + } + } +} From 37aaeeed82fe64812357b05d4b2d5bb3f342be41 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Thu, 2 Feb 2017 08:00:37 +0000 Subject: [PATCH 028/113] added houses --- src/Ocelot/Errors/OcelotErrorCode.cs | 4 +- .../LoadBalancers/LoadBalancerHouse.cs | 14 +- .../UnableToFindLoadBalancerError.cs | 12 ++ .../ServiceDiscovery/IServiceProviderHouse.cs | 12 ++ .../ServiceDiscovery/ServiceProviderHouse.cs | 34 +++++ .../UnableToFindServiceProviderError.cs | 12 ++ .../LoadBalancer/LoadBalancerHouseTests.cs | 22 ++- .../ServiceProviderHouseTests.cs | 126 ++++++++++++++++++ 8 files changed, 229 insertions(+), 7 deletions(-) create mode 100644 src/Ocelot/LoadBalancer/LoadBalancers/UnableToFindLoadBalancerError.cs create mode 100644 src/Ocelot/ServiceDiscovery/IServiceProviderHouse.cs create mode 100644 src/Ocelot/ServiceDiscovery/ServiceProviderHouse.cs create mode 100644 src/Ocelot/ServiceDiscovery/UnableToFindServiceProviderError.cs create mode 100644 test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderHouseTests.cs diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index 85c3e097..f2c479df 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -23,6 +23,8 @@ DownstreamSchemeNullOrEmptyError, DownstreamHostNullOrEmptyError, ServicesAreNullError, - ServicesAreEmptyError + ServicesAreEmptyError, + UnableToFindServiceProviderError, + UnableToFindLoadBalancerError } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs index 2bb8b966..12c040c0 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -6,7 +6,7 @@ using Ocelot.Responses; namespace Ocelot.LoadBalancer.LoadBalancers { - public class LoadBalancerHouse + public class LoadBalancerHouse : ILoadBalancerHouse { private readonly Dictionary _loadBalancers; @@ -17,7 +17,17 @@ namespace Ocelot.LoadBalancer.LoadBalancers public Response Get(string key) { - return new OkResponse(_loadBalancers[key]); + ILoadBalancer loadBalancer; + + if(_loadBalancers.TryGetValue(key, out loadBalancer)) + { + return new OkResponse(_loadBalancers[key]); + } + + return new ErrorResponse(new List() + { + new UnableToFindLoadBalancerError($"unabe to find load balancer for {key}") + }); } public Response Add(string key, ILoadBalancer loadBalancer) diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/UnableToFindLoadBalancerError.cs b/src/Ocelot/LoadBalancer/LoadBalancers/UnableToFindLoadBalancerError.cs new file mode 100644 index 00000000..3dd3ede4 --- /dev/null +++ b/src/Ocelot/LoadBalancer/LoadBalancers/UnableToFindLoadBalancerError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class UnableToFindLoadBalancerError : Errors.Error + { + public UnableToFindLoadBalancerError(string message) + : base(message, OcelotErrorCode.UnableToFindLoadBalancerError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/IServiceProviderHouse.cs b/src/Ocelot/ServiceDiscovery/IServiceProviderHouse.cs new file mode 100644 index 00000000..0f85f528 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/IServiceProviderHouse.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using Ocelot.Responses; + +namespace Ocelot.ServiceDiscovery +{ + public interface IServiceProviderHouse + { + Response Get(string key); + Response Add(string key, IServiceProvider serviceProvider); + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceProviderHouse.cs b/src/Ocelot/ServiceDiscovery/ServiceProviderHouse.cs new file mode 100644 index 00000000..63d14dce --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/ServiceProviderHouse.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using Ocelot.Responses; + +namespace Ocelot.ServiceDiscovery +{ + public class ServiceProviderHouse : IServiceProviderHouse + { + private Dictionary _serviceProviders; + + public ServiceProviderHouse() + { + _serviceProviders = new Dictionary(); + } + + public Response Get(string key) + { + IServiceProvider serviceProvider; + if(_serviceProviders.TryGetValue(key, out serviceProvider)) + { + return new OkResponse(serviceProvider); + } + + return new ErrorResponse(new List() + { + new UnableToFindServiceProviderError($"unabe to find service provider for {key}") + }); + } + public Response Add(string key, IServiceProvider serviceProvider) + { + _serviceProviders[key] = serviceProvider; + return new OkResponse(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/UnableToFindServiceProviderError.cs b/src/Ocelot/ServiceDiscovery/UnableToFindServiceProviderError.cs new file mode 100644 index 00000000..b8ed1a47 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/UnableToFindServiceProviderError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.ServiceDiscovery +{ + public class UnableToFindServiceProviderError : Error + { + public UnableToFindServiceProviderError(string message) + : base(message, OcelotErrorCode.UnableToFindServiceProviderError) + { + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs index 2fd15ee7..31a7bd37 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -38,7 +38,7 @@ namespace Ocelot.UnitTests.LoadBalancer var key = "test"; this.Given(x => x.GivenThereIsALoadBalancer(key, new FakeLoadBalancer())) - .When(x => x.WhenWeGetThatLoadBalancer(key)) + .When(x => x.WhenWeGetTheLoadBalancer(key)) .Then(x => x.ThenItIsReturned()) .BDDfy(); } @@ -51,13 +51,27 @@ namespace Ocelot.UnitTests.LoadBalancer this.Given(x => x.GivenThereIsALoadBalancer(key, new FakeLoadBalancer())) .And(x => x.GivenThereIsALoadBalancer(keyTwo, new FakeRoundRobinLoadBalancer())) - .When(x => x.WhenWeGetThatLoadBalancer(key)) + .When(x => x.WhenWeGetTheLoadBalancer(key)) .Then(x => x.ThenTheLoadBalancerIs()) - .When(x => x.WhenWeGetThatLoadBalancer(keyTwo)) + .When(x => x.WhenWeGetTheLoadBalancer(keyTwo)) .Then(x => x.ThenTheLoadBalancerIs()) .BDDfy(); } + [Fact] + public void should_return_error_if_no_load_balancer_with_key() + { + this.When(x => x.WhenWeGetTheLoadBalancer("test")) + .Then(x => x.ThenAnErrorIsReturned()) + .BDDfy(); + } + + private void ThenAnErrorIsReturned() + { + _getResult.IsError.ShouldBeTrue(); + _getResult.Errors[0].ShouldBeOfType(); + } + private void ThenTheLoadBalancerIs() { _getResult.Data.ShouldBeOfType(); @@ -82,7 +96,7 @@ namespace Ocelot.UnitTests.LoadBalancer WhenIAddTheLoadBalancer(); } - private void WhenWeGetThatLoadBalancer(string key) + private void WhenWeGetTheLoadBalancer(string key) { _getResult = _loadBalancerHouse.Get(key); } diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderHouseTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderHouseTests.cs new file mode 100644 index 00000000..9443c113 --- /dev/null +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderHouseTests.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Responses; +using Ocelot.ServiceDiscovery; +using Ocelot.Values; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.LoadBalancer +{ + public class ServiceProviderHouseTests + { + private Ocelot.ServiceDiscovery.IServiceProvider _serviceProvider; + private readonly ServiceProviderHouse _serviceProviderHouse; + private Response _addResult; + private Response _getResult; + private string _key; + + public ServiceProviderHouseTests() + { + _serviceProviderHouse = new ServiceProviderHouse(); + } + + [Fact] + public void should_store_service_provider() + { + var key = "test"; + + this.Given(x => x.GivenThereIsAServiceProvider(key, new FakeServiceProvider())) + .When(x => x.WhenIAddTheServiceProvider()) + .Then(x => x.ThenItIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_get_service_provider() + { + var key = "test"; + + this.Given(x => x.GivenThereIsAServiceProvider(key, new FakeServiceProvider())) + .When(x => x.WhenWeGetTheServiceProvider(key)) + .Then(x => x.ThenItIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_store_service_providers_by_key() + { + var key = "test"; + var keyTwo = "testTwo"; + + this.Given(x => x.GivenThereIsAServiceProvider(key, new FakeServiceProvider())) + .And(x => x.GivenThereIsAServiceProvider(keyTwo, new FakeConsulServiceProvider())) + .When(x => x.WhenWeGetTheServiceProvider(key)) + .Then(x => x.ThenTheServiceProviderIs()) + .When(x => x.WhenWeGetTheServiceProvider(keyTwo)) + .Then(x => x.ThenTheServiceProviderIs()) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_no_service_provider_house_with_key() + { + this.When(x => x.WhenWeGetTheServiceProvider("test")) + .Then(x => x.ThenAnErrorIsReturned()) + .BDDfy(); + } + + private void ThenAnErrorIsReturned() + { + _getResult.IsError.ShouldBeTrue(); + _getResult.Errors[0].ShouldBeOfType(); + } + + private void ThenTheServiceProviderIs() + { + _getResult.Data.ShouldBeOfType(); + } + + private void ThenItIsAdded() + { + _addResult.IsError.ShouldBe(false); + _addResult.ShouldBeOfType(); + } + + private void WhenIAddTheServiceProvider() + { + _addResult = _serviceProviderHouse.Add(_key, _serviceProvider); + } + + private void GivenThereIsAServiceProvider(string key, Ocelot.ServiceDiscovery.IServiceProvider serviceProvider) + { + _key = key; + _serviceProvider = serviceProvider; + WhenIAddTheServiceProvider(); + } + + private void WhenWeGetTheServiceProvider(string key) + { + _getResult = _serviceProviderHouse.Get(key); + } + + private void ThenItIsReturned() + { + _getResult.Data.ShouldBe(_serviceProvider); + } + + class FakeServiceProvider : Ocelot.ServiceDiscovery.IServiceProvider + { + public List Get() + { + throw new NotImplementedException(); + } + } + + class FakeConsulServiceProvider : Ocelot.ServiceDiscovery.IServiceProvider + { + public List Get() + { + throw new NotImplementedException(); + } + } + } +} From e80364a1f8af50bf7976fafbc20c6829a092b88a Mon Sep 17 00:00:00 2001 From: geffzhang Date: Thu, 2 Feb 2017 23:35:02 +0800 Subject: [PATCH 029/113] HttpClientHttpRequester implements the Circuit Breaker Pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HttpClientHttpRequester implements the Circuit Breaker Pattern,I using Polly from thepollyproject.org --- .../Requester/HttpClientHttpRequester.cs | 40 ++++++++++++++++++- src/Ocelot/project.json | 3 +- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index 0167ef6a..8056adeb 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -4,22 +4,58 @@ using System.Net.Http; using System.Threading.Tasks; using Ocelot.Errors; using Ocelot.Responses; +using Polly; +using Polly.Timeout; +using Polly.CircuitBreaker; +using Ocelot.Logging; namespace Ocelot.Requester { public class HttpClientHttpRequester : IHttpRequester { + private readonly IOcelotLogger _logger; + + public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + public async Task> GetResponse(Request.Request request) { + double timeoutvalue = 5000; + TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic; + + var timeoutPolicy = Policy + .TimeoutAsync(TimeSpan.FromMilliseconds(timeoutvalue), timeoutStrategy); + + var circuitBreakerPolicy = Policy + .Handle() + .Or() + .Or() + .CircuitBreakerAsync( + exceptionsAllowedBeforeBreaking: 4, + durationOfBreak: TimeSpan.FromSeconds(8), + onBreak: (ex, breakDelay) => + { + _logger.LogError(".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!", ex); + }, + onReset: () => _logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again."), + onHalfOpen: () => _logger.LogDebug(".Breaker logging: Half-open; next call is a trial.") + ); + using (var handler = new HttpClientHandler { CookieContainer = request.CookieContainer }) using (var httpClient = new HttpClient(handler)) { try { - var response = await httpClient.SendAsync(request.HttpRequestMessage); + // Retry the following call according to the policy - 3 times. + HttpResponseMessage response = await Policy.WrapAsync(circuitBreakerPolicy, timeoutPolicy).ExecuteAsync(() => + { + return httpClient.SendAsync(request.HttpRequestMessage); + }); return new OkResponse(response); } - catch (Exception exception) + catch (BrokenCircuitException exception) { return new ErrorResponse(new List diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 85008568..6711d984 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -27,7 +27,8 @@ "Microsoft.NETCore.App": "1.1.0", "CacheManager.Core": "0.9.2", "CacheManager.Microsoft.Extensions.Configuration": "0.9.2", - "CacheManager.Microsoft.Extensions.Logging": "0.9.2" + "CacheManager.Microsoft.Extensions.Logging": "0.9.2", + "Polly": "5.0.3" }, "runtimes": { "win10-x64": {}, From 7fc92da01298f36206af0703ccb970c01655abce Mon Sep 17 00:00:00 2001 From: "tom.pallister" Date: Thu, 2 Feb 2017 15:53:02 +0000 Subject: [PATCH 030/113] Updated release script to use correct target name --- build-and-release-unstable.ps1 | 2 +- test/Ocelot.AcceptanceTests/configuration.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build-and-release-unstable.ps1 b/build-and-release-unstable.ps1 index 9a29f95f..51c6f0d5 100644 --- a/build-and-release-unstable.ps1 +++ b/build-and-release-unstable.ps1 @@ -1 +1 @@ -./build.ps1 -target build-full \ No newline at end of file +./build.ps1 -target BuildAndReleaseUnstable \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index f28abefe..cdcb1624 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamTemplate":"/","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":41879}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Address":null}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Address":null}}} \ No newline at end of file From 07ca7989b012366436a4e90ccdbe32bad9a36472 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Thu, 2 Feb 2017 21:34:15 +0000 Subject: [PATCH 031/113] more work towards getting service discovery working with load balancing --- .../Creator/FileOcelotConfigurationCreator.cs | 71 ++++++---- .../ServiceCollectionExtensions.cs | 6 + .../LoadBalancers/ILoadBalancerFactory.cs | 6 +- .../LoadBalancers/LoadBalancerFactory.cs | 28 ++-- .../Middleware/LoadBalancingMiddleware.cs | 35 ++--- .../LoadBalancingMiddlewareExtensions.cs | 12 ++ src/Ocelot/Middleware/OcelotMiddleware.cs | 15 +++ .../IServiceProviderFactory.cs | 2 + .../ServiceDiscovery/IServiceProviderHouse.cs | 12 -- .../ServiceDiscovery/ServiceProviderHouse.cs | 34 ----- .../FileConfigurationCreatorTests.cs | 6 +- .../LoadBalancer/LoadBalancerFactoryTests.cs | 12 +- .../LoadBalancerMiddlewareTests.cs | 124 +++++++++++++++++ .../ServiceProviderHouseTests.cs | 126 ------------------ 14 files changed, 248 insertions(+), 241 deletions(-) create mode 100644 src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs delete mode 100644 src/Ocelot/ServiceDiscovery/IServiceProviderHouse.cs delete mode 100644 src/Ocelot/ServiceDiscovery/ServiceProviderHouse.cs create mode 100644 test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs delete mode 100644 test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderHouseTests.cs diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 827dcbad..61c61fae 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -27,13 +27,19 @@ namespace Ocelot.Configuration.Creator private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser; private readonly ILogger _logger; + private readonly ILoadBalancerFactory _loadBalanceFactory; + private readonly ILoadBalancerHouse _loadBalancerHouse; public FileOcelotConfigurationCreator( IOptions options, IConfigurationValidator configurationValidator, IClaimToThingConfigurationParser claimToThingConfigurationParser, - ILogger logger) + ILogger logger, + ILoadBalancerFactory loadBalancerFactory, + ILoadBalancerHouse loadBalancerHouse) { + _loadBalanceFactory = loadBalancerFactory; + _loadBalancerHouse = loadBalancerHouse; _options = options; _configurationValidator = configurationValidator; _claimToThingConfigurationParser = claimToThingConfigurationParser; @@ -78,55 +84,62 @@ namespace Ocelot.Configuration.Creator return new OcelotConfiguration(reRoutes); } - private ReRoute SetUpReRoute(FileReRoute reRoute, FileGlobalConfiguration globalConfiguration) + private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) { var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); - var upstreamTemplate = BuildUpstreamTemplate(reRoute); + var upstreamTemplate = BuildUpstreamTemplate(fileReRoute); - var isAuthenticated = !string.IsNullOrEmpty(reRoute.AuthenticationOptions?.Provider); + var isAuthenticated = !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); - var isAuthorised = reRoute.RouteClaimsRequirement?.Count > 0; + var isAuthorised = fileReRoute.RouteClaimsRequirement?.Count > 0; - var isCached = reRoute.FileCacheOptions.TtlSeconds > 0; + var isCached = fileReRoute.FileCacheOptions.TtlSeconds > 0; var requestIdKey = globalRequestIdConfiguration ? globalConfiguration.RequestIdKey - : reRoute.RequestIdKey; + : fileReRoute.RequestIdKey; - var useServiceDiscovery = !string.IsNullOrEmpty(reRoute.ServiceName) + var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName) && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Address) && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); + ReRoute reRoute; + if (isAuthenticated) { - var authOptionsForRoute = new AuthenticationOptions(reRoute.AuthenticationOptions.Provider, - reRoute.AuthenticationOptions.ProviderRootUrl, reRoute.AuthenticationOptions.ScopeName, - reRoute.AuthenticationOptions.RequireHttps, reRoute.AuthenticationOptions.AdditionalScopes, - reRoute.AuthenticationOptions.ScopeSecret); + var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider, + fileReRoute.AuthenticationOptions.ProviderRootUrl, fileReRoute.AuthenticationOptions.ScopeName, + fileReRoute.AuthenticationOptions.RequireHttps, fileReRoute.AuthenticationOptions.AdditionalScopes, + fileReRoute.AuthenticationOptions.ScopeSecret); - var claimsToHeaders = GetAddThingsToRequest(reRoute.AddHeadersToRequest); - var claimsToClaims = GetAddThingsToRequest(reRoute.AddClaimsToRequest); - var claimsToQueries = GetAddThingsToRequest(reRoute.AddQueriesToRequest); + var claimsToHeaders = GetAddThingsToRequest(fileReRoute.AddHeadersToRequest); + var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest); + var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest); - return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate, - reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, + reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), fileReRoute.UpstreamTemplate, + fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, authOptionsForRoute, claimsToHeaders, claimsToClaims, - reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, - requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), - reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, reRoute.DownstreamScheme, - reRoute.LoadBalancer, reRoute.DownstreamHost, reRoute.DownstreamPort); + fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, + requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), + fileReRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, + globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort); } - return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate, - reRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, + reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), fileReRoute.UpstreamTemplate, + fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, null, new List(), new List(), - reRoute.RouteClaimsRequirement, isAuthorised, new List(), - requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), - reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, reRoute.DownstreamScheme, - reRoute.LoadBalancer, reRoute.DownstreamHost, reRoute.DownstreamPort); + fileReRoute.RouteClaimsRequirement, isAuthorised, new List(), + requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), + fileReRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, + globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort); + + var loadBalancer = _loadBalanceFactory.Get(reRoute); + //todo - not sure if this is the correct key, but this is probably the only unique key i can think of + _loadBalancerHouse.Add($"{fileReRoute.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}", loadBalancer); + return reRoute; } private string BuildUpstreamTemplate(FileReRoute reRoute) diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 9f40b009..86564f92 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -23,11 +23,13 @@ using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Headers; using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Infrastructure.RequestData; +using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Logging; using Ocelot.QueryStrings; using Ocelot.Request.Builder; using Ocelot.Requester; using Ocelot.Responder; +using Ocelot.ServiceDiscovery; namespace Ocelot.DependencyInjection { @@ -59,6 +61,10 @@ namespace Ocelot.DependencyInjection { services.AddMvcCore().AddJsonFormatters(); services.AddLogging(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs index 930a5211..55089cde 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs @@ -1,7 +1,9 @@ -namespace Ocelot.LoadBalancer.LoadBalancers +using Ocelot.Configuration; + +namespace Ocelot.LoadBalancer.LoadBalancers { public interface ILoadBalancerFactory { - ILoadBalancer Get(string serviceName, string loadBalancer); + ILoadBalancer Get(ReRoute reRoute); } } \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs index d5203062..7e11df39 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs @@ -1,26 +1,34 @@ -using Ocelot.ServiceDiscovery; +using Ocelot.Configuration; +using Ocelot.ServiceDiscovery; namespace Ocelot.LoadBalancer.LoadBalancers { public class LoadBalancerFactory : ILoadBalancerFactory { - private readonly IServiceProvider _serviceProvider; - - public LoadBalancerFactory(IServiceProvider serviceProvider) + private readonly IServiceProviderFactory _serviceProviderFactory; + public LoadBalancerFactory(IServiceProviderFactory serviceProviderFactory) { - _serviceProvider = serviceProvider; + _serviceProviderFactory = serviceProviderFactory; } - public ILoadBalancer Get(string serviceName, string loadBalancer) + public ILoadBalancer Get(ReRoute reRoute) { - switch (loadBalancer) + var serviceConfig = new ServiceConfiguraion( + reRoute.ServiceName, + reRoute.DownstreamHost, + reRoute.DownstreamPort, + reRoute.UseServiceDiscovery); + + var serviceProvider = _serviceProviderFactory.Get(serviceConfig); + + switch (reRoute.LoadBalancer) { case "RoundRobin": - return new RoundRobinLoadBalancer(_serviceProvider.Get()); + return new RoundRobinLoadBalancer(serviceProvider.Get()); case "LeastConnection": - return new LeastConnectionLoadBalancer(() => _serviceProvider.Get(), serviceName); + return new LeastConnectionLoadBalancer(() => serviceProvider.Get(), reRoute.ServiceName); default: - return new NoLoadBalancer(_serviceProvider.Get()); + return new NoLoadBalancer(serviceProvider.Get()); } } } diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index 5c66e98a..e762186d 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -14,37 +14,30 @@ namespace Ocelot.LoadBalancer.Middleware { private readonly RequestDelegate _next; private readonly IOcelotLogger _logger; + private readonly ILoadBalancerHouse _loadBalancerHouse; public LoadBalancingMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository) + IRequestScopedDataRepository requestScopedDataRepository, + ILoadBalancerHouse loadBalancerHouse) : base(requestScopedDataRepository) { _next = next; _logger = loggerFactory.CreateLogger(); + _loadBalancerHouse = loadBalancerHouse; } public async Task Invoke(HttpContext context) { _logger.LogDebug("started calling query string builder middleware"); - //todo - get out of di? or do this when we bootstrap? - var serviceProviderFactory = new ServiceProviderFactory(); - var serviceConfig = new ServiceConfiguraion( - DownstreamRoute.ReRoute.ServiceName, - DownstreamRoute.ReRoute.DownstreamHost, - DownstreamRoute.ReRoute.DownstreamPort, - DownstreamRoute.ReRoute.UseServiceDiscovery); - //todo - get this out of some kind of service provider house? - var serviceProvider = serviceProviderFactory.Get(serviceConfig); - - //todo - get out of di? or do this when we bootstrap? - var loadBalancerFactory = new LoadBalancerFactory(serviceProvider); - //todo - currently instanciates a load balancer per request which is wrong, - //need some kind of load balance house! :) - var loadBalancer = loadBalancerFactory.Get(DownstreamRoute.ReRoute.ServiceName, DownstreamRoute.ReRoute.LoadBalancer); - var response = loadBalancer.Lease(); - + var loadBalancer = _loadBalancerHouse.Get($"{DownstreamRoute.ReRoute.UpstreamTemplate}{DownstreamRoute.ReRoute.UpstreamHttpMethod}"); + //todo check reponse and return error + + var response = loadBalancer.Data.Lease(); + //todo check reponse and return error + + SetHostAndPortForThisRequest(response.Data); _logger.LogDebug("calling next middleware"); //todo - try next middleware if we get an exception make sure we release @@ -53,11 +46,11 @@ namespace Ocelot.LoadBalancer.Middleware { await _next.Invoke(context); - loadBalancer.Release(response.Data); + loadBalancer.Data.Release(response.Data); } - catch (Exception exception) + catch (Exception) { - loadBalancer.Release(response.Data); + loadBalancer.Data.Release(response.Data); throw; } diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs new file mode 100644 index 00000000..52d47bdd --- /dev/null +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Builder; + +namespace Ocelot.LoadBalancer.Middleware +{ + public static class LoadBalancingMiddlewareExtensions + { + public static IApplicationBuilder UseLoadBalancingMiddlewareExtensions(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Middleware/OcelotMiddleware.cs b/src/Ocelot/Middleware/OcelotMiddleware.cs index 0bb51040..b8937108 100644 --- a/src/Ocelot/Middleware/OcelotMiddleware.cs +++ b/src/Ocelot/Middleware/OcelotMiddleware.cs @@ -3,6 +3,7 @@ using System.Net.Http; using Ocelot.DownstreamRouteFinder; using Ocelot.Errors; using Ocelot.Infrastructure.RequestData; +using Ocelot.Values; namespace Ocelot.Middleware { @@ -69,6 +70,20 @@ namespace Ocelot.Middleware } } + public HostAndPort HostAndPort + { + get + { + var hostAndPort = _requestScopedDataRepository.Get("HostAndPort"); + return hostAndPort.Data; + } + } + + public void SetHostAndPortForThisRequest(HostAndPort hostAndPort) + { + _requestScopedDataRepository.Add("HostAndPort", hostAndPort); + } + public void SetDownstreamRouteForThisRequest(DownstreamRoute downstreamRoute) { _requestScopedDataRepository.Add("DownstreamRoute", downstreamRoute); diff --git a/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs index 67b84c69..62c55f53 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs @@ -1,3 +1,5 @@ +using System; + namespace Ocelot.ServiceDiscovery { public interface IServiceProviderFactory diff --git a/src/Ocelot/ServiceDiscovery/IServiceProviderHouse.cs b/src/Ocelot/ServiceDiscovery/IServiceProviderHouse.cs deleted file mode 100644 index 0f85f528..00000000 --- a/src/Ocelot/ServiceDiscovery/IServiceProviderHouse.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using Ocelot.Responses; - -namespace Ocelot.ServiceDiscovery -{ - public interface IServiceProviderHouse - { - Response Get(string key); - Response Add(string key, IServiceProvider serviceProvider); - } -} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceProviderHouse.cs b/src/Ocelot/ServiceDiscovery/ServiceProviderHouse.cs deleted file mode 100644 index 63d14dce..00000000 --- a/src/Ocelot/ServiceDiscovery/ServiceProviderHouse.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; -using Ocelot.Responses; - -namespace Ocelot.ServiceDiscovery -{ - public class ServiceProviderHouse : IServiceProviderHouse - { - private Dictionary _serviceProviders; - - public ServiceProviderHouse() - { - _serviceProviders = new Dictionary(); - } - - public Response Get(string key) - { - IServiceProvider serviceProvider; - if(_serviceProviders.TryGetValue(key, out serviceProvider)) - { - return new OkResponse(serviceProvider); - } - - return new ErrorResponse(new List() - { - new UnableToFindServiceProviderError($"unabe to find service provider for {key}") - }); - } - public Response Add(string key, IServiceProvider serviceProvider) - { - _serviceProviders[key] = serviceProvider; - return new OkResponse(); - } - } -} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index fc80a478..b6e28033 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -8,6 +8,7 @@ using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; +using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Responses; using Shouldly; using TestStack.BDDfy; @@ -24,6 +25,8 @@ namespace Ocelot.UnitTests.Configuration private readonly Mock _configParser; private readonly Mock> _logger; private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator; + private readonly Mock _loadBalancerFactory; + private readonly Mock _loadBalancerHouse; public FileConfigurationCreatorTests() { @@ -32,7 +35,8 @@ namespace Ocelot.UnitTests.Configuration _validator = new Mock(); _fileConfig = new Mock>(); _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( - _fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object); + _fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object, + _loadBalancerFactory.Object, _loadBalancerHouse.Object); } [Fact] diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index c4eb736b..7361c726 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -14,12 +14,12 @@ namespace Ocelot.UnitTests.LoadBalancer private ReRoute _reRoute; private LoadBalancerFactory _factory; private ILoadBalancer _result; - private Mock _serviceProvider; + private Mock _serviceProviderFactory; public LoadBalancerFactoryTests() { - _serviceProvider = new Mock(); - _factory = new LoadBalancerFactory(_serviceProvider.Object); + _serviceProviderFactory = new Mock(); + _factory = new LoadBalancerFactory(_serviceProviderFactory.Object); } [Fact] @@ -75,8 +75,8 @@ namespace Ocelot.UnitTests.LoadBalancer private void ThenTheServiceProviderIsCalledCorrectly() { - _serviceProvider - .Verify(x => x.Get(), Times.Once); + _serviceProviderFactory + .Verify(x => x.Get(It.IsAny()), Times.Once); } private void GivenAReRoute(ReRoute reRoute) @@ -86,7 +86,7 @@ namespace Ocelot.UnitTests.LoadBalancer private void WhenIGetTheLoadBalancer() { - _result = _factory.Get(_reRoute.ServiceName, _reRoute.LoadBalancer); + _result = _factory.Get(_reRoute); } private void ThenTheLoadBalancerIsReturned() diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs new file mode 100644 index 00000000..ed6d30c0 --- /dev/null +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -0,0 +1,124 @@ +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Ocelot.Configuration.Builder; +using Ocelot.DownstreamRouteFinder; +using Ocelot.Infrastructure.RequestData; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.LoadBalancer.Middleware; +using Ocelot.Logging; +using Ocelot.Responses; +using Ocelot.Values; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.LoadBalancer +{ + public class LoadBalancerMiddlewareTests + { + private readonly Mock _loadBalancerHouse; + private readonly Mock _scopedRepository; + private readonly Mock _loadBalancer; + private readonly string _url; + private readonly TestServer _server; + private readonly HttpClient _client; + private HttpResponseMessage _result; + private OkResponse _request; + private OkResponse _downstreamUrl; + private OkResponse _downstreamRoute; + + public LoadBalancerMiddlewareTests() + { + _url = "http://localhost:51879"; + _loadBalancerHouse = new Mock(); + _scopedRepository = new Mock(); + var builder = new WebHostBuilder() + .ConfigureServices(x => + { + x.AddSingleton(); + x.AddLogging(); + x.AddSingleton(_loadBalancerHouse.Object); + x.AddSingleton(_scopedRepository.Object); + }) + .UseUrls(_url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(_url) + .Configure(app => + { + app.UseLoadBalancingMiddlewareExtensions(); + }); + + _server = new TestServer(builder); + _client = _server.CreateClient(); + } + + [Fact] + public void should_call_scoped_data_repository_correctly() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .Build()); + + this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) + .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheLoadBalancerHouseReturns()) + .And(x => x.GivenTheLoadBalancerReturns()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) + .BDDfy(); + } + + private void GivenTheLoadBalancerReturns() + { + _loadBalancer + .Setup(x => x.Lease()) + .Returns(new OkResponse(new HostAndPort("127.0.0.1", 80))); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + _scopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + + private void GivenTheLoadBalancerHouseReturns() + { + _loadBalancerHouse + .Setup(x => x.Get(It.IsAny())) + .Returns(new OkResponse(_loadBalancer.Object)); + } + + private void ThenTheScopedDataRepositoryIsCalledCorrectly() + { + _scopedRepository + .Verify(x => x.Add("Request", _request.Data), Times.Once()); + } + + private void WhenICallTheMiddleware() + { + _result = _client.GetAsync(_url).Result; + } + + private void GivenTheDownStreamUrlIs(string downstreamUrl) + { + _downstreamUrl = new OkResponse(downstreamUrl); + _scopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamUrl); + } + + public void Dispose() + { + _client.Dispose(); + _server.Dispose(); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderHouseTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderHouseTests.cs deleted file mode 100644 index 9443c113..00000000 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderHouseTests.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using System.Collections.Generic; -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.Responses; -using Ocelot.ServiceDiscovery; -using Ocelot.Values; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.LoadBalancer -{ - public class ServiceProviderHouseTests - { - private Ocelot.ServiceDiscovery.IServiceProvider _serviceProvider; - private readonly ServiceProviderHouse _serviceProviderHouse; - private Response _addResult; - private Response _getResult; - private string _key; - - public ServiceProviderHouseTests() - { - _serviceProviderHouse = new ServiceProviderHouse(); - } - - [Fact] - public void should_store_service_provider() - { - var key = "test"; - - this.Given(x => x.GivenThereIsAServiceProvider(key, new FakeServiceProvider())) - .When(x => x.WhenIAddTheServiceProvider()) - .Then(x => x.ThenItIsAdded()) - .BDDfy(); - } - - [Fact] - public void should_get_service_provider() - { - var key = "test"; - - this.Given(x => x.GivenThereIsAServiceProvider(key, new FakeServiceProvider())) - .When(x => x.WhenWeGetTheServiceProvider(key)) - .Then(x => x.ThenItIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_store_service_providers_by_key() - { - var key = "test"; - var keyTwo = "testTwo"; - - this.Given(x => x.GivenThereIsAServiceProvider(key, new FakeServiceProvider())) - .And(x => x.GivenThereIsAServiceProvider(keyTwo, new FakeConsulServiceProvider())) - .When(x => x.WhenWeGetTheServiceProvider(key)) - .Then(x => x.ThenTheServiceProviderIs()) - .When(x => x.WhenWeGetTheServiceProvider(keyTwo)) - .Then(x => x.ThenTheServiceProviderIs()) - .BDDfy(); - } - - [Fact] - public void should_return_error_if_no_service_provider_house_with_key() - { - this.When(x => x.WhenWeGetTheServiceProvider("test")) - .Then(x => x.ThenAnErrorIsReturned()) - .BDDfy(); - } - - private void ThenAnErrorIsReturned() - { - _getResult.IsError.ShouldBeTrue(); - _getResult.Errors[0].ShouldBeOfType(); - } - - private void ThenTheServiceProviderIs() - { - _getResult.Data.ShouldBeOfType(); - } - - private void ThenItIsAdded() - { - _addResult.IsError.ShouldBe(false); - _addResult.ShouldBeOfType(); - } - - private void WhenIAddTheServiceProvider() - { - _addResult = _serviceProviderHouse.Add(_key, _serviceProvider); - } - - private void GivenThereIsAServiceProvider(string key, Ocelot.ServiceDiscovery.IServiceProvider serviceProvider) - { - _key = key; - _serviceProvider = serviceProvider; - WhenIAddTheServiceProvider(); - } - - private void WhenWeGetTheServiceProvider(string key) - { - _getResult = _serviceProviderHouse.Get(key); - } - - private void ThenItIsReturned() - { - _getResult.Data.ShouldBe(_serviceProvider); - } - - class FakeServiceProvider : Ocelot.ServiceDiscovery.IServiceProvider - { - public List Get() - { - throw new NotImplementedException(); - } - } - - class FakeConsulServiceProvider : Ocelot.ServiceDiscovery.IServiceProvider - { - public List Get() - { - throw new NotImplementedException(); - } - } - } -} From 666a2e71132df72efe1498ecdb70cbd7f73278a8 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Thu, 2 Feb 2017 21:38:08 +0000 Subject: [PATCH 032/113] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 249a99a3..d96b9868 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ocelot -[![Build status](https://ci.appveyor.com/api/projects/status/roahbe4nl526ysya?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot) +[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/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) From f2c6d1c799d909e96d598880af1b5313d45f6f7e Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Thu, 2 Feb 2017 22:34:46 +0000 Subject: [PATCH 033/113] load balancer middle ware test and cake mac osx build script --- build.sh | 101 ++++++++++++++++++ .../ServiceCollectionExtensions.cs | 1 - .../Middleware/LoadBalancingMiddleware.cs | 2 +- .../LoadBalancerMiddlewareTests.cs | 8 +- 4 files changed, 108 insertions(+), 4 deletions(-) create mode 100755 build.sh diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..04731adf --- /dev/null +++ b/build.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash + +########################################################################## +# This is the Cake bootstrapper script for Linux and OS X. +# This file was downloaded from https://github.com/cake-build/resources +# Feel free to change this file to fit your needs. +########################################################################## + +# Define directories. +SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +TOOLS_DIR=$SCRIPT_DIR/tools +NUGET_EXE=$TOOLS_DIR/nuget.exe +CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe +PACKAGES_CONFIG=$TOOLS_DIR/packages.config +PACKAGES_CONFIG_MD5=$TOOLS_DIR/packages.config.md5sum + +# Define md5sum or md5 depending on Linux/OSX +MD5_EXE= +if [[ "$(uname -s)" == "Darwin" ]]; then + MD5_EXE="md5 -r" +else + MD5_EXE="md5sum" +fi + +# Define default arguments. +SCRIPT="build.cake" +TARGET="Default" +CONFIGURATION="Release" +VERBOSITY="verbose" +DRYRUN= +SHOW_VERSION=false +SCRIPT_ARGUMENTS=() + +# Parse arguments. +for i in "$@"; do + case $1 in + -s|--script) SCRIPT="$2"; shift ;; + -t|--target) TARGET="$2"; shift ;; + -c|--configuration) CONFIGURATION="$2"; shift ;; + -v|--verbosity) VERBOSITY="$2"; shift ;; + -d|--dryrun) DRYRUN="-dryrun" ;; + --version) SHOW_VERSION=true ;; + --) shift; SCRIPT_ARGUMENTS+=("$@"); break ;; + *) SCRIPT_ARGUMENTS+=("$1") ;; + esac + shift +done + +# Make sure the tools folder exist. +if [ ! -d "$TOOLS_DIR" ]; then + mkdir "$TOOLS_DIR" +fi + +# Make sure that packages.config exist. +if [ ! -f "$TOOLS_DIR/packages.config" ]; then + echo "Downloading packages.config..." + curl -Lsfo "$TOOLS_DIR/packages.config" http://cakebuild.net/download/bootstrapper/packages + if [ $? -ne 0 ]; then + echo "An error occured while downloading packages.config." + exit 1 + fi +fi + +# Download NuGet if it does not exist. +if [ ! -f "$NUGET_EXE" ]; then + echo "Downloading NuGet..." + curl -Lsfo "$NUGET_EXE" https://dist.nuget.org/win-x86-commandline/latest/nuget.exe + if [ $? -ne 0 ]; then + echo "An error occured while downloading nuget.exe." + exit 1 + fi +fi + +# Restore tools from NuGet. +pushd "$TOOLS_DIR" >/dev/null +if [ ! -f "$PACKAGES_CONFIG_MD5" ] || [ "$( cat "$PACKAGES_CONFIG_MD5" | sed 's/\r$//' )" != "$( $MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' )" ]; then + find . -type d ! -name . | xargs rm -rf +fi + +mono "$NUGET_EXE" install -ExcludeVersion +if [ $? -ne 0 ]; then + echo "Could not restore NuGet packages." + exit 1 +fi + +$MD5_EXE "$PACKAGES_CONFIG" | awk '{ print $1 }' >| "$PACKAGES_CONFIG_MD5" + +popd >/dev/null + +# Make sure that Cake has been installed. +if [ ! -f "$CAKE_EXE" ]; then + echo "Could not find Cake.exe at '$CAKE_EXE'." + exit 1 +fi + +# Start Cake +if $SHOW_VERSION; then + exec mono "$CAKE_EXE" -version +else + exec mono "$CAKE_EXE" $SCRIPT -verbosity=$VERBOSITY -configuration=$CONFIGURATION -target=$TARGET $DRYRUN "${SCRIPT_ARGUMENTS[@]}" +fi \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 86564f92..1615a69b 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -64,7 +64,6 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index e762186d..0ef74324 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -29,7 +29,7 @@ namespace Ocelot.LoadBalancer.Middleware public async Task Invoke(HttpContext context) { - _logger.LogDebug("started calling query string builder middleware"); + _logger.LogDebug("started calling load balancing middleware"); var loadBalancer = _loadBalancerHouse.Get($"{DownstreamRoute.ReRoute.UpstreamTemplate}{DownstreamRoute.ReRoute.UpstreamHttpMethod}"); //todo check reponse and return error diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index ed6d30c0..7f34c903 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -27,6 +27,7 @@ namespace Ocelot.UnitTests.LoadBalancer private readonly TestServer _server; private readonly HttpClient _client; private HttpResponseMessage _result; + private HostAndPort _hostAndPort; private OkResponse _request; private OkResponse _downstreamUrl; private OkResponse _downstreamRoute; @@ -36,6 +37,8 @@ namespace Ocelot.UnitTests.LoadBalancer _url = "http://localhost:51879"; _loadBalancerHouse = new Mock(); _scopedRepository = new Mock(); + _loadBalancer = new Mock(); + _loadBalancerHouse = new Mock(); var builder = new WebHostBuilder() .ConfigureServices(x => { @@ -76,9 +79,10 @@ namespace Ocelot.UnitTests.LoadBalancer private void GivenTheLoadBalancerReturns() { + _hostAndPort = new HostAndPort("127.0.0.1", 80); _loadBalancer .Setup(x => x.Lease()) - .Returns(new OkResponse(new HostAndPort("127.0.0.1", 80))); + .Returns(new OkResponse(_hostAndPort)); } private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) @@ -99,7 +103,7 @@ namespace Ocelot.UnitTests.LoadBalancer private void ThenTheScopedDataRepositoryIsCalledCorrectly() { _scopedRepository - .Verify(x => x.Add("Request", _request.Data), Times.Once()); + .Verify(x => x.Add("HostAndPort", _hostAndPort), Times.Once()); } private void WhenICallTheMiddleware() From 883be802b303c4049fcf40267ad55ccab3a39bb3 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Fri, 3 Feb 2017 14:49:46 +0800 Subject: [PATCH 034/113] Refactor CircuitBreaker Pattern Add Qos Config in ReRoute And Refactor CircuitBreakingDelegatingHandler --- .../Configuration/Builder/ReRouteBuilder.cs | 24 +- .../Creator/FileOcelotConfigurationCreator.cs | 7 +- src/Ocelot/Configuration/File/FileReRoute.cs | 3 + src/Ocelot/Configuration/ReRoute.cs | 9 +- .../Request/Builder/HttpRequestCreator.cs | 4 +- src/Ocelot/Request/Builder/IRequestCreator.cs | 3 +- src/Ocelot/Request/Builder/RequestBuilder.cs | 9 +- .../HttpRequestBuilderMiddleware.cs | 3 +- src/Ocelot/Request/Request.cs | 7 +- .../CircuitBreakingDelegatingHandler.cs | 74 +++ src/Ocelot/Requester/HttpClientBuilder.cs | 41 ++ .../Requester/HttpClientHttpRequester.cs | 55 +-- src/Ocelot/Requester/IHttpRequester.cs | 2 + src/Ocelot/Values/QoS.cs | 27 ++ .../AuthenticationTests.cs | 9 + .../AuthorisationTests.cs | 3 + test/Ocelot.AcceptanceTests/CachingTests.cs | 6 + .../CaseSensitiveRoutingTests.cs | 30 +- .../ClaimsToHeadersForwardingTests.cs | 3 + .../ClaimsToQueryStringForwardingTests.cs | 3 + .../CustomMiddlewareTests.cs | 18 + test/Ocelot.AcceptanceTests/RequestIdTests.cs | 13 +- test/Ocelot.AcceptanceTests/RoutingTests.cs | 31 +- .../Ocelot.AcceptanceTests/configuration.json | 2 +- test/Ocelot.ManualTest/configuration.json | 437 ++++++++++-------- .../HttpRequestBuilderMiddlewareTests.cs | 4 +- .../Request/RequestBuilderTests.cs | 30 +- .../Requester/HttpRequesterMiddlewareTests.cs | 2 +- 28 files changed, 600 insertions(+), 259 deletions(-) create mode 100644 src/Ocelot/Requester/CircuitBreakingDelegatingHandler.cs create mode 100644 src/Ocelot/Requester/HttpClientBuilder.cs create mode 100644 src/Ocelot/Values/QoS.cs diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 1e06a440..effb9cb8 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -32,6 +32,9 @@ namespace Ocelot.Configuration.Builder private string _downstreamScheme; private string _downstreamHost; private int _dsPort; + private int _exceptionsAllowedBeforeBreaking; + private int _durationOfBreak; + private int _timeoutValue; public ReRouteBuilder() { @@ -192,6 +195,24 @@ namespace Ocelot.Configuration.Builder return this; } + public ReRouteBuilder WithExceptionsAllowedBeforeBreaking(int exceptionsAllowedBeforeBreaking) + { + _exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; + return this; + } + + public ReRouteBuilder WithDurationOfBreak(int durationOfBreak) + { + _durationOfBreak = durationOfBreak; + return this; + } + + public ReRouteBuilder WithTimeoutValue(int timeoutValue) + { + _timeoutValue = timeoutValue; + return this; + } + public ReRoute Build() { Func downstreamHostFunc = () => new HostAndPort(_downstreamHost, _dsPort); @@ -200,7 +221,8 @@ namespace Ocelot.Configuration.Builder _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _serviceName, - _useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, downstreamHostFunc, _downstreamScheme); + _useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, downstreamHostFunc, _downstreamScheme, + _exceptionsAllowedBeforeBreaking,_durationOfBreak, _timeoutValue); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 8884f0d9..e22ecd0b 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -96,7 +96,6 @@ namespace Ocelot.Configuration.Creator && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Address) && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); - Func downstreamHostAndPortFunc = () => new HostAndPort(reRoute.DownstreamHost.Trim('/'), reRoute.DownstreamPort); if (isAuthenticated) @@ -116,7 +115,8 @@ namespace Ocelot.Configuration.Creator reRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme); + globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme, + reRoute.ExceptionsAllowedBeforeBreaking, reRoute.DurationOfBreak, reRoute.TimeoutValue); } return new ReRoute(new DownstreamPathTemplate(reRoute.DownstreamPathTemplate), reRoute.UpstreamTemplate, @@ -125,7 +125,8 @@ namespace Ocelot.Configuration.Creator reRoute.RouteClaimsRequirement, isAuthorised, new List(), requestIdKey, isCached, new CacheOptions(reRoute.FileCacheOptions.TtlSeconds), reRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme); + globalConfiguration?.ServiceDiscoveryProvider?.Address, downstreamHostAndPortFunc, reRoute.DownstreamScheme, + reRoute.ExceptionsAllowedBeforeBreaking, reRoute.DurationOfBreak, reRoute.TimeoutValue); } private string BuildUpstreamTemplate(FileReRoute reRoute) diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index a653224a..314f13bf 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -29,5 +29,8 @@ namespace Ocelot.Configuration.File public string DownstreamScheme {get;set;} public string DownstreamHost {get;set;} public int DownstreamPort { get; set; } + public int ExceptionsAllowedBeforeBreaking { get; set; } + public int DurationOfBreak { get; set; } + public int TimeoutValue { get; set; } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 960374cc..f6e188b7 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -10,7 +10,8 @@ namespace Ocelot.Configuration bool isAuthenticated, AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries, string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery, - string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func downstreamHostAndPort, string downstreamScheme) + string serviceDiscoveryProvider, string serviceDiscoveryAddress, Func downstreamHostAndPort, string downstreamScheme, + int exceptionsAllowedBeforeBreaking =3, int durationofBreak =8, int timeoutValue = 5000) { DownstreamPathTemplate = downstreamPathTemplate; UpstreamTemplate = upstreamTemplate; @@ -35,6 +36,9 @@ namespace Ocelot.Configuration ServiceDiscoveryAddress = serviceDiscoveryAddress; DownstreamHostAndPort = downstreamHostAndPort; DownstreamScheme = downstreamScheme; + ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; + DurationOfBreak = durationofBreak; + TimeoutValue = timeoutValue; } public DownstreamPathTemplate DownstreamPathTemplate { get; private set; } @@ -57,5 +61,8 @@ namespace Ocelot.Configuration public string ServiceDiscoveryAddress { get; private set;} public Func DownstreamHostAndPort {get;private set;} public string DownstreamScheme {get;private set;} + public int ExceptionsAllowedBeforeBreaking { get; private set; } + public int DurationOfBreak { get; private set; } + public int TimeoutValue { get; private set; } } } \ No newline at end of file diff --git a/src/Ocelot/Request/Builder/HttpRequestCreator.cs b/src/Ocelot/Request/Builder/HttpRequestCreator.cs index 91234331..27ba4c07 100644 --- a/src/Ocelot/Request/Builder/HttpRequestCreator.cs +++ b/src/Ocelot/Request/Builder/HttpRequestCreator.cs @@ -15,7 +15,8 @@ namespace Ocelot.Request.Builder IRequestCookieCollection cookies, QueryString queryString, string contentType, - RequestId.RequestId requestId) + RequestId.RequestId requestId, + Values.QoS qos) { var request = await new RequestBuilder() .WithHttpMethod(httpMethod) @@ -26,6 +27,7 @@ namespace Ocelot.Request.Builder .WithHeaders(headers) .WithRequestId(requestId) .WithCookies(cookies) + .WithQos(qos) .Build(); return new OkResponse(request); diff --git a/src/Ocelot/Request/Builder/IRequestCreator.cs b/src/Ocelot/Request/Builder/IRequestCreator.cs index 7641d848..858636b5 100644 --- a/src/Ocelot/Request/Builder/IRequestCreator.cs +++ b/src/Ocelot/Request/Builder/IRequestCreator.cs @@ -14,6 +14,7 @@ namespace Ocelot.Request.Builder IRequestCookieCollection cookies, QueryString queryString, string contentType, - RequestId.RequestId requestId); + RequestId.RequestId requestId, + Values.QoS qos); } } diff --git a/src/Ocelot/Request/Builder/RequestBuilder.cs b/src/Ocelot/Request/Builder/RequestBuilder.cs index c9463993..d7f35ef7 100644 --- a/src/Ocelot/Request/Builder/RequestBuilder.cs +++ b/src/Ocelot/Request/Builder/RequestBuilder.cs @@ -22,6 +22,7 @@ namespace Ocelot.Request.Builder private RequestId.RequestId _requestId; private IRequestCookieCollection _cookies; private readonly string[] _unsupportedHeaders = {"host"}; + private Values.QoS _qos; public RequestBuilder WithHttpMethod(string httpMethod) { @@ -71,6 +72,12 @@ namespace Ocelot.Request.Builder return this; } + public RequestBuilder WithQos(Values.QoS qos) + { + _qos = qos; + return this; + } + public async Task Build() { var uri = CreateUri(); @@ -90,7 +97,7 @@ namespace Ocelot.Request.Builder var cookieContainer = CreateCookieContainer(uri); - return new Request(httpRequestMessage, cookieContainer); + return new Request(httpRequestMessage, cookieContainer, _qos); } private Uri CreateUri() diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs index a2c5194b..a875ea8c 100644 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs +++ b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs @@ -32,7 +32,8 @@ namespace Ocelot.Request.Middleware var buildResult = await _requestCreator .Build(context.Request.Method, DownstreamUrl, context.Request.Body, context.Request.Headers, context.Request.Cookies, context.Request.QueryString, - context.Request.ContentType, new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier)); + context.Request.ContentType, new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier), + new Values.QoS(DownstreamRoute.ReRoute.ExceptionsAllowedBeforeBreaking, DownstreamRoute.ReRoute.DurationOfBreak, DownstreamRoute.ReRoute.TimeoutValue)); if (buildResult.IsError) { diff --git a/src/Ocelot/Request/Request.cs b/src/Ocelot/Request/Request.cs index d43071e1..60e23b1e 100644 --- a/src/Ocelot/Request/Request.cs +++ b/src/Ocelot/Request/Request.cs @@ -1,17 +1,20 @@ -using System.Net; +using Ocelot.Values; +using System.Net; using System.Net.Http; namespace Ocelot.Request { public class Request { - public Request(HttpRequestMessage httpRequestMessage, CookieContainer cookieContainer) + public Request(HttpRequestMessage httpRequestMessage, CookieContainer cookieContainer, QoS qos) { HttpRequestMessage = httpRequestMessage; CookieContainer = cookieContainer; + Qos = qos; } public HttpRequestMessage HttpRequestMessage { get; private set; } public CookieContainer CookieContainer { get; private set; } + public QoS Qos { get; private set; } } } diff --git a/src/Ocelot/Requester/CircuitBreakingDelegatingHandler.cs b/src/Ocelot/Requester/CircuitBreakingDelegatingHandler.cs new file mode 100644 index 00000000..4adc0eeb --- /dev/null +++ b/src/Ocelot/Requester/CircuitBreakingDelegatingHandler.cs @@ -0,0 +1,74 @@ +using Ocelot.Logging; +using Polly; +using Polly.CircuitBreaker; +using Polly.Timeout; +using System; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Ocelot.Requester +{ + public class CircuitBreakingDelegatingHandler : DelegatingHandler + { + private readonly IOcelotLogger _logger; + private readonly int _exceptionsAllowedBeforeBreaking; + private readonly TimeSpan _durationOfBreak; + private readonly Policy _circuitBreakerPolicy; + private readonly TimeoutPolicy _timeoutPolicy; + + public CircuitBreakingDelegatingHandler(int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak,TimeSpan timeoutValue + ,TimeoutStrategy timeoutStrategy, IOcelotLogger logger, HttpMessageHandler innerHandler) + : base(innerHandler) + { + this._exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; + this._durationOfBreak = durationOfBreak; + + _circuitBreakerPolicy = Policy + .Handle() + .Or() + .Or() + .CircuitBreakerAsync( + exceptionsAllowedBeforeBreaking: exceptionsAllowedBeforeBreaking, + durationOfBreak: durationOfBreak, + onBreak: (ex, breakDelay) => + { + _logger.LogError(".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!", ex); + }, + onReset: () => _logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again."), + onHalfOpen: () => _logger.LogDebug(".Breaker logging: Half-open; next call is a trial.") + ); + _timeoutPolicy = Policy.TimeoutAsync(timeoutValue, timeoutStrategy); + _logger = logger; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + Task responseTask = null; + + try + { + responseTask = Policy.WrapAsync(_circuitBreakerPolicy, _timeoutPolicy).ExecuteAsync(() => + { + return base.SendAsync(request,cancellationToken); + }); + return responseTask; + } + catch (BrokenCircuitException ex) + { + _logger.LogError($"Reached to allowed number of exceptions. Circuit is open. AllowedExceptionCount: {_exceptionsAllowedBeforeBreaking}, DurationOfBreak: {_durationOfBreak}",ex); + throw; + } + catch (HttpRequestException) + { + return responseTask; + } + } + + private static bool IsTransientFailure(HttpResponseMessage result) + { + return result.StatusCode >= HttpStatusCode.InternalServerError; + } + } +} diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs new file mode 100644 index 00000000..ebf19d24 --- /dev/null +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -0,0 +1,41 @@ +using Ocelot.Logging; +using Ocelot.Values; +using Polly.Timeout; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Ocelot.Requester +{ + internal class HttpClientBuilder + { + private readonly Dictionary> handlers = new Dictionary>(); + + public HttpClientBuilder WithCircuitBreaker(QoS qos, IOcelotLogger logger, HttpMessageHandler innerHandler) + { + handlers.Add(5000, () => new CircuitBreakingDelegatingHandler(qos.ExceptionsAllowedBeforeBreaking, qos.DurationOfBreak, qos.TimeoutValue, qos.TimeoutStrategy, logger, innerHandler)); + return this; + } + + internal HttpClient Build() + { + return handlers.Any() ? new HttpClient(CreateHttpMessageHandler()) : new HttpClient(); + } + + private HttpMessageHandler CreateHttpMessageHandler() + { + HttpMessageHandler httpMessageHandler = new HttpClientHandler(); + + handlers.OrderByDescending(handler => handler.Key).Select(handler => handler.Value).Reverse().ToList().ForEach(handler => + { + var delegatingHandler = handler(); + delegatingHandler.InnerHandler = httpMessageHandler; + httpMessageHandler = delegatingHandler; + }); + + return httpMessageHandler; + } + } +} diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index 8056adeb..35c405d1 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -4,9 +4,6 @@ using System.Net.Http; using System.Threading.Tasks; using Ocelot.Errors; using Ocelot.Responses; -using Polly; -using Polly.Timeout; -using Polly.CircuitBreaker; using Ocelot.Logging; namespace Ocelot.Requester @@ -14,7 +11,7 @@ namespace Ocelot.Requester public class HttpClientHttpRequester : IHttpRequester { private readonly IOcelotLogger _logger; - + public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger(); @@ -22,46 +19,26 @@ namespace Ocelot.Requester public async Task> GetResponse(Request.Request request) { - double timeoutvalue = 5000; - TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic; - - var timeoutPolicy = Policy - .TimeoutAsync(TimeSpan.FromMilliseconds(timeoutvalue), timeoutStrategy); - - var circuitBreakerPolicy = Policy - .Handle() - .Or() - .Or() - .CircuitBreakerAsync( - exceptionsAllowedBeforeBreaking: 4, - durationOfBreak: TimeSpan.FromSeconds(8), - onBreak: (ex, breakDelay) => - { - _logger.LogError(".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!", ex); - }, - onReset: () => _logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again."), - onHalfOpen: () => _logger.LogDebug(".Breaker logging: Half-open; next call is a trial.") - ); + HttpClientBuilder builder = new HttpClientBuilder(); using (var handler = new HttpClientHandler { CookieContainer = request.CookieContainer }) - using (var httpClient = new HttpClient(handler)) { - try + builder.WithCircuitBreaker(request.Qos, _logger, handler); + using (var httpClient = builder.Build()) { - // Retry the following call according to the policy - 3 times. - HttpResponseMessage response = await Policy.WrapAsync(circuitBreakerPolicy, timeoutPolicy).ExecuteAsync(() => + try { - return httpClient.SendAsync(request.HttpRequestMessage); - }); - return new OkResponse(response); - } - catch (BrokenCircuitException exception) - { - return - new ErrorResponse(new List - { - new UnableToCompleteRequestError(exception) - }); + var response = await httpClient.SendAsync(request.HttpRequestMessage); + return new OkResponse(response); + } + catch (Exception exception) + { + return + new ErrorResponse(new List + { + new UnableToCompleteRequestError(exception) + }); + } } } } diff --git a/src/Ocelot/Requester/IHttpRequester.cs b/src/Ocelot/Requester/IHttpRequester.cs index 201c3add..e3972957 100644 --- a/src/Ocelot/Requester/IHttpRequester.cs +++ b/src/Ocelot/Requester/IHttpRequester.cs @@ -7,5 +7,7 @@ namespace Ocelot.Requester public interface IHttpRequester { Task> GetResponse(Request.Request request); + + } } diff --git a/src/Ocelot/Values/QoS.cs b/src/Ocelot/Values/QoS.cs new file mode 100644 index 00000000..b3e8df6a --- /dev/null +++ b/src/Ocelot/Values/QoS.cs @@ -0,0 +1,27 @@ +using Polly.Timeout; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Values +{ + public class QoS + { + public QoS(int exceptionsAllowedBeforeBreaking, int durationofBreak, int timeoutValue, TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) + { + ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; + DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak); + TimeoutValue = TimeSpan.FromMilliseconds(timeoutValue); + TimeoutStrategy = timeoutStrategy; + } + + public int ExceptionsAllowedBeforeBreaking { get; private set; } + + public TimeSpan DurationOfBreak { get; private set; } + + public TimeSpan TimeoutValue { get; private set; } + + public TimeoutStrategy TimeoutStrategy { get; private set; } + } +} diff --git a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs index 8b14f4f1..4a23282c 100644 --- a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs @@ -125,6 +125,9 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, AuthenticationOptions = new FileAuthenticationOptions { AdditionalScopes = new List(), @@ -165,6 +168,9 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, AuthenticationOptions = new FileAuthenticationOptions { AdditionalScopes = new List(), @@ -205,6 +211,9 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, AuthenticationOptions = new FileAuthenticationOptions { AdditionalScopes = new List(), diff --git a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs index 1f86c6ff..17f99528 100644 --- a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs @@ -43,6 +43,9 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, AuthenticationOptions = new FileAuthenticationOptions { AdditionalScopes = new List(), diff --git a/test/Ocelot.AcceptanceTests/CachingTests.cs b/test/Ocelot.AcceptanceTests/CachingTests.cs index e4e628af..46f5dc3c 100644 --- a/test/Ocelot.AcceptanceTests/CachingTests.cs +++ b/test/Ocelot.AcceptanceTests/CachingTests.cs @@ -37,6 +37,9 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, FileCacheOptions = new FileCacheOptions { TtlSeconds = 100 @@ -73,6 +76,9 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, FileCacheOptions = new FileCacheOptions { TtlSeconds = 1 diff --git a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs index 81824602..9cb8e72c 100644 --- a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs @@ -35,7 +35,10 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", - UpstreamHttpMethod = "Get" + UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000 } } }; @@ -63,7 +66,10 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", - ReRouteIsCaseSensitive = false + ReRouteIsCaseSensitive = false, + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -91,7 +97,10 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", - ReRouteIsCaseSensitive = true + ReRouteIsCaseSensitive = true, + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000 } } }; @@ -119,7 +128,10 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/PRODUCTS/{productId}", UpstreamHttpMethod = "Get", - ReRouteIsCaseSensitive = true + ReRouteIsCaseSensitive = true, + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -147,7 +159,10 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", - ReRouteIsCaseSensitive = true + ReRouteIsCaseSensitive = true, + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -175,7 +190,10 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/PRODUCTS/{productId}", UpstreamHttpMethod = "Get", - ReRouteIsCaseSensitive = true + ReRouteIsCaseSensitive = true, + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000 } } }; diff --git a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs index 08bbd968..4c036eac 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs @@ -57,6 +57,9 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, AuthenticationOptions = new FileAuthenticationOptions { AdditionalScopes = new List diff --git a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs index 04dc25db..fe8ff799 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs @@ -57,6 +57,9 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, AuthenticationOptions = new FileAuthenticationOptions { AdditionalScopes = new List diff --git a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs index f6f6de20..1ad929de 100644 --- a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -51,6 +51,9 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -88,6 +91,9 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -125,6 +131,9 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -162,6 +171,9 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -199,6 +211,9 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -236,6 +251,9 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; diff --git a/test/Ocelot.AcceptanceTests/RequestIdTests.cs b/test/Ocelot.AcceptanceTests/RequestIdTests.cs index 9334786b..2273eb2c 100644 --- a/test/Ocelot.AcceptanceTests/RequestIdTests.cs +++ b/test/Ocelot.AcceptanceTests/RequestIdTests.cs @@ -39,7 +39,10 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - RequestIdKey = _steps.RequestIdKey + RequestIdKey = _steps.RequestIdKey, + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -67,7 +70,10 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - RequestIdKey = _steps.RequestIdKey + RequestIdKey = _steps.RequestIdKey, + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -97,6 +103,9 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } }, GlobalConfiguration = new FileGlobalConfiguration diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 4f97114f..acfec46c 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -46,6 +46,9 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, UpstreamTemplate = "/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -74,6 +77,9 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, UpstreamTemplate = "/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -102,6 +108,9 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, UpstreamTemplate = "/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -130,6 +139,9 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, UpstreamTemplate = "/products/", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -158,6 +170,9 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, UpstreamTemplate = "/products", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -186,6 +201,9 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -212,7 +230,10 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", DownstreamPort = 51879, UpstreamTemplate = "/products/{productId}", - UpstreamHttpMethod = "Get" + UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -240,7 +261,10 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", UpstreamTemplate = "/", - UpstreamHttpMethod = "Post" + UpstreamHttpMethod = "Post", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; @@ -269,6 +293,9 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", DownstreamPort = 51879, UpstreamHttpMethod = "Get", + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak =5, + TimeoutValue = 5000, } } }; diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index f28abefe..0c347f55 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamTemplate":"/","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":41879}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Address":null}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"/","UpstreamTemplate":"/","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":41879,"ExceptionsAllowedBeforeBreaking":3,"DurationOfBreak":5,"TimeoutValue":5000}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Address":null}}} \ No newline at end of file diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index f7e2bb75..a887f3fb 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -1,119 +1,149 @@ { "ReRoutes": [ - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "http", - "DownstreamHost": "localhost", - "DownstreamPort": 52876, - "UpstreamTemplate": "/identityserverexample", - "UpstreamHttpMethod": "Get", - "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": "/", + "DownstreamScheme": "http", + "DownstreamHost": "localhost", + "DownstreamPort": 52876, + "UpstreamTemplate": "/identityserverexample", + "UpstreamHttpMethod": "Get", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000, + "AuthenticationOptions": { + "Provider": "IdentityServer", + "ProviderRootUrl": "http://localhost:52888", + "ScopeName": "api", + "AdditionalScopes": [ + "openid", + "offline_access" + ], + "ScopeSecret": "secret" }, - { - "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts", - "UpstreamHttpMethod": "Get", - "FileCacheOptions": { "TtlSeconds": 15 } + "AddHeadersToRequest": { + "CustomerId": "Claims[CustomerId] > value", + "LocationId": "Claims[LocationId] > value", + "UserType": "Claims[sub] > value[0] > |", + "UserId": "Claims[sub] > value[1] > |" }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Get" + "AddClaimsToRequest": { + "CustomerId": "Claims[CustomerId] > value", + "LocationId": "Claims[LocationId] > value", + "UserType": "Claims[sub] > value[0] > |", + "UserId": "Claims[sub] > value[1] > |" }, - { - "DownstreamPathTemplate": "/posts/{postId}/comments", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}/comments", - "UpstreamHttpMethod": "Get" + "AddQueriesToRequest": { + "CustomerId": "Claims[CustomerId] > value", + "LocationId": "Claims[LocationId] > value", + "UserType": "Claims[sub] > value[0] > |", + "UserId": "Claims[sub] > value[1] > |" }, - { - "DownstreamPathTemplate": "/comments", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/comments", - "UpstreamHttpMethod": "Get" - }, - { - "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts", - "UpstreamHttpMethod": "Post" - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Put" - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Patch" - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Delete" - }, - { - "DownstreamPathTemplate": "/api/products", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/products", - "UpstreamHttpMethod": "Get", - "FileCacheOptions": { "TtlSeconds": 15 } + "RouteClaimsRequirement": { + "UserType": "registered" }, + "RequestIdKey": "OcRequestId" + }, + { + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/posts", + "UpstreamHttpMethod": "Get", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Get", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + { + "DownstreamPathTemplate": "/posts/{postId}/comments", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/posts/{postId}/comments", + "UpstreamHttpMethod": "Get", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + { + "DownstreamPathTemplate": "/comments", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/comments", + "UpstreamHttpMethod": "Get", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + { + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/posts", + "UpstreamHttpMethod": "Post", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Put", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Patch", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Delete", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + { + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/products", + "UpstreamHttpMethod": "Get", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000, + "FileCacheOptions": { "TtlSeconds": 15 } + }, { "DownstreamPathTemplate": "/api/products/{productId}", "DownstreamScheme": "http", @@ -123,87 +153,114 @@ "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, - { - "DownstreamPathTemplate": "/api/products", - "DownstreamScheme": "http", - "DownstreamHost": "products20161126090340.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamTemplate": "/products", - "UpstreamHttpMethod": "Post", - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/products/{productId}", - "DownstreamScheme": "http", - "DownstreamHost": "products20161126090340.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", - "UpstreamHttpMethod": "Put", - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/products/{productId}", - "DownstreamScheme": "http", - "DownstreamHost": "products20161126090340.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", - "UpstreamHttpMethod": "Delete", - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamTemplate": "/customers", - "UpstreamHttpMethod": "Get", - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers/{customerId}", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", - "UpstreamHttpMethod": "Get", - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamTemplate": "/customers", - "UpstreamHttpMethod": "Post", - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers/{customerId}", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", - "UpstreamHttpMethod": "Put", - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers/{customerId}", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", - "UpstreamHttpMethod": "Delete", - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts/", - "UpstreamHttpMethod": "Get", - "FileCacheOptions": { "TtlSeconds": 15 } - } + { + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamTemplate": "/products", + "UpstreamHttpMethod": "Post", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamTemplate": "/products/{productId}", + "UpstreamHttpMethod": "Put", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamTemplate": "/products/{productId}", + "UpstreamHttpMethod": "Delete", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/customers", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamTemplate": "/customers", + "UpstreamHttpMethod": "Get", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamTemplate": "/customers/{customerId}", + "UpstreamHttpMethod": "Get", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/customers", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamTemplate": "/customers", + "UpstreamHttpMethod": "Post", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamTemplate": "/customers/{customerId}", + "UpstreamHttpMethod": "Put", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/customers/{customerId}", + "DownstreamScheme": "http", + "DownstreamHost": "customers20161126090811.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamTemplate": "/customers/{customerId}", + "UpstreamHttpMethod": "Delete", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/posts/", + "UpstreamHttpMethod": "Get", + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000, + "FileCacheOptions": { "TtlSeconds": 15 } + } ], "GlobalConfiguration": { "RequestIdKey": "OcRequestId" diff --git a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs index e2f81abe..406e7cb6 100644 --- a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs @@ -72,7 +72,7 @@ namespace Ocelot.UnitTests.Request this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), new CookieContainer()))) + .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), new CookieContainer(),new Values.QoS(3, 8 ,5000, Polly.Timeout.TimeoutStrategy.Pessimistic)))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); @@ -91,7 +91,7 @@ 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(), 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 3a07532e..55d18c38 100644 --- a/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs +++ b/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs @@ -25,6 +25,7 @@ namespace Ocelot.UnitTests.Request private readonly IRequestCreator _requestCreator; private Response _result; private Ocelot.RequestId.RequestId _requestId; + private Ocelot.Values.QoS _qos; public RequestBuilderTests() { @@ -37,6 +38,7 @@ namespace Ocelot.UnitTests.Request { this.Given(x => x.GivenIHaveHttpMethod("GET")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) + .And(x=> x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectDownstreamUrlIsUsed("http://www.bbc.co.uk/")) .BDDfy(); @@ -47,6 +49,8 @@ namespace Ocelot.UnitTests.Request { this.Given(x => x.GivenIHaveHttpMethod("POST")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) + .And(x => x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectHttpMethodIsUsed(HttpMethod.Post)) .BDDfy(); @@ -59,7 +63,9 @@ namespace Ocelot.UnitTests.Request .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) .And(x => x.GivenTheContentTypeIs("application/json")) - .When(x => x.WhenICreateARequest()) + .And(x => x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + + .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectContentIsUsed(new StringContent("Hi from Tom"))) .BDDfy(); } @@ -71,6 +77,8 @@ namespace Ocelot.UnitTests.Request .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) .And(x => x.GivenTheContentTypeIs("application/json")) + .And(x => x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary { @@ -88,6 +96,8 @@ namespace Ocelot.UnitTests.Request .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) .And(x => x.GivenTheContentTypeIs("application/json; charset=utf-8")) + .And(x => x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary { @@ -107,6 +117,8 @@ namespace Ocelot.UnitTests.Request { {"ChopSticks", "Bubbles" } })) + .And(x => x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary { @@ -124,7 +136,8 @@ namespace Ocelot.UnitTests.Request .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary())) .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", requestId))) - .When(x => x.WhenICreateARequest()) + .And(x => x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary { {"RequestId", requestId } @@ -142,7 +155,8 @@ namespace Ocelot.UnitTests.Request {"RequestId", "534534gv54gv45g" } })) .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", Guid.NewGuid().ToString()))) - .When(x => x.WhenICreateARequest()) + .And(x => x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary { {"RequestId", "534534gv54gv45g" } @@ -161,7 +175,8 @@ namespace Ocelot.UnitTests.Request .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary())) .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId(requestIdKey, requestIdValue))) - .When(x => x.WhenICreateARequest()) + .And(x => x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheRequestIdIsNotInTheHeaders()) .BDDfy(); } @@ -171,6 +186,11 @@ namespace Ocelot.UnitTests.Request _requestId = requestId; } + private void GivenTheQos(Ocelot.Values.QoS qos) + { + _qos = qos; + } + [Fact] public void should_use_cookies() { @@ -281,7 +301,7 @@ namespace Ocelot.UnitTests.Request private void WhenICreateARequest() { _result = _requestCreator.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers, - _cookies, _query, _contentType, _requestId).Result; + _cookies, _query, _contentType, _requestId, _qos).Result; } diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index d99a99eb..66c7491e 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -61,7 +61,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()))) + this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),new CookieContainer(), new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic)))) .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) .And(x => x.GivenTheScopedRepoReturns()) .When(x => x.WhenICallTheMiddleware()) From aef6507da347301962f2d7b364b1c24ea084e665 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 3 Feb 2017 07:43:26 +0000 Subject: [PATCH 035/113] fixed failing tests after service discovery changes --- .../FileConfigurationCreatorTests.cs | 49 +++++++++++++++++++ .../LoadBalancer/LoadBalancerFactoryTests.cs | 13 +++++ 2 files changed, 62 insertions(+) diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index b6e28033..b1893b5d 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -27,6 +27,7 @@ namespace Ocelot.UnitTests.Configuration private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator; private readonly Mock _loadBalancerFactory; private readonly Mock _loadBalancerHouse; + private readonly Mock _loadBalancer; public FileConfigurationCreatorTests() { @@ -34,11 +35,39 @@ namespace Ocelot.UnitTests.Configuration _configParser = new Mock(); _validator = new Mock(); _fileConfig = new Mock>(); + _loadBalancerFactory = new Mock(); + _loadBalancerHouse = new Mock(); + _loadBalancer = new Mock(); _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object, _loadBalancerFactory.Object, _loadBalancerHouse.Object); } + [Fact] + public void should_create_load_balancer() + { + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHost = "127.0.0.1", + UpstreamTemplate = "/api/products/{productId}", + DownstreamPathTemplate = "/products/{productId}", + UpstreamHttpMethod = "Get", + } + }, + })) + .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheLoadBalancerFactoryReturns()) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly()) + .And(x => x.ThenTheLoadBalancerHouseIsCalledCorrectly()) + + .BDDfy(); + } + [Fact] public void should_use_downstream_host() { @@ -70,6 +99,7 @@ namespace Ocelot.UnitTests.Configuration .BDDfy(); } + [Fact] public void should_use_downstream_scheme() { this.Given(x => x.GivenTheConfigIs(new FileConfiguration @@ -580,5 +610,24 @@ namespace Ocelot.UnitTests.Configuration } } + + private void GivenTheLoadBalancerFactoryReturns() + { + _loadBalancerFactory + .Setup(x => x.Get(It.IsAny())) + .Returns(_loadBalancer.Object); + } + + private void TheLoadBalancerFactoryIsCalledCorrectly() + { + _loadBalancerFactory + .Verify(x => x.Get(It.IsAny()), Times.Once); + } + + private void ThenTheLoadBalancerHouseIsCalledCorrectly() + { + _loadBalancerHouse + .Verify(x => x.Add(It.IsAny(), _loadBalancer.Object), Times.Once); + } } } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index 7361c726..d645b1b6 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -15,13 +15,22 @@ namespace Ocelot.UnitTests.LoadBalancer private LoadBalancerFactory _factory; private ILoadBalancer _result; private Mock _serviceProviderFactory; + private Mock _serviceProvider; public LoadBalancerFactoryTests() { _serviceProviderFactory = new Mock(); + _serviceProvider = new Mock(); _factory = new LoadBalancerFactory(_serviceProviderFactory.Object); } + private void GivenTheServiceProviderFactoryReturns() + { + _serviceProviderFactory + .Setup(x => x.Get(It.IsAny())) + .Returns(_serviceProvider.Object); + } + [Fact] public void should_return_no_load_balancer() { @@ -29,6 +38,7 @@ namespace Ocelot.UnitTests.LoadBalancer .Build(); this.Given(x => x.GivenAReRoute(reRoute)) + .And(x => x.GivenTheServiceProviderFactoryReturns()) .When(x => x.WhenIGetTheLoadBalancer()) .Then(x => x.ThenTheLoadBalancerIsReturned()) .BDDfy(); @@ -42,6 +52,7 @@ namespace Ocelot.UnitTests.LoadBalancer .Build(); this.Given(x => x.GivenAReRoute(reRoute)) + .And(x => x.GivenTheServiceProviderFactoryReturns()) .When(x => x.WhenIGetTheLoadBalancer()) .Then(x => x.ThenTheLoadBalancerIsReturned()) .BDDfy(); @@ -55,6 +66,7 @@ namespace Ocelot.UnitTests.LoadBalancer .Build(); this.Given(x => x.GivenAReRoute(reRoute)) + .And(x => x.GivenTheServiceProviderFactoryReturns()) .When(x => x.WhenIGetTheLoadBalancer()) .Then(x => x.ThenTheLoadBalancerIsReturned()) .BDDfy(); @@ -68,6 +80,7 @@ namespace Ocelot.UnitTests.LoadBalancer .Build(); this.Given(x => x.GivenAReRoute(reRoute)) + .And(x => x.GivenTheServiceProviderFactoryReturns()) .When(x => x.WhenIGetTheLoadBalancer()) .Then(x => x.ThenTheServiceProviderIsCalledCorrectly()) .BDDfy(); From f285b0e0add544a14e443144477cf3d389a3e229 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 3 Feb 2017 08:00:07 +0000 Subject: [PATCH 036/113] plugged load balancer middleware into Ocelot pipeline, load balanced downstream host and port now used by url creator middleware --- .../Configuration/Builder/ReRouteBuilder.cs | 9 +++++- .../Creator/FileOcelotConfigurationCreator.cs | 10 ++++--- src/Ocelot/Configuration/ReRoute.cs | 4 ++- .../DownstreamUrlCreatorMiddleware.cs | 7 +---- .../Middleware/LoadBalancingMiddleware.cs | 28 +++++++++++-------- .../LoadBalancingMiddlewareExtensions.cs | 2 +- .../Middleware/OcelotMiddlewareExtensions.cs | 4 +++ .../DownstreamUrlCreatorMiddlewareTests.cs | 14 +++++++++- .../LoadBalancerMiddlewareTests.cs | 2 +- 9 files changed, 54 insertions(+), 26 deletions(-) diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 9579330c..e12b1e4b 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -6,6 +6,7 @@ namespace Ocelot.Configuration.Builder { public class ReRouteBuilder { + private string _loadBalancerKey; private string _downstreamPathTemplate; private string _upstreamTemplate; private string _upstreamTemplatePattern; @@ -199,6 +200,12 @@ namespace Ocelot.Configuration.Builder return this; } + public ReRouteBuilder WithLoadBalancerKey(string loadBalancerKey) + { + _loadBalancerKey = loadBalancerKey; + return this; + } + public ReRoute Build() { return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, @@ -206,7 +213,7 @@ namespace Ocelot.Configuration.Builder _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _serviceName, _useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, _downstreamScheme, _loadBalancer, - _downstreamHost, _dsPort); + _downstreamHost, _dsPort, _loadBalancerKey); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 61c61fae..e9ceb41a 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -104,6 +104,9 @@ namespace Ocelot.Configuration.Creator && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Address) && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); + //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.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}"; + ReRoute reRoute; if (isAuthenticated) @@ -124,7 +127,7 @@ namespace Ocelot.Configuration.Creator requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), fileReRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort); + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort,loadBalancerKey); } reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), fileReRoute.UpstreamTemplate, @@ -134,11 +137,10 @@ namespace Ocelot.Configuration.Creator requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), fileReRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort); + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort,loadBalancerKey); var loadBalancer = _loadBalanceFactory.Get(reRoute); - //todo - not sure if this is the correct key, but this is probably the only unique key i can think of - _loadBalancerHouse.Add($"{fileReRoute.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}", loadBalancer); + _loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer); return reRoute; } diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 5cbaaebb..987f5be7 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -11,8 +11,9 @@ namespace Ocelot.Configuration List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries, string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery, string serviceDiscoveryProvider, string serviceDiscoveryAddress, - string downstreamScheme, string loadBalancer, string downstreamHost, int downstreamPort) + string downstreamScheme, string loadBalancer, string downstreamHost, int downstreamPort, string loadBalancerKey) { + LoadBalancerKey = loadBalancerKey; LoadBalancer = loadBalancer; DownstreamHost = downstreamHost; DownstreamPort = downstreamPort; @@ -39,6 +40,7 @@ namespace Ocelot.Configuration ServiceDiscoveryAddress = serviceDiscoveryAddress; DownstreamScheme = downstreamScheme; } + public string LoadBalancerKey {get;private set;} public DownstreamPathTemplate DownstreamPathTemplate { get; private set; } public string UpstreamTemplate { get; private set; } public string UpstreamTemplatePattern { get; private set; } diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index bca20466..80365074 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -47,15 +47,12 @@ namespace Ocelot.DownstreamUrlCreator.Middleware var dsScheme = DownstreamRoute.ReRoute.DownstreamScheme; - //todo - get this out of scoped data repo? - var dsHostAndPort = new HostAndPort(DownstreamRoute.ReRoute.DownstreamHost, - DownstreamRoute.ReRoute.DownstreamPort); + var dsHostAndPort = HostAndPort; var dsUrl = _urlBuilder.Build(dsPath.Data.Value, dsScheme, dsHostAndPort); if (dsUrl.IsError) { - //todo - release the lb connection? _logger.LogDebug("IUrlBuilder returned an error, setting pipeline error"); SetPipelineError(dsUrl.Errors); @@ -70,8 +67,6 @@ namespace Ocelot.DownstreamUrlCreator.Middleware await _next.Invoke(context); - //todo - release the lb connection? - _logger.LogDebug("succesfully called next middleware"); } } diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index 0ef74324..99bf5167 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -31,26 +31,32 @@ namespace Ocelot.LoadBalancer.Middleware { _logger.LogDebug("started calling load balancing middleware"); - var loadBalancer = _loadBalancerHouse.Get($"{DownstreamRoute.ReRoute.UpstreamTemplate}{DownstreamRoute.ReRoute.UpstreamHttpMethod}"); - //todo check reponse and return error - - var response = loadBalancer.Data.Lease(); - //todo check reponse and return error - - SetHostAndPortForThisRequest(response.Data); + var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey); + if(loadBalancer.IsError) + { + //set errors and return + } + + var hostAndPort = loadBalancer.Data.Lease(); + if(hostAndPort.IsError) + { + //set errors and return + } + + SetHostAndPortForThisRequest(hostAndPort.Data); + _logger.LogDebug("calling next middleware"); - //todo - try next middleware if we get an exception make sure we release - //the host and port? Not sure if this is the way to go but we shall see! try { await _next.Invoke(context); - loadBalancer.Data.Release(response.Data); + loadBalancer.Data.Release(hostAndPort.Data); } catch (Exception) { - loadBalancer.Data.Release(response.Data); + loadBalancer.Data.Release(hostAndPort.Data); + _logger.LogDebug("error calling next middleware, exception will be thrown to global handler"); throw; } diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs index 52d47bdd..0d0224b8 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs @@ -4,7 +4,7 @@ namespace Ocelot.LoadBalancer.Middleware { public static class LoadBalancingMiddlewareExtensions { - public static IApplicationBuilder UseLoadBalancingMiddlewareExtensions(this IApplicationBuilder builder) + public static IApplicationBuilder UseLoadBalancingMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware(); } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index dfa3b3f4..b0cd3f7e 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -18,6 +18,7 @@ namespace Ocelot.Middleware using System.Threading.Tasks; using Authorisation.Middleware; using Microsoft.AspNetCore.Http; + using Ocelot.LoadBalancer.Middleware; public static class OcelotMiddlewareExtensions { @@ -98,6 +99,9 @@ namespace Ocelot.Middleware // Now we can run any query string transformation logic builder.UseQueryStringBuilderMiddleware(); + // Get the load balancer for this request + builder.UseLoadBalancingMiddleware(); + // This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used builder.UseDownstreamUrlCreatorMiddleware(); diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index 5581a32e..a01677b2 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -36,6 +36,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator private HttpResponseMessage _result; private OkResponse _downstreamPath; private OkResponse _downstreamUrl; + private HostAndPort _hostAndPort; public DownstreamUrlCreatorMiddlewareTests() { @@ -69,14 +70,25 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator [Fact] public void should_call_dependencies_correctly() { + var hostAndPort = new HostAndPort("127.0.0.1", 80); + this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("any old string").Build()))) + .And(x => x.GivenTheHostAndPortIs(hostAndPort)) .And(x => x.TheUrlReplacerReturns("/api/products/1")) - .And(x => x.TheUrlBuilderReturns("http://www.bbc.co.uk/api/products/1")) + .And(x => x.TheUrlBuilderReturns("http://127.0.0.1:80/api/products/1")) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); } + private void GivenTheHostAndPortIs(HostAndPort hostAndPort) + { + _hostAndPort = hostAndPort; + _scopedRepository + .Setup(x => x.Get("HostAndPort")) + .Returns(new OkResponse(_hostAndPort)); + } + private void TheUrlBuilderReturns(string dsUrl) { _downstreamUrl = new OkResponse(new DownstreamUrl(dsUrl)); diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index 7f34c903..93c63884 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -54,7 +54,7 @@ namespace Ocelot.UnitTests.LoadBalancer .UseUrls(_url) .Configure(app => { - app.UseLoadBalancingMiddlewareExtensions(); + app.UseLoadBalancingMiddleware(); }); _server = new TestServer(builder); From c88eeb66bb42a807ef189d9570e388c0a5c7a245 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Fri, 3 Feb 2017 08:58:56 +0000 Subject: [PATCH 037/113] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d96b9868..2e57bd6c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ocelot -[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot) +[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-build) [![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) From 4577ec60d5dc1262ad38da0ef216279499372e77 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Fri, 3 Feb 2017 08:59:45 +0000 Subject: [PATCH 038/113] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e57bd6c..ae1ef033 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ocelot -[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-build) +[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb) [![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) From b0ff2fd317dff2d78844f547a9ab0f94ade9484c Mon Sep 17 00:00:00 2001 From: "tom.pallister" Date: Fri, 3 Feb 2017 13:02:51 +0000 Subject: [PATCH 039/113] fixed failing tests --- .../Creator/FileOcelotConfigurationCreator.cs | 34 +++++++++++-------- .../ServiceCollectionExtensions.cs | 4 +-- .../FileConfigurationCreatorTests.cs | 2 ++ 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index e9ceb41a..2bbc6705 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -108,7 +108,7 @@ namespace Ocelot.Configuration.Creator var loadBalancerKey = $"{fileReRoute.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}"; ReRoute reRoute; - + if (isAuthenticated) { var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider, @@ -120,24 +120,30 @@ namespace Ocelot.Configuration.Creator var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest); var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest); - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), fileReRoute.UpstreamTemplate, + reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), + fileReRoute.UpstreamTemplate, fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, authOptionsForRoute, claimsToHeaders, claimsToClaims, fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), - fileReRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort,loadBalancerKey); - } - - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), fileReRoute.UpstreamTemplate, - fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, - null, new List(), new List(), - fileReRoute.RouteClaimsRequirement, isAuthorised, new List(), - requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), - fileReRoute.ServiceName, useServiceDiscovery, globalConfiguration?.ServiceDiscoveryProvider?.Provider, + fileReRoute.ServiceName, useServiceDiscovery, + globalConfiguration?.ServiceDiscoveryProvider?.Provider, globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort,loadBalancerKey); + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey); + } + else + { + reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), + fileReRoute.UpstreamTemplate, + fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, + null, new List(), new List(), + fileReRoute.RouteClaimsRequirement, isAuthorised, new List(), + requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), + fileReRoute.ServiceName, useServiceDiscovery, + globalConfiguration?.ServiceDiscoveryProvider?.Provider, + globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey); + } var loadBalancer = _loadBalanceFactory.Get(reRoute); _loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer); diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 1615a69b..839a825b 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -61,8 +61,8 @@ namespace Ocelot.DependencyInjection { services.AddMvcCore().AddJsonFormatters(); services.AddLogging(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index b1893b5d..65a61240 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -410,6 +410,7 @@ namespace Ocelot.UnitTests.Configuration })) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheConfigHeaderExtractorReturns(new ClaimToThing("CustomerId", "CustomerId", "", 0))) + .And(x => x.GivenTheLoadBalancerFactoryReturns()) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(expected)) .And(x => x.ThenTheAuthenticationOptionsAre(expected)) @@ -464,6 +465,7 @@ namespace Ocelot.UnitTests.Configuration } })) .And(x => x.GivenTheConfigIsValid()) + .And(x => x.GivenTheLoadBalancerFactoryReturns()) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(expected)) .And(x => x.ThenTheAuthenticationOptionsAre(expected)) From 9828c3b427afe744f7f60dcf6ddf2c49bf559feb Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 3 Feb 2017 22:50:57 +0000 Subject: [PATCH 040/113] started adding consul acceptance test --- build.cake | 4 +- src/Ocelot/Configuration/ReRoute.cs | 3 +- .../ServiceCollectionExtensions.cs | 2 +- src/Ocelot/Errors/OcelotErrorCode.cs | 2 +- .../LoadBalancers/LoadBalancerFactory.cs | 9 +- .../ConfigurationServiceProvider.cs | 4 +- ...ovider.cs => IServiceDiscoveryProvider.cs} | 2 +- .../IServiceDiscoveryProviderFactory.cs | 9 ++ .../IServiceProviderFactory.cs | 9 -- .../ServiceDiscoveryProviderFactory.cs | 18 +++ .../ServiceProviderConfiguraion.cs | 21 +++ .../ServiceProviderFactory.cs | 34 ----- ...ableToFindServiceDiscoveryProviderError.cs | 12 ++ .../UnableToFindServiceProviderError.cs | 12 -- .../ServiceDiscoveryTests.cs | 135 ++++++++++++++++++ test/Ocelot.AcceptanceTests/Steps.cs | 13 ++ .../LoadBalancer/LoadBalancerFactoryTests.cs | 12 +- .../ServiceProviderFactoryTests.cs | 12 +- 18 files changed, 234 insertions(+), 79 deletions(-) rename src/Ocelot/ServiceDiscovery/{IServiceProvider.cs => IServiceDiscoveryProvider.cs} (74%) create mode 100644 src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs delete mode 100644 src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs create mode 100644 src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs create mode 100644 src/Ocelot/ServiceDiscovery/ServiceProviderConfiguraion.cs delete mode 100644 src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs create mode 100644 src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs delete mode 100644 src/Ocelot/ServiceDiscovery/UnableToFindServiceProviderError.cs create mode 100644 test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs diff --git a/build.cake b/build.cake index 1d798d74..26b45b25 100644 --- a/build.cake +++ b/build.cake @@ -42,8 +42,8 @@ var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; // internal build variables - don't change these. var releaseTag = ""; -var buildVersion = committedVersion; var committedVersion = "0.0.0-dev"; +var buildVersion = committedVersion; var target = Argument("target", "Default"); @@ -264,7 +264,7 @@ private string GetNuGetVersionForCommit() /// Updates project version in all of our projects private void PersistVersion(string version) { - Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, version)); + //Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, version)); var projectJsonFiles = GetFiles("./**/project.json"); diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 987f5be7..d9fe60c9 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -11,7 +11,8 @@ namespace Ocelot.Configuration List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries, string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery, string serviceDiscoveryProvider, string serviceDiscoveryAddress, - string downstreamScheme, string loadBalancer, string downstreamHost, int downstreamPort, string loadBalancerKey) + string downstreamScheme, string loadBalancer, string downstreamHost, int downstreamPort, + string loadBalancerKey) { LoadBalancerKey = loadBalancerKey; LoadBalancer = loadBalancer; diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 839a825b..0a6bd42c 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -61,7 +61,7 @@ namespace Ocelot.DependencyInjection { services.AddMvcCore().AddJsonFormatters(); services.AddLogging(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index f2c479df..d24988b9 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -24,7 +24,7 @@ DownstreamHostNullOrEmptyError, ServicesAreNullError, ServicesAreEmptyError, - UnableToFindServiceProviderError, + UnableToFindServiceDiscoveryProviderError, UnableToFindLoadBalancerError } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs index 7e11df39..082f3b61 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs @@ -5,19 +5,20 @@ namespace Ocelot.LoadBalancer.LoadBalancers { public class LoadBalancerFactory : ILoadBalancerFactory { - private readonly IServiceProviderFactory _serviceProviderFactory; - public LoadBalancerFactory(IServiceProviderFactory serviceProviderFactory) + private readonly IServiceDiscoveryProviderFactory _serviceProviderFactory; + public LoadBalancerFactory(IServiceDiscoveryProviderFactory serviceProviderFactory) { _serviceProviderFactory = serviceProviderFactory; } public ILoadBalancer Get(ReRoute reRoute) { - var serviceConfig = new ServiceConfiguraion( + var serviceConfig = new ServiceProviderConfiguraion( reRoute.ServiceName, reRoute.DownstreamHost, reRoute.DownstreamPort, - reRoute.UseServiceDiscovery); + reRoute.UseServiceDiscovery, + reRoute.ServiceDiscoveryProvider); var serviceProvider = _serviceProviderFactory.Get(serviceConfig); diff --git a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs index b207f772..f1045be3 100644 --- a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs +++ b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using Ocelot.Values; - + namespace Ocelot.ServiceDiscovery { - public class ConfigurationServiceProvider : IServiceProvider + public class ConfigurationServiceProvider : IServiceDiscoveryProvider { private List _services; diff --git a/src/Ocelot/ServiceDiscovery/IServiceProvider.cs b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs similarity index 74% rename from src/Ocelot/ServiceDiscovery/IServiceProvider.cs rename to src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs index 60e428c8..2732b5e3 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceProvider.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs @@ -3,7 +3,7 @@ using Ocelot.Values; namespace Ocelot.ServiceDiscovery { - public interface IServiceProvider + public interface IServiceDiscoveryProvider { List Get(); } diff --git a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs new file mode 100644 index 00000000..fe2acaa8 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ocelot.ServiceDiscovery +{ + public interface IServiceDiscoveryProviderFactory + { + IServiceDiscoveryProvider Get(ServiceProviderConfiguraion serviceConfig); + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs deleted file mode 100644 index 62c55f53..00000000 --- a/src/Ocelot/ServiceDiscovery/IServiceProviderFactory.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Ocelot.ServiceDiscovery -{ - public interface IServiceProviderFactory - { - IServiceProvider Get(ServiceConfiguraion serviceConfig); - } -} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs new file mode 100644 index 00000000..cb06e99c --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Ocelot.Values; + +namespace Ocelot.ServiceDiscovery +{ + public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory + { + public IServiceDiscoveryProvider Get(ServiceProviderConfiguraion serviceConfig) + { + var services = new List() + { + new Service(serviceConfig.ServiceName, new HostAndPort(serviceConfig.DownstreamHost, serviceConfig.DownstreamPort)) + }; + + return new ConfigurationServiceProvider(services); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceProviderConfiguraion.cs b/src/Ocelot/ServiceDiscovery/ServiceProviderConfiguraion.cs new file mode 100644 index 00000000..70638aaa --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/ServiceProviderConfiguraion.cs @@ -0,0 +1,21 @@ +namespace Ocelot.ServiceDiscovery +{ + public class ServiceProviderConfiguraion + { + public ServiceProviderConfiguraion(string serviceName, string downstreamHost, + int downstreamPort, bool useServiceDiscovery, string serviceDiscoveryProvider) + { + ServiceName = serviceName; + DownstreamHost = downstreamHost; + DownstreamPort = downstreamPort; + UseServiceDiscovery = useServiceDiscovery; + ServiceDiscoveryProvider = serviceDiscoveryProvider; + } + + public string ServiceName { get; } + public string DownstreamHost { get; } + public int DownstreamPort { get; } + public bool UseServiceDiscovery { get; } + public string ServiceDiscoveryProvider {get;} + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs deleted file mode 100644 index be3b8b8c..00000000 --- a/src/Ocelot/ServiceDiscovery/ServiceProviderFactory.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; -using Ocelot.Values; - -namespace Ocelot.ServiceDiscovery -{ - public class ServiceProviderFactory : IServiceProviderFactory - { - public IServiceProvider Get(ServiceConfiguraion serviceConfig) - { - var services = new List() - { - new Service(serviceConfig.ServiceName, new HostAndPort(serviceConfig.DownstreamHost, serviceConfig.DownstreamPort)) - }; - - return new ConfigurationServiceProvider(services); - } - } - - public class ServiceConfiguraion - { - public ServiceConfiguraion(string serviceName, string downstreamHost, int downstreamPort, bool useServiceDiscovery) - { - ServiceName = serviceName; - DownstreamHost = downstreamHost; - DownstreamPort = downstreamPort; - UseServiceDiscovery = useServiceDiscovery; - } - - public string ServiceName { get; } - public string DownstreamHost { get; } - public int DownstreamPort { get; } - public bool UseServiceDiscovery { get; } - } -} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs b/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs new file mode 100644 index 00000000..163e63ef --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.ServiceDiscovery +{ + public class UnableToFindServiceDiscoveryProviderError : Error + { + public UnableToFindServiceDiscoveryProviderError(string message) + : base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/UnableToFindServiceProviderError.cs b/src/Ocelot/ServiceDiscovery/UnableToFindServiceProviderError.cs deleted file mode 100644 index b8ed1a47..00000000 --- a/src/Ocelot/ServiceDiscovery/UnableToFindServiceProviderError.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.ServiceDiscovery -{ - public class UnableToFindServiceProviderError : Error - { - public UnableToFindServiceProviderError(string message) - : base(message, OcelotErrorCode.UnableToFindServiceProviderError) - { - } - } -} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs new file mode 100644 index 00000000..5994b86b --- /dev/null +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class ServiceDiscoveryTests : IDisposable + { + private IWebHost _builder; + private IWebHost _fakeConsulBuilder; + private readonly Steps _steps; + + public ServiceDiscoveryTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_use_service_discovery_and_load_balance_request() + { + var serviceName = "product"; + var downstreamServiceOneUrl = "http://localhost:51879"; + var downstreamServiceTwoUrl = "http://localhost:51880"; + var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; + var downstreamServiceOneCounter = 0; + var downstreamServiceTwoCounter = 0; + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + UpstreamTemplate = "/", + UpstreamHttpMethod = "Get", + ServiceName = serviceName, + LoadBalancer = "LeastConnection", + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Provider = "Consul" + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, 200, downstreamServiceOneCounter)) + .And(x => x.GivenThereIsAServiceRunningOn(downstreamServiceTwoUrl, 200, downstreamServiceTwoCounter)) + .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceName, downstreamServiceOneUrl, downstreamServiceTwoUrl)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50)) + .Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50, downstreamServiceOneCounter, downstreamServiceTwoCounter)) + .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(downstreamServiceOneCounter,downstreamServiceTwoCounter)) + .BDDfy(); + } + + private void ThenBothServicesCalledRealisticAmountOfTimes(int counterOne, int counterTwo) + { + counterOne.ShouldBeGreaterThan(10); + counterTwo.ShouldBeGreaterThan(10); + } + + private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected, int counterOne, int counterTwo) + { + var total = counterOne + counterTwo; + total.ShouldBe(expected); + } + + private void GivenTheServicesAreRegisteredWithConsul(string serviceName, params string[] urls) + { + //register these services with fake consul + } + + private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) + { + _fakeConsulBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + //do consul shit + }); + }) + .Build(); + + _fakeConsulBuilder.Start(); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode, int counter) + { + _builder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + counter++; + context.Response.StatusCode = statusCode; + }); + }) + .Build(); + + _builder.Start(); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index c2bd7ee7..666c3256 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Threading.Tasks; using CacheManager.Core; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; @@ -153,6 +154,18 @@ namespace Ocelot.AcceptanceTests _response = _ocelotClient.GetAsync(url).Result; } + public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times) + { + var tasks = new Task[times]; + + for (int i = 0; i < times; i++) + { + tasks[i] = _ocelotClient.GetAsync(url); + } + + Task.WaitAll(tasks); + } + public void WhenIGetUrlOnTheApiGateway(string url, string requestId) { _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(RequestIdKey, requestId); diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index d645b1b6..e8e0210b 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -14,20 +14,20 @@ namespace Ocelot.UnitTests.LoadBalancer private ReRoute _reRoute; private LoadBalancerFactory _factory; private ILoadBalancer _result; - private Mock _serviceProviderFactory; - private Mock _serviceProvider; + private Mock _serviceProviderFactory; + private Mock _serviceProvider; public LoadBalancerFactoryTests() { - _serviceProviderFactory = new Mock(); - _serviceProvider = new Mock(); + _serviceProviderFactory = new Mock(); + _serviceProvider = new Mock(); _factory = new LoadBalancerFactory(_serviceProviderFactory.Object); } private void GivenTheServiceProviderFactoryReturns() { _serviceProviderFactory - .Setup(x => x.Get(It.IsAny())) + .Setup(x => x.Get(It.IsAny())) .Returns(_serviceProvider.Object); } @@ -89,7 +89,7 @@ namespace Ocelot.UnitTests.LoadBalancer 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 9bcc2672..82e5cb73 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -7,19 +7,19 @@ namespace Ocelot.UnitTests.ServiceDiscovery { public class ServiceProviderFactoryTests { - private ServiceConfiguraion _serviceConfig; - private IServiceProvider _result; - private readonly ServiceProviderFactory _factory; + private ServiceProviderConfiguraion _serviceConfig; + private IServiceDiscoveryProvider _result; + private readonly ServiceDiscoveryProviderFactory _factory; public ServiceProviderFactoryTests() { - _factory = new ServiceProviderFactory(); + _factory = new ServiceDiscoveryProviderFactory(); } [Fact] public void should_return_no_service_provider() { - var serviceConfig = new ServiceConfiguraion("product", "127.0.0.1", 80, false); + var serviceConfig = new ServiceProviderConfiguraion("product", "127.0.0.1", 80, false, "Does not matter"); this.Given(x => x.GivenTheReRoute(serviceConfig)) .When(x => x.WhenIGetTheServiceProvider()) @@ -27,7 +27,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery .BDDfy(); } - private void GivenTheReRoute(ServiceConfiguraion serviceConfig) + private void GivenTheReRoute(ServiceProviderConfiguraion serviceConfig) { _serviceConfig = serviceConfig; } From 2a03d33d7e8e5252a00be671afc5d455409b4a86 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 3 Feb 2017 22:53:53 +0000 Subject: [PATCH 041/113] added cake log back in --- build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cake b/build.cake index 26b45b25..1a1f005d 100644 --- a/build.cake +++ b/build.cake @@ -264,7 +264,7 @@ private string GetNuGetVersionForCommit() /// Updates project version in all of our projects private void PersistVersion(string version) { - //Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, version)); + Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, version)); var projectJsonFiles = GetFiles("./**/project.json"); From 9c9315a94f45bead2d5f80e7de444510408356bd Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 3 Feb 2017 22:59:00 +0000 Subject: [PATCH 042/113] updated tests url --- test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index 5994b86b..b2134a70 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -27,8 +27,8 @@ namespace Ocelot.AcceptanceTests public void should_use_service_discovery_and_load_balance_request() { var serviceName = "product"; - var downstreamServiceOneUrl = "http://localhost:51879"; - var downstreamServiceTwoUrl = "http://localhost:51880"; + var downstreamServiceOneUrl = "http://localhost:50879"; + var downstreamServiceTwoUrl = "http://localhost:50880"; var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; var downstreamServiceOneCounter = 0; var downstreamServiceTwoCounter = 0; From 84f01433b5ed2bc5f71753f5a90236d43cdeca40 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 4 Feb 2017 11:47:07 +0000 Subject: [PATCH 043/113] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae1ef033..cb5e8bd5 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Install Ocelot and it's dependecies 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-Package Ocelot -Pre` +`Install-Package Ocelot` All versions can be found [here](https://www.nuget.org/packages/Ocelot/) From 7900aa3f49553ce7057650e14173660a103eefd1 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 4 Feb 2017 12:06:33 +0000 Subject: [PATCH 044/113] got sidetracked and downgraded to .net core app 1.1 that actually exists no idea why i had 1.4 :( --- .../ConsulServiceDiscoveryProvider.cs | 16 +++++ .../ServiceDiscoveryProviderFactory.cs | 10 ++++ src/Ocelot/project.json | 59 ++++++++++--------- .../TestConfiguration.cs | 4 +- test/Ocelot.AcceptanceTests/project.json | 2 +- test/Ocelot.Benchmarks/project.json | 4 +- test/Ocelot.ManualTest/project.json | 2 +- .../ServiceProviderFactoryTests.cs | 11 ++++ test/Ocelot.UnitTests/project.json | 2 +- 9 files changed, 73 insertions(+), 37 deletions(-) create mode 100644 src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs diff --git a/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs new file mode 100644 index 00000000..af5c4ddc --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Values; + +namespace Ocelot.ServiceDiscovery +{ + public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider + { + public List Get() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index cb06e99c..e8701418 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -7,6 +7,11 @@ namespace Ocelot.ServiceDiscovery { public IServiceDiscoveryProvider Get(ServiceProviderConfiguraion serviceConfig) { + if (serviceConfig.UseServiceDiscovery) + { + return GetServiceDiscoveryProvider(serviceConfig.ServiceName, serviceConfig.ServiceDiscoveryProvider); + } + var services = new List() { new Service(serviceConfig.ServiceName, new HostAndPort(serviceConfig.DownstreamHost, serviceConfig.DownstreamPort)) @@ -14,5 +19,10 @@ namespace Ocelot.ServiceDiscovery return new ConfigurationServiceProvider(services); } + + private IServiceDiscoveryProvider GetServiceDiscoveryProvider(string serviceName, string serviceProviderName) + { + return new ConsulServiceDiscoveryProvider(); + } } } \ No newline at end of file diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 8d259469..6ce4ffbb 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -1,41 +1,42 @@ { "version": "0.0.0-dev", - "dependencies": { - "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", - "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", - "Microsoft.Extensions.Configuration.Json": "1.1.0", - "Microsoft.Extensions.Logging": "1.1.0", - "Microsoft.Extensions.Logging.Console": "1.1.0", - "Microsoft.Extensions.Logging.Debug": "1.1.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", - "Microsoft.AspNetCore.Http": "1.1.0", - "System.Text.RegularExpressions": "4.3.0", - "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", - "Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0", - "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0", - "Microsoft.AspNetCore.Authentication.Cookies": "1.1.0", - "Microsoft.AspNetCore.Authentication.Google": "1.1.0", - "Microsoft.AspNetCore.Authentication.Facebook": "1.1.0", - "Microsoft.AspNetCore.Authentication.Twitter": "1.1.0", - "Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.1.0", - "Microsoft.AspNetCore.Authentication": "1.1.0", - "IdentityServer4.AccessTokenValidation": "1.0.2", - "Microsoft.AspNetCore.Mvc": "1.1.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", - "Microsoft.NETCore.App": "1.1.0", - "CacheManager.Core": "0.9.2", - "CacheManager.Microsoft.Extensions.Configuration": "0.9.2", - "CacheManager.Microsoft.Extensions.Logging": "0.9.2" - }, + "dependencies": { + "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", + "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", + "Microsoft.Extensions.Configuration.Json": "1.1.0", + "Microsoft.Extensions.Logging": "1.1.0", + "Microsoft.Extensions.Logging.Console": "1.1.0", + "Microsoft.Extensions.Logging.Debug": "1.1.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", + "Microsoft.AspNetCore.Http": "1.1.0", + "System.Text.RegularExpressions": "4.3.0", + "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", + "Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0", + "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0", + "Microsoft.AspNetCore.Authentication.Cookies": "1.1.0", + "Microsoft.AspNetCore.Authentication.Google": "1.1.0", + "Microsoft.AspNetCore.Authentication.Facebook": "1.1.0", + "Microsoft.AspNetCore.Authentication.Twitter": "1.1.0", + "Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.1.0", + "Microsoft.AspNetCore.Authentication": "1.1.0", + "IdentityServer4.AccessTokenValidation": "1.0.2", + "Microsoft.AspNetCore.Mvc": "1.1.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", + "Microsoft.NETCore.App": "1.1.0", + "CacheManager.Core": "0.9.2", + "CacheManager.Microsoft.Extensions.Configuration": "0.9.2", + "CacheManager.Microsoft.Extensions.Logging": "0.9.2", + "Consul": "0.7.2.1" + }, "runtimes": { "win10-x64": {}, "osx.10.11-x64":{}, "win7-x64": {} }, "frameworks": { - "netcoreapp1.4": { + "netcoreapp1.1": { "imports": [ ] } diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index 0aa730be..ce802efb 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -4,14 +4,12 @@ public static class TestConfiguration { - public static double Version => 1.4; + public static double Version => 1.1; public static string ConfigurationPath => GetConfigurationPath(); public static string GetConfigurationPath() { var osArchitecture = RuntimeInformation.OSArchitecture.ToString(); - - var oSDescription = string.Empty; if(RuntimeInformation.OSDescription.ToLower().Contains("darwin")) { diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index 17f35a3c..2e5f9ee8 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -40,7 +40,7 @@ "win7-x64": {} }, "frameworks": { - "netcoreapp1.4": { + "netcoreapp1.1": { "imports": [ ] } diff --git a/test/Ocelot.Benchmarks/project.json b/test/Ocelot.Benchmarks/project.json index 5f7a4987..061a2223 100644 --- a/test/Ocelot.Benchmarks/project.json +++ b/test/Ocelot.Benchmarks/project.json @@ -6,7 +6,7 @@ "dependencies": { "Ocelot": "0.0.0-dev", - "BenchmarkDotNet": "0.10.1" + "BenchmarkDotNet": "0.10.2" }, "runtimes": { "win10-x64": {}, @@ -14,7 +14,7 @@ "win7-x64": {} }, "frameworks": { - "netcoreapp1.4": { + "netcoreapp1.1": { "imports": [ ] } diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index 3ae09ccb..cf67f9bd 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -24,7 +24,7 @@ "win7-x64": {} }, "frameworks": { - "netcoreapp1.4": { + "netcoreapp1.1": { "imports": [ ] } diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index 82e5cb73..97fb265a 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -27,6 +27,17 @@ namespace Ocelot.UnitTests.ServiceDiscovery .BDDfy(); } + [Fact] + public void should_return_consul_service_provider() + { + var serviceConfig = new ServiceProviderConfiguraion("product", string.Empty, 0, true, "Consul"); + + this.Given(x => x.GivenTheReRoute(serviceConfig)) + .When(x => x.WhenIGetTheServiceProvider()) + .Then(x => x.ThenTheServiceProviderIs()) + .BDDfy(); + } + private void GivenTheReRoute(ServiceProviderConfiguraion serviceConfig) { _serviceConfig = serviceConfig; diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index ab3e6cb1..3151ac57 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -32,7 +32,7 @@ "win7-x64": {} }, "frameworks": { - "netcoreapp1.4": { + "netcoreapp1.1": { "imports": [ ] } From c46dcc05b82207b81a92e5f069ac26d11e20c493 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 4 Feb 2017 13:16:31 +0000 Subject: [PATCH 045/113] started implementing the consul service provider --- .../Configuration/Builder/ReRouteBuilder.cs | 19 +++++-- .../Creator/FileOcelotConfigurationCreator.cs | 38 +++++++------- .../Creator/IOcelotConfigurationCreator.cs | 3 +- .../File/FileServiceDiscoveryProvider.cs | 3 +- .../Provider/IOcelotConfigurationProvider.cs | 5 +- .../Provider/OcelotConfigurationProvider.cs | 7 +-- src/Ocelot/Configuration/ReRoute.cs | 29 +++++------ .../ServiceProviderConfiguraion.cs | 12 +++-- .../Finder/DownstreamRouteFinder.cs | 5 +- .../Finder/IDownstreamRouteFinder.cs | 5 +- .../DownstreamRouteFinderMiddleware.cs | 2 +- .../Extensions/StringExtensions.cs | 24 +++++++++ .../LoadBalancers/ILoadBalancer.cs | 3 +- .../LoadBalancers/ILoadBalancerFactory.cs | 5 +- .../LeastConnectionLoadBalancer.cs | 13 ++--- .../LoadBalancers/LoadBalancerFactory.cs | 23 +++++---- .../LoadBalancers/NoLoadBalancer.cs | 3 +- .../LoadBalancers/RoundRobinLoadBalancer.cs | 3 +- .../Middleware/LoadBalancingMiddleware.cs | 2 +- .../ConfigurationServiceProvider.cs | 5 +- .../ConsulRegistryConfiguration.cs | 16 ++++++ .../ConsulServiceDiscoveryProvider.cs | 43 +++++++++++++++- .../IServiceDiscoveryProvider.cs | 3 +- .../IServiceDiscoveryProviderFactory.cs | 1 + .../ServiceDiscoveryProviderFactory.cs | 14 +++-- src/Ocelot/Values/Service.cs | 22 ++++++-- .../ServiceDiscoveryTests.cs | 5 +- .../Ocelot.AcceptanceTests/configuration.json | 2 +- .../FileConfigurationCreatorTests.cs | 6 +-- .../FileConfigurationProviderTests.cs | 4 +- .../DownstreamRouteFinderMiddlewareTests.cs | 2 +- .../DownstreamRouteFinderTests.cs | 4 +- .../LoadBalancer/LeastConnectionTests.cs | 51 ++++++++++--------- .../LoadBalancer/LoadBalancerFactoryTests.cs | 2 +- .../LoadBalancer/LoadBalancerHouseTests.cs | 5 +- .../LoadBalancerMiddlewareTests.cs | 2 +- .../LoadBalancer/NoLoadBalancerTests.cs | 4 +- .../LoadBalancer/RoundRobinTests.cs | 14 ++--- .../ConfigurationServiceProviderTests.cs | 4 +- .../ServiceProviderFactoryTests.cs | 5 +- .../ServiceDiscovery/ServiceRegistryTests.cs | 4 +- 41 files changed, 282 insertions(+), 140 deletions(-) rename src/Ocelot/{ServiceDiscovery => Configuration}/ServiceProviderConfiguraion.cs (53%) create mode 100644 src/Ocelot/Infrastructure/Extensions/StringExtensions.cs create mode 100644 src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index e12b1e4b..caa09d3f 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -34,6 +34,8 @@ namespace Ocelot.Configuration.Builder private string _downstreamHost; private int _dsPort; private string _loadBalancer; + private string _serviceProviderHost; + private int _serviceProviderPort; public ReRouteBuilder() { @@ -206,14 +208,25 @@ namespace Ocelot.Configuration.Builder return this; } + public ReRouteBuilder WithServiceProviderHost(string serviceProviderHost) + { + _serviceProviderHost = serviceProviderHost; + return this; + } + + public ReRouteBuilder WithServiceProviderPort(int serviceProviderPort) + { + _serviceProviderPort = serviceProviderPort; + return this; + } + public ReRoute Build() { return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, - _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _serviceName, - _useServiceDiscovery, _serviceDiscoveryAddress, _serviceDiscoveryProvider, _downstreamScheme, _loadBalancer, - _downstreamHost, _dsPort, _loadBalancerKey); + _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _downstreamScheme, _loadBalancer, + _downstreamHost, _dsPort, _loadBalancerKey, new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _dsPort, _useServiceDiscovery, _serviceDiscoveryProvider, _serviceProviderHost, _serviceProviderPort)); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 2bbc6705..703239d0 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Ocelot.Configuration.File; @@ -8,7 +9,6 @@ using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Responses; -using Ocelot.ServiceDiscovery; using Ocelot.Utilities; using Ocelot.Values; @@ -46,9 +46,9 @@ namespace Ocelot.Configuration.Creator _logger = logger; } - public Response Create() + public async Task> Create() { - var config = SetUpConfiguration(); + var config = await SetUpConfiguration(); return new OkResponse(config); } @@ -57,7 +57,7 @@ namespace Ocelot.Configuration.Creator /// This method is meant to be tempoary to convert a config to an ocelot config...probably wont keep this but we will see /// will need a refactor at some point as its crap /// - private IOcelotConfiguration SetUpConfiguration() + private async Task SetUpConfiguration() { var response = _configurationValidator.IsValid(_options.Value); @@ -77,14 +77,14 @@ namespace Ocelot.Configuration.Creator foreach (var reRoute in _options.Value.ReRoutes) { - var ocelotReRoute = SetUpReRoute(reRoute, _options.Value.GlobalConfiguration); + var ocelotReRoute = await SetUpReRoute(reRoute, _options.Value.GlobalConfiguration); reRoutes.Add(ocelotReRoute); } return new OcelotConfiguration(reRoutes); } - private ReRoute SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) + private async Task SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) { var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); @@ -101,7 +101,6 @@ namespace Ocelot.Configuration.Creator : fileReRoute.RequestIdKey; var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName) - && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Address) && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); //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 @@ -109,6 +108,13 @@ namespace Ocelot.Configuration.Creator ReRoute reRoute; + var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; + + var serviceProviderConfiguration = new ServiceProviderConfiguraion(fileReRoute.ServiceName, + fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, useServiceDiscovery, + globalConfiguration?.ServiceDiscoveryProvider?.Provider, globalConfiguration?.ServiceDiscoveryProvider?.Host, + serviceProviderPort); + if (isAuthenticated) { var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider, @@ -125,11 +131,10 @@ namespace Ocelot.Configuration.Creator fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, authOptionsForRoute, claimsToHeaders, claimsToClaims, fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, - requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), - fileReRoute.ServiceName, useServiceDiscovery, - globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey); + requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds) + , fileReRoute.DownstreamScheme, + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, + serviceProviderConfiguration); } else { @@ -139,13 +144,12 @@ namespace Ocelot.Configuration.Creator null, new List(), new List(), fileReRoute.RouteClaimsRequirement, isAuthorised, new List(), requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), - fileReRoute.ServiceName, useServiceDiscovery, - globalConfiguration?.ServiceDiscoveryProvider?.Provider, - globalConfiguration?.ServiceDiscoveryProvider?.Address, fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey); + fileReRoute.DownstreamScheme, + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, + serviceProviderConfiguration); } - var loadBalancer = _loadBalanceFactory.Get(reRoute); + var loadBalancer = await _loadBalanceFactory.Get(reRoute); _loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer); return reRoute; } diff --git a/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs index 6cc7c2e8..7547d91f 100644 --- a/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs @@ -1,9 +1,10 @@ +using System.Threading.Tasks; using Ocelot.Responses; namespace Ocelot.Configuration.Creator { public interface IOcelotConfigurationCreator { - Response Create(); + Task> Create(); } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs index 47efc6df..2f26b6ea 100644 --- a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs +++ b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs @@ -3,6 +3,7 @@ namespace Ocelot.Configuration.File public class FileServiceDiscoveryProvider { public string Provider {get;set;} - public string Address {get;set;} + public string Host {get;set;} + public int Port { get; set; } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs index 30ded2e9..3256e44a 100644 --- a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs @@ -1,9 +1,10 @@ -using Ocelot.Responses; +using System.Threading.Tasks; +using Ocelot.Responses; namespace Ocelot.Configuration.Provider { public interface IOcelotConfigurationProvider { - Response Get(); + Task> Get(); } } diff --git a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs index 4b6c5fd2..80fd5697 100644 --- a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs @@ -1,4 +1,5 @@ -using Ocelot.Configuration.Creator; +using System.Threading.Tasks; +using Ocelot.Configuration.Creator; using Ocelot.Configuration.Repository; using Ocelot.Responses; @@ -19,7 +20,7 @@ namespace Ocelot.Configuration.Provider _creator = creator; } - public Response Get() + public async Task> Get() { var repoConfig = _repo.Get(); @@ -30,7 +31,7 @@ namespace Ocelot.Configuration.Provider if (repoConfig.Data == null) { - var creatorConfig = _creator.Create(); + var creatorConfig = await _creator.Create(); if (creatorConfig.IsError) { diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index d9fe60c9..278d0746 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -6,15 +6,20 @@ namespace Ocelot.Configuration { public class ReRoute { - public ReRoute(DownstreamPathTemplate downstreamPathTemplate, string upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, - bool isAuthenticated, AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, - List claimsToClaims, Dictionary routeClaimsRequirement, bool isAuthorised, List claimsToQueries, - string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string serviceName, bool useServiceDiscovery, - string serviceDiscoveryProvider, string serviceDiscoveryAddress, - string downstreamScheme, string loadBalancer, string downstreamHost, int downstreamPort, - string loadBalancerKey) + public ReRoute(DownstreamPathTemplate downstreamPathTemplate, + string upstreamTemplate, string upstreamHttpMethod, + string upstreamTemplatePattern, + bool isAuthenticated, AuthenticationOptions authenticationOptions, + List configurationHeaderExtractorProperties, + List claimsToClaims, + Dictionary routeClaimsRequirement, bool isAuthorised, + List claimsToQueries, + string requestIdKey, bool isCached, CacheOptions fileCacheOptions, + string downstreamScheme, string loadBalancer, string downstreamHost, + int downstreamPort, string loadBalancerKey, ServiceProviderConfiguraion serviceProviderConfiguraion) { LoadBalancerKey = loadBalancerKey; + ServiceProviderConfiguraion = serviceProviderConfiguraion; LoadBalancer = loadBalancer; DownstreamHost = downstreamHost; DownstreamPort = downstreamPort; @@ -35,12 +40,9 @@ namespace Ocelot.Configuration ?? new List(); ClaimsToHeaders = configurationHeaderExtractorProperties ?? new List(); - ServiceName = serviceName; - UseServiceDiscovery = useServiceDiscovery; - ServiceDiscoveryProvider = serviceDiscoveryProvider; - ServiceDiscoveryAddress = serviceDiscoveryAddress; DownstreamScheme = downstreamScheme; } + public string LoadBalancerKey {get;private set;} public DownstreamPathTemplate DownstreamPathTemplate { get; private set; } public string UpstreamTemplate { get; private set; } @@ -56,13 +58,10 @@ namespace Ocelot.Configuration public string RequestIdKey { get; private set; } public bool IsCached { get; private set; } public CacheOptions FileCacheOptions { get; private set; } - public string ServiceName { get; private set;} - public bool UseServiceDiscovery { get; private set;} - public string ServiceDiscoveryProvider { get; private set;} - public string ServiceDiscoveryAddress { get; private set;} public string DownstreamScheme {get;private set;} public string LoadBalancer {get;private set;} public string DownstreamHost { get; private set; } public int DownstreamPort { get; private set; } + public ServiceProviderConfiguraion ServiceProviderConfiguraion { get; private set; } } } \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ServiceProviderConfiguraion.cs b/src/Ocelot/Configuration/ServiceProviderConfiguraion.cs similarity index 53% rename from src/Ocelot/ServiceDiscovery/ServiceProviderConfiguraion.cs rename to src/Ocelot/Configuration/ServiceProviderConfiguraion.cs index 70638aaa..d471a9e5 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceProviderConfiguraion.cs +++ b/src/Ocelot/Configuration/ServiceProviderConfiguraion.cs @@ -1,21 +1,25 @@ -namespace Ocelot.ServiceDiscovery +namespace Ocelot.Configuration { public class ServiceProviderConfiguraion { - public ServiceProviderConfiguraion(string serviceName, string downstreamHost, - int downstreamPort, bool useServiceDiscovery, string serviceDiscoveryProvider) + public ServiceProviderConfiguraion(string serviceName, string downstreamHost, + int downstreamPort, bool useServiceDiscovery, string serviceDiscoveryProvider, string serviceProviderHost, int serviceProviderPort) { ServiceName = serviceName; DownstreamHost = downstreamHost; DownstreamPort = downstreamPort; UseServiceDiscovery = useServiceDiscovery; ServiceDiscoveryProvider = serviceDiscoveryProvider; + ServiceProviderHost = serviceProviderHost; + ServiceProviderPort = serviceProviderPort; } public string ServiceName { get; } public string DownstreamHost { get; } public int DownstreamPort { get; } public bool UseServiceDiscovery { get; } - public string ServiceDiscoveryProvider {get;} + public string ServiceDiscoveryProvider { get; } + public string ServiceProviderHost { get; private set; } + public int ServiceProviderPort { get; private set; } } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 752da281..eacd6912 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Ocelot.Configuration.Provider; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Errors; @@ -21,9 +22,9 @@ namespace Ocelot.DownstreamRouteFinder.Finder _urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; } - public Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod) + public async Task> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod) { - var configuration = _configProvider.Get(); + var configuration = await _configProvider.Get(); var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase)); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs index e351ab2f..7ae3ff79 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs @@ -1,9 +1,10 @@ -using Ocelot.Responses; +using System.Threading.Tasks; +using Ocelot.Responses; namespace Ocelot.DownstreamRouteFinder.Finder { public interface IDownstreamRouteFinder { - Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod); + Task> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod); } } diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index f445b46b..e88bfde8 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -34,7 +34,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware _logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath); - var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method); + var downstreamRoute = await _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method); if (downstreamRoute.IsError) { diff --git a/src/Ocelot/Infrastructure/Extensions/StringExtensions.cs b/src/Ocelot/Infrastructure/Extensions/StringExtensions.cs new file mode 100644 index 00000000..d7458381 --- /dev/null +++ b/src/Ocelot/Infrastructure/Extensions/StringExtensions.cs @@ -0,0 +1,24 @@ +using System; + +namespace Ocelot.Infrastructure.Extensions +{ + public static class StringExtensions + { + public static string TrimStart(this string source, string trim, StringComparison stringComparison = StringComparison.Ordinal) + { + if (source == null) + { + return null; + } + + string s = source; + while (s.StartsWith(trim, stringComparison)) + { + s = s.Substring(trim.Length); + } + + return s; + } + + } +} \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs index 100ee6f0..aa2a8f02 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Ocelot.Responses; using Ocelot.Values; @@ -6,7 +7,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers { public interface ILoadBalancer { - Response Lease(); + Task> Lease(); Response Release(HostAndPort hostAndPort); } } \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs index 55089cde..19fdf3eb 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancerFactory.cs @@ -1,9 +1,10 @@ -using Ocelot.Configuration; +using System.Threading.Tasks; +using Ocelot.Configuration; namespace Ocelot.LoadBalancer.LoadBalancers { public interface ILoadBalancerFactory { - ILoadBalancer Get(ReRoute reRoute); + Task Get(ReRoute reRoute); } } \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs index 4799ab12..38984567 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Ocelot.Errors; using Ocelot.Responses; using Ocelot.Values; @@ -9,20 +10,20 @@ namespace Ocelot.LoadBalancer.LoadBalancers { public class LeastConnectionLoadBalancer : ILoadBalancer { - private Func> _services; - private List _leases; - private string _serviceName; + private readonly Func>> _services; + private readonly List _leases; + private readonly string _serviceName; - public LeastConnectionLoadBalancer(Func> services, string serviceName) + public LeastConnectionLoadBalancer(Func>> services, string serviceName) { _services = services; _serviceName = serviceName; _leases = new List(); } - public Response Lease() + public async Task> Lease() { - var services = _services(); + var services = await _services.Invoke(); if (services == null) { diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs index 082f3b61..08e45d2b 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs @@ -1,4 +1,5 @@ -using Ocelot.Configuration; +using System.Threading.Tasks; +using Ocelot.Configuration; using Ocelot.ServiceDiscovery; namespace Ocelot.LoadBalancer.LoadBalancers @@ -11,25 +12,27 @@ namespace Ocelot.LoadBalancer.LoadBalancers _serviceProviderFactory = serviceProviderFactory; } - public ILoadBalancer Get(ReRoute reRoute) + public async Task Get(ReRoute reRoute) { var serviceConfig = new ServiceProviderConfiguraion( - reRoute.ServiceName, - reRoute.DownstreamHost, - reRoute.DownstreamPort, - reRoute.UseServiceDiscovery, - reRoute.ServiceDiscoveryProvider); + reRoute.ServiceProviderConfiguraion.ServiceName, + reRoute.ServiceProviderConfiguraion.DownstreamHost, + reRoute.ServiceProviderConfiguraion.DownstreamPort, + reRoute.ServiceProviderConfiguraion.UseServiceDiscovery, + reRoute.ServiceProviderConfiguraion.ServiceDiscoveryProvider, + reRoute.ServiceProviderConfiguraion.ServiceProviderHost, + reRoute.ServiceProviderConfiguraion.ServiceProviderPort); var serviceProvider = _serviceProviderFactory.Get(serviceConfig); switch (reRoute.LoadBalancer) { case "RoundRobin": - return new RoundRobinLoadBalancer(serviceProvider.Get()); + return new RoundRobinLoadBalancer(await serviceProvider.Get()); case "LeastConnection": - return new LeastConnectionLoadBalancer(() => serviceProvider.Get(), reRoute.ServiceName); + return new LeastConnectionLoadBalancer(async () => await serviceProvider.Get(), reRoute.ServiceProviderConfiguraion.ServiceName); default: - return new NoLoadBalancer(serviceProvider.Get()); + return new NoLoadBalancer(await serviceProvider.Get()); } } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs index 2788656a..f654dca8 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Ocelot.Responses; using Ocelot.Values; @@ -14,7 +15,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers _services = services; } - public Response Lease() + public async Task> Lease() { var service = _services.FirstOrDefault(); return new OkResponse(service.HostAndPort); diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs index 1ffb46ce..0bb1f829 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Ocelot.Responses; using Ocelot.Values; @@ -14,7 +15,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers _services = services; } - public Response Lease() + public async Task> Lease() { if (_last >= _services.Count) { diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index 99bf5167..d2e9ee8a 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -37,7 +37,7 @@ namespace Ocelot.LoadBalancer.Middleware //set errors and return } - var hostAndPort = loadBalancer.Data.Lease(); + var hostAndPort = await loadBalancer.Data.Lease(); if(hostAndPort.IsError) { //set errors and return diff --git a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs index f1045be3..f6280d7b 100644 --- a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs +++ b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs @@ -1,18 +1,19 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Ocelot.Values; namespace Ocelot.ServiceDiscovery { public class ConfigurationServiceProvider : IServiceDiscoveryProvider { - private List _services; + private readonly List _services; public ConfigurationServiceProvider(List services) { _services = services; } - public List Get() + public async Task> Get() { return _services; } diff --git a/src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs b/src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs new file mode 100644 index 00000000..8d496a85 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs @@ -0,0 +1,16 @@ +namespace Ocelot.ServiceDiscovery +{ + public class ConsulRegistryConfiguration + { + public ConsulRegistryConfiguration(string hostName, int port, string serviceName) + { + HostName = hostName; + Port = port; + ServiceName = serviceName; + } + + public string ServiceName { get; private set; } + public string HostName { get; private set; } + public int Port { get; private set; } + } +} \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs index af5c4ddc..c74c90f0 100644 --- a/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs +++ b/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs @@ -2,15 +2,54 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Consul; +using Ocelot.Infrastructure.Extensions; using Ocelot.Values; namespace Ocelot.ServiceDiscovery { public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider { - public List Get() + private readonly ConsulRegistryConfiguration _configuration; + private readonly ConsulClient _consul; + private const string VersionPrefix = "version-"; + + public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration consulRegistryConfiguration) { - throw new NotImplementedException(); + var consulHost = string.IsNullOrEmpty(consulRegistryConfiguration?.HostName) ? "localhost" : consulRegistryConfiguration.HostName; + var consulPort = consulRegistryConfiguration?.Port ?? 8500; + _configuration = new ConsulRegistryConfiguration(consulHost, consulPort, consulRegistryConfiguration?.ServiceName); + + _consul = new ConsulClient(config => + { + config.Address = new Uri($"http://{_configuration.HostName}:{_configuration.Port}"); + }); + } + + public async Task> Get() + { + var queryResult = await _consul.Health.Service(_configuration.ServiceName, string.Empty, true); + + var services = queryResult.Response.Select(BuildService); + + return services.ToList(); + } + + private Service BuildService(ServiceEntry serviceEntry) + { + return new Service( + serviceEntry.Service.Service, + new HostAndPort(serviceEntry.Service.Address, serviceEntry.Service.Port), + serviceEntry.Service.ID, + GetVersionFromStrings(serviceEntry.Service.Tags), + serviceEntry.Service.Tags ?? Enumerable.Empty()); + } + + private string GetVersionFromStrings(IEnumerable strings) + { + return strings + ?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal)) + .TrimStart(VersionPrefix); } } } diff --git a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs index 2732b5e3..2c643d4b 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Ocelot.Values; namespace Ocelot.ServiceDiscovery { public interface IServiceDiscoveryProvider { - List Get(); + Task> Get(); } } \ No newline at end of file diff --git a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs index fe2acaa8..6c6c3d4c 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs @@ -1,4 +1,5 @@ using System; +using Ocelot.Configuration; namespace Ocelot.ServiceDiscovery { diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index e8701418..00622190 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Ocelot.Configuration; using Ocelot.Values; namespace Ocelot.ServiceDiscovery @@ -9,20 +10,25 @@ namespace Ocelot.ServiceDiscovery { if (serviceConfig.UseServiceDiscovery) { - return GetServiceDiscoveryProvider(serviceConfig.ServiceName, serviceConfig.ServiceDiscoveryProvider); + return GetServiceDiscoveryProvider(serviceConfig.ServiceName, serviceConfig.ServiceDiscoveryProvider, serviceConfig.ServiceProviderHost, serviceConfig.ServiceProviderPort); } var services = new List() { - new Service(serviceConfig.ServiceName, new HostAndPort(serviceConfig.DownstreamHost, serviceConfig.DownstreamPort)) + new Service(serviceConfig.ServiceName, + new HostAndPort(serviceConfig.DownstreamHost, serviceConfig.DownstreamPort), + string.Empty, + string.Empty, + new string[0]) }; return new ConfigurationServiceProvider(services); } - private IServiceDiscoveryProvider GetServiceDiscoveryProvider(string serviceName, string serviceProviderName) + private IServiceDiscoveryProvider GetServiceDiscoveryProvider(string serviceName, string serviceProviderName, string providerHostName, int providerPort) { - return new ConsulServiceDiscoveryProvider(); + var consulRegistryConfiguration = new ConsulRegistryConfiguration(providerHostName, providerPort, serviceName); + return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration); } } } \ No newline at end of file diff --git a/src/Ocelot/Values/Service.cs b/src/Ocelot/Values/Service.cs index 104fbc09..0ba12b79 100644 --- a/src/Ocelot/Values/Service.cs +++ b/src/Ocelot/Values/Service.cs @@ -1,13 +1,29 @@ +using System.Collections.Generic; + namespace Ocelot.Values { public class Service { - public Service(string name, HostAndPort hostAndPort) + public Service(string name, + HostAndPort hostAndPort, + string id, + string version, + IEnumerable tags) { Name = name; HostAndPort = hostAndPort; + Id = id; + Version = version; + Tags = tags; } - public string Name {get; private set;} - public HostAndPort HostAndPort {get; private set;} + public string Id { get; private set; } + + public string Name { get; private set; } + + public string Version { get; private set; } + + public IEnumerable Tags { get; private set; } + + public HostAndPort HostAndPort { get; private set; } } } \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index b2134a70..06bee686 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -29,7 +29,7 @@ namespace Ocelot.AcceptanceTests var serviceName = "product"; var downstreamServiceOneUrl = "http://localhost:50879"; var downstreamServiceTwoUrl = "http://localhost:50880"; - var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; + var fakeConsulServiceDiscoveryUrl = "http://localhost:8500"; var downstreamServiceOneCounter = 0; var downstreamServiceTwoCounter = 0; @@ -51,7 +51,8 @@ namespace Ocelot.AcceptanceTests { ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() { - Provider = "Consul" + Provider = "Consul", + Host = "localhost" } } }; diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index 713b93f1..d7db55c7 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Address":null}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 65a61240..e1da7de0 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -151,7 +151,7 @@ namespace Ocelot.UnitTests.Configuration ServiceDiscoveryProvider = new FileServiceDiscoveryProvider { Provider = "consul", - Address = "127.0.0.1" + Host = "127.0.0.1" } } })) @@ -579,7 +579,7 @@ namespace Ocelot.UnitTests.Configuration private void WhenICreateTheConfig() { - _config = _ocelotConfigurationCreator.Create(); + _config = _ocelotConfigurationCreator.Create().Result; } private void ThenTheReRoutesAre(List expectedReRoutes) @@ -617,7 +617,7 @@ namespace Ocelot.UnitTests.Configuration { _loadBalancerFactory .Setup(x => x.Get(It.IsAny())) - .Returns(_loadBalancer.Object); + .ReturnsAsync(_loadBalancer.Object); } private void TheLoadBalancerFactoryIsCalledCorrectly() diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs index 56fb6487..98e01293 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs @@ -81,7 +81,7 @@ namespace Ocelot.UnitTests.Configuration { _creator .Setup(x => x.Create()) - .Returns(config); + .ReturnsAsync(config); } private void GivenTheRepoReturns(Response config) @@ -93,7 +93,7 @@ namespace Ocelot.UnitTests.Configuration private void WhenIGetTheConfig() { - _result = _ocelotConfigurationProvider.Get(); + _result = _ocelotConfigurationProvider.Get().Result; } private void TheFollowingIsReturned(Response expected) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 0d5a6d48..a80a3168 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -84,7 +84,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _downstreamRoute = new OkResponse(downstreamRoute); _downstreamRouteFinder .Setup(x => x.FindDownstreamRoute(It.IsAny(), It.IsAny())) - .Returns(_downstreamRoute); + .ReturnsAsync(_downstreamRoute); } public void Dispose() diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index c0afca42..dc9978b3 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -159,7 +159,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _reRoutesConfig = reRoutesConfig; _mockConfig .Setup(x => x.Get()) - .Returns(new OkResponse(new OcelotConfiguration(_reRoutesConfig))); + .ReturnsAsync(new OkResponse(new OcelotConfiguration(_reRoutesConfig))); } private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) @@ -169,7 +169,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void WhenICallTheFinder() { - _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod); + _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod).Result; } private void ThenTheFollowingIsReturned(DownstreamRoute expected) diff --git a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs index a8617b22..47b3a7d0 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Responses; using Ocelot.Values; @@ -24,7 +25,7 @@ namespace Ocelot.UnitTests.LoadBalancer var availableServices = new List { - new Service(serviceName, hostAndPort) + new Service(serviceName, hostAndPort, string.Empty, string.Empty, new string[0]) }; this.Given(x => x.GivenAHostAndPort(hostAndPort)) @@ -41,23 +42,23 @@ namespace Ocelot.UnitTests.LoadBalancer var availableServices = new List { - new Service(serviceName, new HostAndPort("127.0.0.1", 80)), - new Service(serviceName, new HostAndPort("127.0.0.2", 80)), - new Service(serviceName, new HostAndPort("127.0.0.3", 80)) + new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), + new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), + new Service(serviceName, new HostAndPort("127.0.0.3", 80), string.Empty, string.Empty, new string[0]) }; _services = availableServices; - _leastConnection = new LeastConnectionLoadBalancer(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName); - var response = _leastConnection.Lease(); + var response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[2].HostAndPort.DownstreamHost); } @@ -69,26 +70,26 @@ namespace Ocelot.UnitTests.LoadBalancer var availableServices = new List { - new Service(serviceName, new HostAndPort("127.0.0.1", 80)), - new Service(serviceName, new HostAndPort("127.0.0.2", 80)), + new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), + new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), }; _services = availableServices; - _leastConnection = new LeastConnectionLoadBalancer(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName); - var response = _leastConnection.Lease(); + var response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); } @@ -100,33 +101,33 @@ namespace Ocelot.UnitTests.LoadBalancer var availableServices = new List { - new Service(serviceName, new HostAndPort("127.0.0.1", 80)), - new Service(serviceName, new HostAndPort("127.0.0.2", 80)), + new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), + new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), }; _services = availableServices; - _leastConnection = new LeastConnectionLoadBalancer(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName); - var response = _leastConnection.Lease(); + var response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); //release this so 2 should have 1 connection and we should get 2 back as our next host and port _leastConnection.Release(availableServices[1].HostAndPort); - response = _leastConnection.Lease(); + response = _leastConnection.Lease().Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); } @@ -172,7 +173,7 @@ namespace Ocelot.UnitTests.LoadBalancer private void GivenTheLoadBalancerStarts(List services, string serviceName) { _services = services; - _leastConnection = new LeastConnectionLoadBalancer(() => _services, serviceName); + _leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName); } private void WhenTheLoadBalancerStarts(List services, string serviceName) @@ -187,7 +188,7 @@ namespace Ocelot.UnitTests.LoadBalancer private void WhenIGetTheNextHostAndPort() { - _result = _leastConnection.Lease(); + _result = _leastConnection.Lease().Result; } private void ThenTheNextHostAndPortIsReturned() diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index e8e0210b..d030eb99 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -99,7 +99,7 @@ namespace Ocelot.UnitTests.LoadBalancer private void WhenIGetTheLoadBalancer() { - _result = _factory.Get(_reRoute); + _result = _factory.Get(_reRoute).Result; } private void ThenTheLoadBalancerIsReturned() diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs index 31a7bd37..471e4b70 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Responses; using Ocelot.Values; @@ -108,7 +109,7 @@ namespace Ocelot.UnitTests.LoadBalancer class FakeLoadBalancer : ILoadBalancer { - public Response Lease() + public Task> Lease() { throw new NotImplementedException(); } @@ -121,7 +122,7 @@ namespace Ocelot.UnitTests.LoadBalancer class FakeRoundRobinLoadBalancer : ILoadBalancer { - public Response Lease() + public Task> Lease() { throw new NotImplementedException(); } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index 93c63884..ab6a59a8 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -82,7 +82,7 @@ namespace Ocelot.UnitTests.LoadBalancer _hostAndPort = new HostAndPort("127.0.0.1", 80); _loadBalancer .Setup(x => x.Lease()) - .Returns(new OkResponse(_hostAndPort)); + .ReturnsAsync(new OkResponse(_hostAndPort)); } private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) diff --git a/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs index a2fd2be8..ac89a6d0 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs @@ -21,7 +21,7 @@ namespace Ocelot.UnitTests.LoadBalancer var services = new List { - new Service("product", hostAndPort) + new Service("product", hostAndPort, string.Empty, string.Empty, new string[0]) }; this.Given(x => x.GivenServices(services)) .When(x => x.WhenIGetTheNextHostAndPort()) @@ -37,7 +37,7 @@ namespace Ocelot.UnitTests.LoadBalancer private void WhenIGetTheNextHostAndPort() { _loadBalancer = new NoLoadBalancer(_services); - _result = _loadBalancer.Lease(); + _result = _loadBalancer.Lease().Result; } private void ThenTheHostAndPortIs(HostAndPort expected) diff --git a/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs b/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs index cb934af8..f2ef5367 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs @@ -19,9 +19,9 @@ namespace Ocelot.UnitTests.LoadBalancer { _services = new List { - new Service("product", new HostAndPort("127.0.0.1", 5000)), - new Service("product", new HostAndPort("127.0.0.1", 5001)), - new Service("product", new HostAndPort("127.0.0.1", 5001)) + new Service("product", new HostAndPort("127.0.0.1", 5000), string.Empty, string.Empty, new string[0]), + new Service("product", new HostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0]), + new Service("product", new HostAndPort("127.0.0.1", 5001), string.Empty, string.Empty, new string[0]) }; _roundRobin = new RoundRobinLoadBalancer(_services); @@ -46,18 +46,18 @@ namespace Ocelot.UnitTests.LoadBalancer while (stopWatch.ElapsedMilliseconds < 1000) { - var address = _roundRobin.Lease(); + var address = _roundRobin.Lease().Result; address.Data.ShouldBe(_services[0].HostAndPort); - address = _roundRobin.Lease(); + address = _roundRobin.Lease().Result; address.Data.ShouldBe(_services[1].HostAndPort); - address = _roundRobin.Lease(); + address = _roundRobin.Lease().Result; address.Data.ShouldBe(_services[2].HostAndPort); } } private void GivenIGetTheNextAddress() { - _hostAndPort = _roundRobin.Lease(); + _hostAndPort = _roundRobin.Lease().Result; } private void ThenTheNextAddressIndexIs(int index) diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs index 182dd514..f1e732e7 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs @@ -21,7 +21,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery var services = new List { - new Service("product", hostAndPort) + new Service("product", hostAndPort, string.Empty, string.Empty, new string[0]) }; this.Given(x => x.GivenServices(services)) @@ -38,7 +38,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery private void WhenIGetTheService() { _serviceProvider = new ConfigurationServiceProvider(_expected); - _result = _serviceProvider.Get(); + _result = _serviceProvider.Get().Result; } private void ThenTheFollowingIsReturned(List services) diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index 97fb265a..7dae5e47 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -1,3 +1,4 @@ +using Ocelot.Configuration; using Ocelot.ServiceDiscovery; using Shouldly; using TestStack.BDDfy; @@ -19,7 +20,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery [Fact] public void should_return_no_service_provider() { - var serviceConfig = new ServiceProviderConfiguraion("product", "127.0.0.1", 80, false, "Does not matter"); + var serviceConfig = new ServiceProviderConfiguraion("product", "127.0.0.1", 80, false, "Does not matter", string.Empty, 0); this.Given(x => x.GivenTheReRoute(serviceConfig)) .When(x => x.WhenIGetTheServiceProvider()) @@ -30,7 +31,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery [Fact] public void should_return_consul_service_provider() { - var serviceConfig = new ServiceProviderConfiguraion("product", string.Empty, 0, true, "Consul"); + var serviceConfig = new ServiceProviderConfiguraion("product", string.Empty, 0, true, "Consul", string.Empty, 0); this.Given(x => x.GivenTheReRoute(serviceConfig)) .When(x => x.WhenIGetTheServiceProvider()) diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs index 61f7c975..87425329 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceRegistryTests.cs @@ -50,13 +50,13 @@ namespace Ocelot.UnitTests.ServiceDiscovery private void GivenAServiceIsRegistered(string name, string address, int port) { - _service = new Service(name, new HostAndPort(address, port)); + _service = new Service(name, new HostAndPort(address, port), string.Empty, string.Empty, new string[0]); _serviceRepository.Set(_service); } private void GivenAServiceToRegister(string name, string address, int port) { - _service = new Service(name, new HostAndPort(address, port)); + _service = new Service(name, new HostAndPort(address, port), string.Empty, string.Empty, new string[0]); } private void WhenIRegisterTheService() From fb0f101732277e31134ce06a2d9d4e7ca04ecc1c Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sun, 5 Feb 2017 21:08:16 +0000 Subject: [PATCH 046/113] wip fake consul provider --- .../LeastConnectionLoadBalancer.cs | 37 ++++--- .../LoadBalancers/LoadBalancerHouse.cs | 12 ++- .../Middleware/OcelotMiddlewareExtensions.cs | 16 +++ .../ServiceDiscoveryTests.cs | 98 +++++++++++++++---- .../TestConfiguration.cs | 2 +- test/Ocelot.AcceptanceTests/project.json | 3 +- .../LoadBalancer/LeastConnectionTests.cs | 45 +++++++++ 7 files changed, 174 insertions(+), 39 deletions(-) diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs index 38984567..bfb4817b 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs @@ -13,6 +13,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers private readonly Func>> _services; private readonly List _leases; private readonly string _serviceName; + private static readonly object _syncLock = new object(); public LeastConnectionLoadBalancer(Func>> services, string serviceName) { @@ -35,32 +36,38 @@ namespace Ocelot.LoadBalancer.LoadBalancers return new ErrorResponse(new List() { new ServicesAreEmptyError($"services were empty for {_serviceName}") }); } - //todo - maybe this should be moved somewhere else...? Maybe on a repeater on seperate thread? loop every second and update or something? - UpdateServices(services); + lock(_syncLock) + { + //todo - maybe this should be moved somewhere else...? Maybe on a repeater on seperate thread? loop every second and update or something? + UpdateServices(services); - var leaseWithLeastConnections = GetLeaseWithLeastConnections(); + var leaseWithLeastConnections = GetLeaseWithLeastConnections(); - _leases.Remove(leaseWithLeastConnections); + _leases.Remove(leaseWithLeastConnections); - leaseWithLeastConnections = AddConnection(leaseWithLeastConnections); + leaseWithLeastConnections = AddConnection(leaseWithLeastConnections); - _leases.Add(leaseWithLeastConnections); - - return new OkResponse(new HostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort)); + _leases.Add(leaseWithLeastConnections); + + return new OkResponse(new HostAndPort(leaseWithLeastConnections.HostAndPort.DownstreamHost, leaseWithLeastConnections.HostAndPort.DownstreamPort)); + } } public Response Release(HostAndPort hostAndPort) { - var matchingLease = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == hostAndPort.DownstreamHost - && l.HostAndPort.DownstreamPort == hostAndPort.DownstreamPort); - - if (matchingLease != null) + lock(_syncLock) { - var replacementLease = new Lease(hostAndPort, matchingLease.Connections - 1); + var matchingLease = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == hostAndPort.DownstreamHost + && l.HostAndPort.DownstreamPort == hostAndPort.DownstreamPort); - _leases.Remove(matchingLease); + if (matchingLease != null) + { + var replacementLease = new Lease(hostAndPort, matchingLease.Connections - 1); - _leases.Add(replacementLease); + _leases.Remove(matchingLease); + + _leases.Add(replacementLease); + } } return new OkResponse(); diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs index 12c040c0..63ac3243 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -32,8 +32,16 @@ namespace Ocelot.LoadBalancer.LoadBalancers public Response Add(string key, ILoadBalancer loadBalancer) { - _loadBalancers[key] = loadBalancer; - return new OkResponse(); + try + { + _loadBalancers.Add(key, loadBalancer); + return new OkResponse(); + } + catch (System.Exception exception) + { + Console.WriteLine(exception.StackTrace); + throw; + } } } } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index b0cd3f7e..352aa501 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -18,6 +18,7 @@ namespace Ocelot.Middleware using System.Threading.Tasks; using Authorisation.Middleware; using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.Provider; using Ocelot.LoadBalancer.Middleware; public static class OcelotMiddlewareExtensions @@ -29,6 +30,7 @@ namespace Ocelot.Middleware /// public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder) { + CreateConfiguration(builder); builder.UseOcelot(new OcelotMiddlewareConfiguration()); return builder; } @@ -41,6 +43,8 @@ namespace Ocelot.Middleware /// public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) { + CreateConfiguration(builder); + // This is registered to catch any global exceptions that are not handled builder.UseExceptionHandlerMiddleware(); @@ -118,6 +122,18 @@ namespace Ocelot.Middleware return builder; } + private static void CreateConfiguration(IApplicationBuilder builder) + { + var configProvider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider)); + + var config = configProvider.Get(); + + if(config == null) + { + throw new Exception("Unable to start Ocelot: configuration was null"); + } + } + private static void UseIfNotNull(this IApplicationBuilder builder, Func, Task> middleware) { if (middleware != null) diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index 06bee686..d8e5149d 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Net; +using Consul; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -14,13 +15,19 @@ namespace Ocelot.AcceptanceTests { public class ServiceDiscoveryTests : IDisposable { - private IWebHost _builder; + private IWebHost _builderOne; + private IWebHost _builderTwo; private IWebHost _fakeConsulBuilder; private readonly Steps _steps; + private List _serviceEntries; + private int _counterOne; + private int _counterTwo; + private static readonly object _syncLock = new object(); public ServiceDiscoveryTests() { _steps = new Steps(); + _serviceEntries = new List(); } [Fact] @@ -32,6 +39,28 @@ namespace Ocelot.AcceptanceTests var fakeConsulServiceDiscoveryUrl = "http://localhost:8500"; var downstreamServiceOneCounter = 0; var downstreamServiceTwoCounter = 0; + var serviceEntryOne = new ServiceEntry() + { + Service = new AgentService() + { + Service = serviceName, + Address = "localhost", + Port = 50879, + ID = Guid.NewGuid().ToString(), + Tags = new string[0] + }, + }; + var serviceEntryTwo = new ServiceEntry() + { + Service = new AgentService() + { + Service = serviceName, + Address = "localhost", + Port = 50880, + ID = Guid.NewGuid().ToString(), + Tags = new string[0] + }, + }; var configuration = new FileConfiguration { @@ -52,38 +81,42 @@ namespace Ocelot.AcceptanceTests ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() { Provider = "Consul", - Host = "localhost" + Host = "localhost", + Port = 8500 } } }; - this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, 200, downstreamServiceOneCounter)) - .And(x => x.GivenThereIsAServiceRunningOn(downstreamServiceTwoUrl, 200, downstreamServiceTwoCounter)) + this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200)) + .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200)) .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceName, downstreamServiceOneUrl, downstreamServiceTwoUrl)) + .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50)) - .Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50, downstreamServiceOneCounter, downstreamServiceTwoCounter)) - .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(downstreamServiceOneCounter,downstreamServiceTwoCounter)) + .Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50)) + .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes()) .BDDfy(); } - private void ThenBothServicesCalledRealisticAmountOfTimes(int counterOne, int counterTwo) + private void ThenBothServicesCalledRealisticAmountOfTimes() { - counterOne.ShouldBeGreaterThan(10); - counterTwo.ShouldBeGreaterThan(10); + _counterOne.ShouldBeGreaterThan(25); + _counterTwo.ShouldBeGreaterThan(25); } - private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected, int counterOne, int counterTwo) + private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected) { - var total = counterOne + counterTwo; + var total = _counterOne + _counterTwo; total.ShouldBe(expected); } - private void GivenTheServicesAreRegisteredWithConsul(string serviceName, params string[] urls) + private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries) { - //register these services with fake consul + foreach(var serviceEntry in serviceEntries) + { + _serviceEntries.Add(serviceEntry); + } } private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) @@ -98,7 +131,10 @@ namespace Ocelot.AcceptanceTests { app.Run(async context => { - //do consul shit + if(context.Request.Path.Value == "/v1/health/service/product") + { + await context.Response.WriteJsonAsync(_serviceEntries); + } }); }) .Build(); @@ -106,9 +142,9 @@ namespace Ocelot.AcceptanceTests _fakeConsulBuilder.Start(); } - private void GivenThereIsAServiceRunningOn(string url, int statusCode, int counter) + private void GivenProductServiceOneIsRunning(string url, int statusCode) { - _builder = new WebHostBuilder() + _builderOne = new WebHostBuilder() .UseUrls(url) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) @@ -118,18 +154,40 @@ namespace Ocelot.AcceptanceTests { app.Run(async context => { - counter++; + _counterOne++; context.Response.StatusCode = statusCode; }); }) .Build(); - _builder.Start(); + _builderOne.Start(); + } + + private void GivenProductServiceTwoIsRunning(string url, int statusCode) + { + _builderTwo = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + _counterTwo++; + context.Response.StatusCode = statusCode; + }); + }) + .Build(); + + _builderTwo.Start(); } public void Dispose() { - _builder?.Dispose(); + _builderOne?.Dispose(); + _builderTwo?.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index ce802efb..6784391c 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -28,7 +28,7 @@ { var runTime = $"{oSDescription}-{osArchitecture}".ToLower(); - var configPath = $"./bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; + var configPath = $"./test/Ocelot.AcceptanceTests/bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; return configPath; } diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index 2e5f9ee8..f1aa378b 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -32,7 +32,8 @@ "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", "Microsoft.NETCore.App": "1.1.0", "Shouldly": "2.8.2", - "TestStack.BDDfy": "4.3.2" + "TestStack.BDDfy": "4.3.2", + "Consul": "0.7.2.1" }, "runtimes": { "win10-x64": {}, diff --git a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs index 47b3a7d0..3896b68e 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Threading.Tasks; using Ocelot.LoadBalancer.LoadBalancers; @@ -15,6 +16,50 @@ namespace Ocelot.UnitTests.LoadBalancer private Response _result; private LeastConnectionLoadBalancer _leastConnection; private List _services; + private Random _random; + + public LeastConnectionTests() + { + _random = new Random(); + } + + [Fact] + public void should_be_able_to_lease_and_release_concurrently() + { + var serviceName = "products"; + + var availableServices = new List + { + new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), + new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), + }; + + _services = availableServices; + _leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName); + + var tasks = new Task[100]; + try + { + for(var i = 0; i < tasks.Length; i++) + { + tasks[i] = LeaseDelayAndRelease(); + } + + Task.WaitAll(tasks); + } + catch (System.Exception exception) + { + Console.WriteLine(exception.StackTrace); + throw; + } + } + + private async Task LeaseDelayAndRelease() + { + var hostAndPort = await _leastConnection.Lease(); + await Task.Delay(_random.Next(1, 100)); + var response = _leastConnection.Release(hostAndPort.Data); + } [Fact] public void should_get_next_url() From 932bcb73d463ec1b474fa7361e4d43a248dde788 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 5 Feb 2017 21:21:02 +0000 Subject: [PATCH 047/113] wip: removed some debug statements and all tests passing on my PC...feel there is something wrong with the service discovery test around task execution not completing --- .../LoadBalancers/LoadBalancerHouse.cs | 12 +++++------- .../ServiceDiscoveryTests.cs | 7 +++---- .../Ocelot.AcceptanceTests/TestConfiguration.cs | 2 +- .../LoadBalancer/LeastConnectionTests.cs | 17 +++++------------ 4 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs index 63ac3243..cc6ea73b 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -32,16 +32,14 @@ namespace Ocelot.LoadBalancer.LoadBalancers public Response Add(string key, ILoadBalancer loadBalancer) { - try + if (!_loadBalancers.ContainsKey(key)) { _loadBalancers.Add(key, loadBalancer); - return new OkResponse(); - } - catch (System.Exception exception) - { - Console.WriteLine(exception.StackTrace); - throw; } + + _loadBalancers.Remove(key); + _loadBalancers.Add(key, loadBalancer); + return new OkResponse(); } } } diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index d8e5149d..2d9d55af 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -19,10 +19,9 @@ namespace Ocelot.AcceptanceTests private IWebHost _builderTwo; private IWebHost _fakeConsulBuilder; private readonly Steps _steps; - private List _serviceEntries; + private readonly List _serviceEntries; private int _counterOne; private int _counterTwo; - private static readonly object _syncLock = new object(); public ServiceDiscoveryTests() { @@ -101,8 +100,8 @@ namespace Ocelot.AcceptanceTests private void ThenBothServicesCalledRealisticAmountOfTimes() { - _counterOne.ShouldBeGreaterThan(25); - _counterTwo.ShouldBeGreaterThan(25); + _counterOne.ShouldBe(25); + _counterTwo.ShouldBe(25); } private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected) diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index 6784391c..ce802efb 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -28,7 +28,7 @@ { var runTime = $"{oSDescription}-{osArchitecture}".ToLower(); - var configPath = $"./test/Ocelot.AcceptanceTests/bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; + var configPath = $"./bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; return configPath; } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs index 3896b68e..f5ea4738 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs @@ -38,20 +38,13 @@ namespace Ocelot.UnitTests.LoadBalancer _leastConnection = new LeastConnectionLoadBalancer(() => Task.FromResult(_services), serviceName); var tasks = new Task[100]; - try + + for(var i = 0; i < tasks.Length; i++) { - for(var i = 0; i < tasks.Length; i++) - { - tasks[i] = LeaseDelayAndRelease(); - } + tasks[i] = LeaseDelayAndRelease(); + } - Task.WaitAll(tasks); - } - catch (System.Exception exception) - { - Console.WriteLine(exception.StackTrace); - throw; - } + Task.WaitAll(tasks); } private async Task LeaseDelayAndRelease() From d91242ac2c2b80d4ab39d4666657d59633dea224 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 5 Feb 2017 21:35:50 +0000 Subject: [PATCH 048/113] wip: modifications to service discovery acceptance test to see if it will work on mac --- README.md | 2 +- .../ServiceDiscoveryTests.cs | 21 +++++++++++++++---- test/Ocelot.AcceptanceTests/Steps.cs | 11 +++++++++- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 249a99a3..9d93546d 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ touch either via gitter or create an issue. ## How to install Ocelot is designed to work with ASP.NET core only and is currently -built to netcoreapp1.4 [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. +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 all we have is the pre version. Once we have something working in diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index 2d9d55af..1bc6723a 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -22,6 +22,7 @@ namespace Ocelot.AcceptanceTests private readonly List _serviceEntries; private int _counterOne; private int _counterTwo; + private static readonly object _syncLock = new object(); public ServiceDiscoveryTests() { @@ -152,9 +153,15 @@ namespace Ocelot.AcceptanceTests .Configure(app => { app.Run(async context => - { - _counterOne++; + { + var response = string.Empty; + lock (_syncLock) + { + _counterOne++; + response = _counterOne.ToString(); + } context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(response); }); }) .Build(); @@ -173,9 +180,15 @@ namespace Ocelot.AcceptanceTests .Configure(app => { app.Run(async context => - { - _counterTwo++; + { + var response = string.Empty; + lock (_syncLock) + { + _counterTwo++; + response = _counterTwo.ToString(); + } context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(response); }); }) .Build(); diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 666c3256..5d015994 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -160,12 +160,21 @@ namespace Ocelot.AcceptanceTests for (int i = 0; i < times; i++) { - tasks[i] = _ocelotClient.GetAsync(url); + var urlCopy = url; + tasks[i] = GetForServiceDiscoveryTest(urlCopy); } Task.WaitAll(tasks); } + private async Task GetForServiceDiscoveryTest(string url) + { + var response = await _ocelotClient.GetAsync(url); + var content = await response.Content.ReadAsStringAsync(); + var count = int.Parse(content); + count.ShouldBeGreaterThan(0); + } + public void WhenIGetUrlOnTheApiGateway(string url, string requestId) { _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(RequestIdKey, requestId); From c3e60ac08a26f7374f517489c9bdb161616b5596 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sun, 5 Feb 2017 22:00:23 +0000 Subject: [PATCH 049/113] wip: added some sleep time into service discovery test as I think Im overloading the test server, sometimes it just returns 404 when Ocelot makes a request to it --- .../ServiceDiscoveryTests.cs | 42 ++++++++++++------- test/Ocelot.AcceptanceTests/Steps.cs | 10 ++++- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index 1bc6723a..ef7e0dd7 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -37,8 +37,6 @@ namespace Ocelot.AcceptanceTests var downstreamServiceOneUrl = "http://localhost:50879"; var downstreamServiceTwoUrl = "http://localhost:50880"; var fakeConsulServiceDiscoveryUrl = "http://localhost:8500"; - var downstreamServiceOneCounter = 0; - var downstreamServiceTwoCounter = 0; var serviceEntryOne = new ServiceEntry() { Service = new AgentService() @@ -154,14 +152,21 @@ namespace Ocelot.AcceptanceTests { app.Run(async context => { - var response = string.Empty; - lock (_syncLock) + try { - _counterOne++; - response = _counterOne.ToString(); + var response = string.Empty; + lock (_syncLock) + { + _counterOne++; + response = _counterOne.ToString(); + } + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(response); + } + catch (System.Exception exception) + { + await context.Response.WriteAsync(exception.StackTrace); } - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(response); }); }) .Build(); @@ -181,14 +186,23 @@ namespace Ocelot.AcceptanceTests { app.Run(async context => { - var response = string.Empty; - lock (_syncLock) + try { - _counterTwo++; - response = _counterTwo.ToString(); + var response = string.Empty; + lock (_syncLock) + { + _counterTwo++; + response = _counterTwo.ToString(); + } + + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(response); } - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(response); + catch (System.Exception exception) + { + await context.Response.WriteAsync(exception.StackTrace); + } + }); }) .Build(); diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 5d015994..9b5faa04 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Threading; using System.Threading.Tasks; using CacheManager.Core; using Microsoft.AspNetCore.Hosting; @@ -30,6 +31,12 @@ namespace Ocelot.AcceptanceTests private BearerToken _token; public HttpClient OcelotClient => _ocelotClient; public string RequestIdKey = "OcRequestId"; + private Random _random; + + public Steps() + { + _random = new Random(); + } public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) { @@ -162,6 +169,7 @@ namespace Ocelot.AcceptanceTests { var urlCopy = url; tasks[i] = GetForServiceDiscoveryTest(urlCopy); + Thread.Sleep(_random.Next(40,60)); } Task.WaitAll(tasks); @@ -171,7 +179,7 @@ namespace Ocelot.AcceptanceTests { var response = await _ocelotClient.GetAsync(url); var content = await response.Content.ReadAsStringAsync(); - var count = int.Parse(content); + int count = int.Parse(content); count.ShouldBeGreaterThan(0); } From a4495b8fa991f2e9e2e4335ef1cb1735030908e3 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 6 Feb 2017 20:22:09 +0000 Subject: [PATCH 050/113] tests for error handling on load balancing middleware --- .../Middleware/LoadBalancingMiddleware.cs | 18 +++-- .../LoadBalancerMiddlewareTests.cs | 73 +++++++++++++++++++ 2 files changed, 83 insertions(+), 8 deletions(-) diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index d2e9ee8a..7d09ea3a 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -31,16 +31,18 @@ namespace Ocelot.LoadBalancer.Middleware { _logger.LogDebug("started calling load balancing middleware"); - var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey); - if(loadBalancer.IsError) + var getLoadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey); + if(getLoadBalancer.IsError) { - //set errors and return + SetPipelineError(getLoadBalancer.Errors); + return; } - var hostAndPort = await loadBalancer.Data.Lease(); + var hostAndPort = await getLoadBalancer.Data.Lease(); if(hostAndPort.IsError) - { - //set errors and return + { + SetPipelineError(hostAndPort.Errors); + return; } SetHostAndPortForThisRequest(hostAndPort.Data); @@ -51,11 +53,11 @@ namespace Ocelot.LoadBalancer.Middleware { await _next.Invoke(context); - loadBalancer.Data.Release(hostAndPort.Data); + getLoadBalancer.Data.Release(hostAndPort.Data); } catch (Exception) { - loadBalancer.Data.Release(hostAndPort.Data); + getLoadBalancer.Data.Release(hostAndPort.Data); _logger.LogDebug("error calling next middleware, exception will be thrown to global handler"); throw; } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index ab6a59a8..5d750f83 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; +using Ocelot.Errors; using Ocelot.Infrastructure.RequestData; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.LoadBalancer.Middleware; @@ -31,6 +32,8 @@ namespace Ocelot.UnitTests.LoadBalancer private OkResponse _request; private OkResponse _downstreamUrl; private OkResponse _downstreamRoute; + private ErrorResponse _getLoadBalancerHouseError; + private ErrorResponse _getHostAndPortError; public LoadBalancerMiddlewareTests() { @@ -77,6 +80,45 @@ namespace Ocelot.UnitTests.LoadBalancer .BDDfy(); } + [Fact] + public void should_set_pipeline_error_if_cannot_get_load_balancer() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .Build()); + + this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) + .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheLoadBalancerHouseReturnsAnError()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline()) + .BDDfy(); + } + + [Fact] + public void should_set_pipeline_error_if_cannot_get_least() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .Build()); + + this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) + .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheLoadBalancerHouseReturns()) + .And(x => x.GivenTheLoadBalancerReturnsAnError()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline()) + .BDDfy(); + } + + private void GivenTheLoadBalancerReturnsAnError() + { + _getHostAndPortError = new ErrorResponse(new List() { new ServicesAreNullError($"services were null for bah") }); + _loadBalancer + .Setup(x => x.Lease()) + .ReturnsAsync(_getHostAndPortError); + } + private void GivenTheLoadBalancerReturns() { _hostAndPort = new HostAndPort("127.0.0.1", 80); @@ -100,12 +142,43 @@ namespace Ocelot.UnitTests.LoadBalancer .Returns(new OkResponse(_loadBalancer.Object)); } + + private void GivenTheLoadBalancerHouseReturnsAnError() + { + _getLoadBalancerHouseError = new ErrorResponse(new List() + { + new UnableToFindLoadBalancerError($"unabe to find load balancer for bah") + }); + + _loadBalancerHouse + .Setup(x => x.Get(It.IsAny())) + .Returns(_getLoadBalancerHouseError); + } + private void ThenTheScopedDataRepositoryIsCalledCorrectly() { _scopedRepository .Verify(x => x.Add("HostAndPort", _hostAndPort), Times.Once()); } + private void ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline() + { + _scopedRepository + .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once); + + _scopedRepository + .Verify(x => x.Add("OcelotMiddlewareErrors", _getLoadBalancerHouseError.Errors), Times.Once); + } + + private void ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline() + { + _scopedRepository + .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once); + + _scopedRepository + .Verify(x => x.Add("OcelotMiddlewareErrors", _getHostAndPortError.Errors), Times.Once); + } + private void WhenICallTheMiddleware() { _result = _client.GetAsync(_url).Result; From 0a66051b92ee4cd3f032fcbe59d0fbb56d33f3db Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 6 Feb 2017 21:47:08 +0000 Subject: [PATCH 051/113] removed some code we dont need as not expecting any errors so should just throw an exception to the global handler --- .../Middleware/ExceptionHandlerMiddleware.cs | 4 ++-- .../LoadBalancer/LoadBalancers/ILoadBalancer.cs | 2 +- .../LeastConnectionLoadBalancer.cs | 4 +--- .../LoadBalancers/NoLoadBalancer.cs | 5 ++--- .../LoadBalancers/RoundRobinLoadBalancer.cs | 5 ++--- .../Middleware/LoadBalancingMiddleware.cs | 13 +++++++------ src/Ocelot/Responder/HttpContextResponder.cs | 6 ++---- src/Ocelot/Responder/IHttpResponder.cs | 5 ++--- .../Responder/Middleware/ResponderMiddleware.cs | 17 +++++------------ .../ConfigurationServiceProvider.cs | 2 +- .../LoadBalancer/LeastConnectionTests.cs | 2 +- .../LoadBalancer/LoadBalancerHouseTests.cs | 4 ++-- .../LoadBalancer/LoadBalancerMiddlewareTests.cs | 9 +++++++++ .../Responder/ResponderMiddlewareTests.cs | 8 -------- 14 files changed, 37 insertions(+), 49 deletions(-) diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs index fc3ab200..6e6b511e 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs @@ -41,13 +41,13 @@ namespace Ocelot.Errors.Middleware var message = CreateMessage(context, e); _logger.LogError(message, e); - await SetInternalServerErrorOnResponse(context); + SetInternalServerErrorOnResponse(context); } _logger.LogDebug("ocelot pipeline finished"); } - private async Task SetInternalServerErrorOnResponse(HttpContext context) + private void SetInternalServerErrorOnResponse(HttpContext context) { context.Response.OnStarting(x => { diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs index aa2a8f02..73d25d48 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs @@ -8,6 +8,6 @@ namespace Ocelot.LoadBalancer.LoadBalancers public interface ILoadBalancer { Task> Lease(); - Response Release(HostAndPort hostAndPort); + void Release(HostAndPort hostAndPort); } } \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs index bfb4817b..cd56ef91 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnectionLoadBalancer.cs @@ -53,7 +53,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers } } - public Response Release(HostAndPort hostAndPort) + public void Release(HostAndPort hostAndPort) { lock(_syncLock) { @@ -69,8 +69,6 @@ namespace Ocelot.LoadBalancer.LoadBalancers _leases.Add(replacementLease); } } - - return new OkResponse(); } private Lease AddConnection(Lease lease) diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs index f654dca8..bf66950b 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs @@ -17,13 +17,12 @@ namespace Ocelot.LoadBalancer.LoadBalancers public async Task> Lease() { - var service = _services.FirstOrDefault(); + var service = await Task.FromResult(_services.FirstOrDefault()); return new OkResponse(service.HostAndPort); } - public Response Release(HostAndPort hostAndPort) + public void Release(HostAndPort hostAndPort) { - return new OkResponse(); } } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs index 0bb1f829..37efe22f 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobinLoadBalancer.cs @@ -22,14 +22,13 @@ namespace Ocelot.LoadBalancer.LoadBalancers _last = 0; } - var next = _services[_last]; + var next = await Task.FromResult(_services[_last]); _last++; return new OkResponse(next.HostAndPort); } - public Response Release(HostAndPort hostAndPort) + public void Release(HostAndPort hostAndPort) { - return new OkResponse(); } } } diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index 7d09ea3a..ce37f828 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -31,14 +31,14 @@ namespace Ocelot.LoadBalancer.Middleware { _logger.LogDebug("started calling load balancing middleware"); - var getLoadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey); - if(getLoadBalancer.IsError) + var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey); + if(loadBalancer.IsError) { - SetPipelineError(getLoadBalancer.Errors); + SetPipelineError(loadBalancer.Errors); return; } - var hostAndPort = await getLoadBalancer.Data.Lease(); + var hostAndPort = await loadBalancer.Data.Lease(); if(hostAndPort.IsError) { SetPipelineError(hostAndPort.Errors); @@ -53,11 +53,12 @@ namespace Ocelot.LoadBalancer.Middleware { await _next.Invoke(context); - getLoadBalancer.Data.Release(hostAndPort.Data); + loadBalancer.Data.Release(hostAndPort.Data); } catch (Exception) { - getLoadBalancer.Data.Release(hostAndPort.Data); + loadBalancer.Data.Release(hostAndPort.Data); + _logger.LogDebug("error calling next middleware, exception will be thrown to global handler"); throw; } diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index 8b61e13b..40b60c30 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -24,7 +24,7 @@ namespace Ocelot.Responder _removeOutputHeaders = removeOutputHeaders; } - public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response) + public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response) { _removeOutputHeaders.Remove(response.Headers); @@ -56,7 +56,6 @@ namespace Ocelot.Responder { await stream.CopyToAsync(context.Response.Body); } - return new OkResponse(); } private static void AddHeaderIfDoesntExist(HttpContext context, KeyValuePair> httpResponseHeader) @@ -67,14 +66,13 @@ namespace Ocelot.Responder } } - public async Task SetErrorResponseOnContext(HttpContext context, int statusCode) + public void SetErrorResponseOnContext(HttpContext context, int statusCode) { context.Response.OnStarting(x => { context.Response.StatusCode = statusCode; return Task.CompletedTask; }, context); - return new OkResponse(); } } } \ No newline at end of file diff --git a/src/Ocelot/Responder/IHttpResponder.cs b/src/Ocelot/Responder/IHttpResponder.cs index 5292f4df..f885c673 100644 --- a/src/Ocelot/Responder/IHttpResponder.cs +++ b/src/Ocelot/Responder/IHttpResponder.cs @@ -1,13 +1,12 @@ using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Ocelot.Responses; namespace Ocelot.Responder { public interface IHttpResponder { - Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response); - Task SetErrorResponseOnContext(HttpContext context, int statusCode); + Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response); + void SetErrorResponseOnContext(HttpContext context, int statusCode); } } diff --git a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs index 06da92dc..6bce4ac6 100644 --- a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs +++ b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs @@ -46,34 +46,27 @@ namespace Ocelot.Responder.Middleware _logger.LogDebug("received errors setting error response"); - await SetErrorResponse(context, errors); + SetErrorResponse(context, errors); } else { _logger.LogDebug("no pipeline error, setting response"); - var setResponse = await _responder.SetResponseOnHttpContext(context, HttpResponseMessage); - - if (setResponse.IsError) - { - _logger.LogDebug("error setting response, returning error to client"); - - await SetErrorResponse(context, setResponse.Errors); - } + await _responder.SetResponseOnHttpContext(context, HttpResponseMessage); } } - private async Task SetErrorResponse(HttpContext context, List errors) + private void SetErrorResponse(HttpContext context, List errors) { var statusCode = _codeMapper.Map(errors); if (!statusCode.IsError) { - await _responder.SetErrorResponseOnContext(context, statusCode.Data); + _responder.SetErrorResponseOnContext(context, statusCode.Data); } else { - await _responder.SetErrorResponseOnContext(context, 500); + _responder.SetErrorResponseOnContext(context, 500); } } } diff --git a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs index f6280d7b..98e7b0f6 100644 --- a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs +++ b/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs @@ -15,7 +15,7 @@ namespace Ocelot.ServiceDiscovery public async Task> Get() { - return _services; + return await Task.FromResult(_services); } } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs index f5ea4738..07002ce3 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs @@ -51,7 +51,7 @@ namespace Ocelot.UnitTests.LoadBalancer { var hostAndPort = await _leastConnection.Lease(); await Task.Delay(_random.Next(1, 100)); - var response = _leastConnection.Release(hostAndPort.Data); + _leastConnection.Release(hostAndPort.Data); } [Fact] diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs index 471e4b70..ac24b490 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -114,7 +114,7 @@ namespace Ocelot.UnitTests.LoadBalancer throw new NotImplementedException(); } - public Response Release(HostAndPort hostAndPort) + public void Release(HostAndPort hostAndPort) { throw new NotImplementedException(); } @@ -127,7 +127,7 @@ namespace Ocelot.UnitTests.LoadBalancer throw new NotImplementedException(); } - public Response Release(HostAndPort hostAndPort) + public void Release(HostAndPort hostAndPort) { throw new NotImplementedException(); } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index 5d750f83..5a9eec87 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -170,6 +170,15 @@ namespace Ocelot.UnitTests.LoadBalancer .Verify(x => x.Add("OcelotMiddlewareErrors", _getLoadBalancerHouseError.Errors), Times.Once); } + private void ThenAnErrorSayingReleaseFailedIsSetOnThePipeline() + { + _scopedRepository + .Verify(x => x.Add("OcelotMiddlewareError", true), Times.Once); + + _scopedRepository + .Verify(x => x.Add("OcelotMiddlewareErrors", It.IsAny>()), Times.Once); + } + private void ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline() { _scopedRepository diff --git a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs index b643028e..09a5c22c 100644 --- a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs @@ -62,19 +62,11 @@ namespace Ocelot.UnitTests.Responder { this.Given(x => x.GivenTheHttpResponseMessageIs(new HttpResponseMessage())) .And(x => x.GivenThereAreNoPipelineErrors()) - .And(x => x.GivenTheResponderReturns()) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenThereAreNoErrors()) .BDDfy(); } - private void GivenTheResponderReturns() - { - _responder - .Setup(x => x.SetResponseOnHttpContext(It.IsAny(), It.IsAny())) - .ReturnsAsync(new OkResponse()); - } - private void GivenThereAreNoPipelineErrors() { _scopedRepository From 201b470cb216c7556aca9924d3e6e1314ecfb04c Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 6 Feb 2017 22:03:48 +0000 Subject: [PATCH 052/113] updated readme for service discovery --- README.md | 39 +++++++++++++++++++++++++++++++++++ configuration-explanation.txt | 14 ++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d93546d..9d46282f 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,44 @@ This means that when Ocelot tries to match the incoming upstream url with an ups 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! + +## 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. + + "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. + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "UpstreamTemplate": "/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 @@ -389,3 +427,4 @@ that isnt available is annoying. Let alone it be null. You can see what we are working on [here](https://github.com/TomPallister/Ocelot/projects/1) + diff --git a/configuration-explanation.txt b/configuration-explanation.txt index ad020469..b6db84a2 100644 --- a/configuration-explanation.txt +++ b/configuration-explanation.txt @@ -80,12 +80,24 @@ # the caching a lot. "FileCacheOptions": { "TtlSeconds": 15 }, # The value of this is used when matching the upstream template to an upstream url. - "ReRouteIsCaseSensitive": false + "ReRouteIsCaseSensitive": false, + # Tells Ocelot the name of the service it is looking when making requests to service discovery + # for hosts and ports + "ServiceName": "product" + # Tells Ocelot which load balancer to use when making downstream requests. + "LoadBalancer": "RoundRobin" }, # This section is meant to be for global configuration settings "GlobalConfiguration": { # If this is set it will override any route specific request id keys, behaves the same # otherwise "RequestIdKey": "OcRequestId", + # If set Ocelot will try and use service discovery to locate downstream hosts and ports + "ServiceDiscoveryProvider": + { + "Provider":"Consul", + "Host":"localhost", + "Port":8500 + } } } \ No newline at end of file From b4ef277c3e270cc07987706e43d8a1fa10b7ed89 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Tue, 7 Feb 2017 07:50:23 +0000 Subject: [PATCH 053/113] added builder for service provider config --- .../Configuration/Builder/ReRouteBuilder.cs | 40 ++---------- .../Creator/FileOcelotConfigurationCreator.cs | 13 ++-- .../ServiceProviderConfiguraionBuilder.cs | 62 +++++++++++++++++++ .../LoadBalancers/LoadBalancerFactory.cs | 13 +--- .../Ocelot.AcceptanceTests/configuration.json | 2 +- .../FileConfigurationCreatorTests.cs | 14 +++-- .../LoadBalancer/LoadBalancerFactoryTests.cs | 20 +++--- .../ServiceProviderFactoryTests.cs | 12 +++- 8 files changed, 110 insertions(+), 66 deletions(-) create mode 100644 src/Ocelot/Configuration/ServiceProviderConfiguraionBuilder.cs diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index caa09d3f..c8bde074 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -26,16 +26,12 @@ namespace Ocelot.Configuration.Builder private string _requestIdHeaderKey; private bool _isCached; private CacheOptions _fileCacheOptions; - private bool _useServiceDiscovery; private string _serviceName; - private string _serviceDiscoveryProvider; - private string _serviceDiscoveryAddress; private string _downstreamScheme; private string _downstreamHost; - private int _dsPort; + private int _downstreamPort; private string _loadBalancer; - private string _serviceProviderHost; - private int _serviceProviderPort; + private ServiceProviderConfiguraion _serviceProviderConfiguraion; public ReRouteBuilder() { @@ -60,30 +56,12 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithServiceDiscoveryAddress(string serviceDiscoveryAddress) - { - _serviceDiscoveryAddress = serviceDiscoveryAddress; - return this; - } - - public ReRouteBuilder WithServiceDiscoveryProvider(string serviceDiscoveryProvider) - { - _serviceDiscoveryProvider = serviceDiscoveryProvider; - return this; - } - public ReRouteBuilder WithServiceName(string serviceName) { _serviceName = serviceName; return this; } - public ReRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery) - { - _useServiceDiscovery = useServiceDiscovery; - return this; - } - public ReRouteBuilder WithDownstreamPathTemplate(string input) { _downstreamPathTemplate = input; @@ -198,7 +176,7 @@ namespace Ocelot.Configuration.Builder public ReRouteBuilder WithDownstreamPort(int port) { - _dsPort = port; + _downstreamPort = port; return this; } @@ -208,15 +186,9 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithServiceProviderHost(string serviceProviderHost) + public ReRouteBuilder WithServiceProviderConfiguraion(ServiceProviderConfiguraion serviceProviderConfiguraion) { - _serviceProviderHost = serviceProviderHost; - return this; - } - - public ReRouteBuilder WithServiceProviderPort(int serviceProviderPort) - { - _serviceProviderPort = serviceProviderPort; + _serviceProviderConfiguraion = serviceProviderConfiguraion; return this; } @@ -226,7 +198,7 @@ namespace Ocelot.Configuration.Builder _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _downstreamScheme, _loadBalancer, - _downstreamHost, _dsPort, _loadBalancerKey, new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _dsPort, _useServiceDiscovery, _serviceDiscoveryProvider, _serviceProviderHost, _serviceProviderPort)); + _downstreamHost, _downstreamPort, _loadBalancerKey, _serviceProviderConfiguraion); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 703239d0..5a265569 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -110,10 +110,15 @@ namespace Ocelot.Configuration.Creator var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; - var serviceProviderConfiguration = new ServiceProviderConfiguraion(fileReRoute.ServiceName, - fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, useServiceDiscovery, - globalConfiguration?.ServiceDiscoveryProvider?.Provider, globalConfiguration?.ServiceDiscoveryProvider?.Host, - serviceProviderPort); + var serviceProviderConfiguration = new ServiceProviderConfiguraionBuilder() + .WithServiceName(fileReRoute.ServiceName) + .WithDownstreamHost(fileReRoute.DownstreamHost) + .WithDownstreamPort(fileReRoute.DownstreamPort) + .WithUseServiceDiscovery(useServiceDiscovery) + .WithServiceDiscoveryProvider(globalConfiguration?.ServiceDiscoveryProvider?.Provider) + .WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host) + .WithServiceDiscoveryProviderPort(serviceProviderPort) + .Build(); if (isAuthenticated) { diff --git a/src/Ocelot/Configuration/ServiceProviderConfiguraionBuilder.cs b/src/Ocelot/Configuration/ServiceProviderConfiguraionBuilder.cs new file mode 100644 index 00000000..e8609683 --- /dev/null +++ b/src/Ocelot/Configuration/ServiceProviderConfiguraionBuilder.cs @@ -0,0 +1,62 @@ +namespace Ocelot.Configuration +{ + public class ServiceProviderConfiguraionBuilder + { + private string _serviceName; + private string _downstreamHost; + private int _downstreamPort; + private bool _userServiceDiscovery; + private string _serviceDiscoveryProvider; + private string _serviceDiscoveryProviderHost; + private int _serviceDiscoveryProviderPort; + + public ServiceProviderConfiguraionBuilder WithServiceName(string serviceName) + { + _serviceName = serviceName; + return this; + } + + public ServiceProviderConfiguraionBuilder WithDownstreamHost(string downstreamHost) + { + _downstreamHost = downstreamHost; + return this; + } + + public ServiceProviderConfiguraionBuilder WithDownstreamPort(int downstreamPort) + { + _downstreamPort = downstreamPort; + return this; + } + + public ServiceProviderConfiguraionBuilder WithUseServiceDiscovery(bool userServiceDiscovery) + { + _userServiceDiscovery = userServiceDiscovery; + return this; + } + + public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProvider(string serviceDiscoveryProvider) + { + _serviceDiscoveryProvider = serviceDiscoveryProvider; + return this; + } + + public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProviderHost(string serviceDiscoveryProviderHost) + { + _serviceDiscoveryProviderHost = serviceDiscoveryProviderHost; + return this; + } + + public ServiceProviderConfiguraionBuilder WithServiceDiscoveryProviderPort(int serviceDiscoveryProviderPort) + { + _serviceDiscoveryProviderPort = serviceDiscoveryProviderPort; + return this; + } + + + public ServiceProviderConfiguraion Build() + { + return new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _downstreamPort, _userServiceDiscovery, + _serviceDiscoveryProvider, _serviceDiscoveryProviderHost,_serviceDiscoveryProviderPort); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs index 08e45d2b..72ccdba1 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerFactory.cs @@ -13,17 +13,8 @@ namespace Ocelot.LoadBalancer.LoadBalancers } public async Task Get(ReRoute reRoute) - { - var serviceConfig = new ServiceProviderConfiguraion( - reRoute.ServiceProviderConfiguraion.ServiceName, - reRoute.ServiceProviderConfiguraion.DownstreamHost, - reRoute.ServiceProviderConfiguraion.DownstreamPort, - reRoute.ServiceProviderConfiguraion.UseServiceDiscovery, - reRoute.ServiceProviderConfiguraion.ServiceDiscoveryProvider, - reRoute.ServiceProviderConfiguraion.ServiceProviderHost, - reRoute.ServiceProviderConfiguraion.ServiceProviderPort); - - var serviceProvider = _serviceProviderFactory.Get(serviceConfig); + { + var serviceProvider = _serviceProviderFactory.Get(reRoute.ServiceProviderConfiguraion); switch (reRoute.LoadBalancer) { diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index 78ab541b..d7db55c7 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index e1da7de0..d9017da2 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -165,9 +165,11 @@ namespace Ocelot.UnitTests.Configuration .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .WithServiceName("ProductService") - .WithUseServiceDiscovery(true) - .WithServiceDiscoveryProvider("consul") - .WithServiceDiscoveryAddress("127.0.01") + .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder() + .WithUseServiceDiscovery(true) + .WithServiceDiscoveryProvider("consul") + .WithServiceDiscoveryProviderHost("127.0.0.1") + .Build()) .Build() })) .BDDfy(); @@ -198,9 +200,9 @@ namespace Ocelot.UnitTests.Configuration .WithUpstreamTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") - .WithUseServiceDiscovery(false) - .WithServiceDiscoveryProvider(null) - .WithServiceDiscoveryAddress(null) + .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder() + .WithUseServiceDiscovery(false) + .Build()) .Build() })) .BDDfy(); diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index d030eb99..9d46807e 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -24,18 +24,12 @@ namespace Ocelot.UnitTests.LoadBalancer _factory = new LoadBalancerFactory(_serviceProviderFactory.Object); } - private void GivenTheServiceProviderFactoryReturns() - { - _serviceProviderFactory - .Setup(x => x.Get(It.IsAny())) - .Returns(_serviceProvider.Object); - } - [Fact] public void should_return_no_load_balancer() { var reRoute = new ReRouteBuilder() - .Build(); + .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) + .Build(); this.Given(x => x.GivenAReRoute(reRoute)) .And(x => x.GivenTheServiceProviderFactoryReturns()) @@ -49,6 +43,7 @@ namespace Ocelot.UnitTests.LoadBalancer { var reRoute = new ReRouteBuilder() .WithLoadBalancer("RoundRobin") + .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) .Build(); this.Given(x => x.GivenAReRoute(reRoute)) @@ -63,6 +58,7 @@ namespace Ocelot.UnitTests.LoadBalancer { var reRoute = new ReRouteBuilder() .WithLoadBalancer("LeastConnection") + .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) .Build(); this.Given(x => x.GivenAReRoute(reRoute)) @@ -77,6 +73,7 @@ namespace Ocelot.UnitTests.LoadBalancer { var reRoute = new ReRouteBuilder() .WithLoadBalancer("RoundRobin") + .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) .Build(); this.Given(x => x.GivenAReRoute(reRoute)) @@ -86,6 +83,13 @@ namespace Ocelot.UnitTests.LoadBalancer .BDDfy(); } + private void GivenTheServiceProviderFactoryReturns() + { + _serviceProviderFactory + .Setup(x => x.Get(It.IsAny())) + .Returns(_serviceProvider.Object); + } + private void ThenTheServiceProviderIsCalledCorrectly() { _serviceProviderFactory diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index 7dae5e47..7ba4608f 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -20,7 +20,11 @@ namespace Ocelot.UnitTests.ServiceDiscovery [Fact] public void should_return_no_service_provider() { - var serviceConfig = new ServiceProviderConfiguraion("product", "127.0.0.1", 80, false, "Does not matter", string.Empty, 0); + var serviceConfig = new ServiceProviderConfiguraionBuilder() + .WithDownstreamHost("127.0.0.1") + .WithDownstreamPort(80) + .WithUseServiceDiscovery(false) + .Build(); this.Given(x => x.GivenTheReRoute(serviceConfig)) .When(x => x.WhenIGetTheServiceProvider()) @@ -31,7 +35,11 @@ namespace Ocelot.UnitTests.ServiceDiscovery [Fact] public void should_return_consul_service_provider() { - var serviceConfig = new ServiceProviderConfiguraion("product", string.Empty, 0, true, "Consul", string.Empty, 0); + var serviceConfig = new ServiceProviderConfiguraionBuilder() + .WithServiceName("product") + .WithUseServiceDiscovery(true) + .WithServiceDiscoveryProvider("Consul") + .Build(); this.Given(x => x.GivenTheReRoute(serviceConfig)) .When(x => x.WhenIGetTheServiceProvider()) From 1671fadea3846cbb0ca7cb90619e97c3d556155a Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Tue, 7 Feb 2017 07:53:41 +0000 Subject: [PATCH 054/113] made upstream template use template path object --- src/Ocelot/Configuration/Builder/ReRouteBuilder.cs | 2 +- .../Creator/FileOcelotConfigurationCreator.cs | 8 ++++---- src/Ocelot/Configuration/ReRoute.cs | 8 ++++---- .../DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs | 2 +- src/Ocelot/Values/DownstreamPathTemplate.cs | 4 ++-- .../DownstreamRouteFinder/DownstreamRouteFinderTests.cs | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index c8bde074..78c96fb5 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -194,7 +194,7 @@ namespace Ocelot.Configuration.Builder public ReRoute Build() { - return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, + return new ReRoute(new PathTemplate(_downstreamPathTemplate), new PathTemplate(_upstreamTemplate), _upstreamHttpMethod, _upstreamTemplatePattern, _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _downstreamScheme, _loadBalancer, diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 5a265569..e02fccd5 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -131,8 +131,8 @@ namespace Ocelot.Configuration.Creator var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest); var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest); - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), - fileReRoute.UpstreamTemplate, + reRoute = new ReRoute(new PathTemplate(fileReRoute.DownstreamPathTemplate), + new PathTemplate(fileReRoute.UpstreamTemplate), fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, authOptionsForRoute, claimsToHeaders, claimsToClaims, fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, @@ -143,8 +143,8 @@ namespace Ocelot.Configuration.Creator } else { - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), - fileReRoute.UpstreamTemplate, + reRoute = new ReRoute(new PathTemplate(fileReRoute.DownstreamPathTemplate), + new PathTemplate(fileReRoute.UpstreamTemplate), fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, null, new List(), new List(), fileReRoute.RouteClaimsRequirement, isAuthorised, new List(), diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 278d0746..dbc0d05e 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -6,8 +6,8 @@ namespace Ocelot.Configuration { public class ReRoute { - public ReRoute(DownstreamPathTemplate downstreamPathTemplate, - string upstreamTemplate, string upstreamHttpMethod, + public ReRoute(PathTemplate downstreamPathTemplate, + PathTemplate upstreamTemplate, string upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, @@ -44,8 +44,8 @@ namespace Ocelot.Configuration } public string LoadBalancerKey {get;private set;} - public DownstreamPathTemplate DownstreamPathTemplate { get; private set; } - public string UpstreamTemplate { get; private set; } + public PathTemplate DownstreamPathTemplate { get; private set; } + public PathTemplate UpstreamTemplate { get; private set; } public string UpstreamTemplatePattern { get; private set; } public string UpstreamHttpMethod { get; private set; } public bool IsAuthenticated { get; private set; } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index eacd6912..910f2bb2 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -34,7 +34,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder if (urlMatch.Data.Match) { - var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamTemplate); + var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamTemplate.Value); return new OkResponse(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute)); } diff --git a/src/Ocelot/Values/DownstreamPathTemplate.cs b/src/Ocelot/Values/DownstreamPathTemplate.cs index a4c720eb..b0b53418 100644 --- a/src/Ocelot/Values/DownstreamPathTemplate.cs +++ b/src/Ocelot/Values/DownstreamPathTemplate.cs @@ -1,8 +1,8 @@ namespace Ocelot.Values { - public class DownstreamPathTemplate + public class PathTemplate { - public DownstreamPathTemplate(string value) + public PathTemplate(string value) { Value = value; } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index dc9978b3..8abb9a61 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -143,7 +143,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void ThenTheUrlMatcherIsCalledCorrectly() { _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamTemplate), Times.Once); + .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamTemplate.Value), Times.Once); } private void GivenTheUrlMatcherReturns(Response match) From 6ad27ec17da0568ea2c9796792fc02a12ccdfa74 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Tue, 7 Feb 2017 07:54:03 +0000 Subject: [PATCH 055/113] made upstream template use template path object --- .../DownstreamUrlTemplateVariableReplacer.cs | 2 +- .../IDownstreamUrlPathTemplateVariableReplacer.cs | 2 +- .../DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs index 9e925631..3c42b4f4 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs @@ -8,7 +8,7 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer { public class DownstreamTemplatePathPlaceholderReplacer : IDownstreamPathPlaceholderReplacer { - public Response Replace(DownstreamPathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues) + public Response Replace(PathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues) { var downstreamPath = new StringBuilder(); diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs index 72d5d4b6..647af63a 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs @@ -7,6 +7,6 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer { public interface IDownstreamPathPlaceholderReplacer { - Response Replace(DownstreamPathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues); + Response Replace(PathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues); } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index a01677b2..c953e264 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -101,7 +101,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator { _downstreamPath = new OkResponse(new DownstreamPath(downstreamUrl)); _downstreamUrlTemplateVariableReplacer - .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) + .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) .Returns(_downstreamPath); } From b10c95219e06e1a2cc12db33e0742c84263a1cf2 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Tue, 7 Feb 2017 19:47:11 +0800 Subject: [PATCH 056/113] merge circuitbreakerpattern code --- global.json | 2 +- .../Configuration/Builder/ReRouteBuilder.cs | 3 +- .../Creator/FileOcelotConfigurationCreator.cs | 29 ++++----- src/Ocelot/Configuration/ReRoute.cs | 2 +- src/Ocelot/project.json | 62 +++++++++---------- .../Ocelot.AcceptanceTests/configuration.json | 2 +- 6 files changed, 49 insertions(+), 51 deletions(-) diff --git a/global.json b/global.json index ff8d898e..616b2c4e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@  { "projects": [ "src", "test" ], "sdk": { - "version": "1.0.0-preview2-003133" + "version": "1.0.0-preview2-003131" } } diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 7fed4047..9420c3f8 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -248,7 +248,8 @@ namespace Ocelot.Configuration.Builder _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _downstreamScheme, _loadBalancer, - _downstreamHost, _dsPort, _loadBalancerKey, new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _dsPort, _useServiceDiscovery, _serviceDiscoveryProvider, _serviceProviderHost, _serviceProviderPort), + _downstreamHost, _dsPort, _loadBalancerKey, new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _dsPort, _useServiceDiscovery, + _serviceDiscoveryProvider, _serviceProviderHost, _serviceProviderPort), _exceptionsAllowedBeforeBreaking,_durationOfBreak, _timeoutValue); } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index f5e5090f..3ff3966c 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -103,9 +103,6 @@ namespace Ocelot.Configuration.Creator var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName) && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); - - Func downstreamHostAndPortFunc = () => new HostAndPort(reRoute.DownstreamHost.Trim('/'), reRoute.DownstreamPort); - //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.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}"; @@ -130,16 +127,16 @@ namespace Ocelot.Configuration.Creator var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest); var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest); - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), - fileReRoute.UpstreamTemplate, - fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, - authOptionsForRoute, claimsToHeaders, claimsToClaims, - fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, - requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds) - , fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, - serviceProviderConfiguration, serviceProviderConfiguration ,reRoute.ExceptionsAllowedBeforeBreaking, - reRoute.DurationOfBreak, reRoute.TimeoutValue); + reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), + fileReRoute.UpstreamTemplate, + fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, + authOptionsForRoute, claimsToHeaders, claimsToClaims, + fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, + requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds) + , fileReRoute.DownstreamScheme, + fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, + serviceProviderConfiguration, fileReRoute.ExceptionsAllowedBeforeBreaking, + fileReRoute.DurationOfBreak, fileReRoute.TimeoutValue); } else { @@ -151,10 +148,10 @@ namespace Ocelot.Configuration.Creator requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), fileReRoute.DownstreamScheme, fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, - serviceProviderConfiguration, serviceProviderConfiguration ,reRoute.ExceptionsAllowedBeforeBreaking, - reRoute.DurationOfBreak, reRoute.TimeoutValue); + serviceProviderConfiguration, fileReRoute.ExceptionsAllowedBeforeBreaking, + fileReRoute.DurationOfBreak, fileReRoute.TimeoutValue); } - + var loadBalancer = await _loadBalanceFactory.Get(reRoute); _loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer); return reRoute; diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 26a2d06e..7065361b 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -16,7 +16,7 @@ namespace Ocelot.Configuration List claimsToQueries, string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string downstreamScheme, string loadBalancer, string downstreamHost, - int downstreamPort, string loadBalancerKey, ServiceProviderConfiguraion serviceProviderConfiguraion, + int downstreamPort, string loadBalancerKey, ServiceProviderConfiguraion serviceProviderConfiguraion, int exceptionsAllowedBeforeBreaking =3, int durationofBreak =8, int timeoutValue = 5000) { LoadBalancerKey = loadBalancerKey; diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 6ba9441d..4e098803 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -1,38 +1,38 @@ { "version": "0.0.0-dev", - "dependencies": { - "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", - "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", - "Microsoft.Extensions.Configuration.Json": "1.1.0", - "Microsoft.Extensions.Logging": "1.1.0", - "Microsoft.Extensions.Logging.Console": "1.1.0", - "Microsoft.Extensions.Logging.Debug": "1.1.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", - "Microsoft.AspNetCore.Http": "1.1.0", - "System.Text.RegularExpressions": "4.3.0", - "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", - "Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0", - "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0", - "Microsoft.AspNetCore.Authentication.Cookies": "1.1.0", - "Microsoft.AspNetCore.Authentication.Google": "1.1.0", - "Microsoft.AspNetCore.Authentication.Facebook": "1.1.0", - "Microsoft.AspNetCore.Authentication.Twitter": "1.1.0", - "Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.1.0", - "Microsoft.AspNetCore.Authentication": "1.1.0", - "IdentityServer4.AccessTokenValidation": "1.0.2", - "Microsoft.AspNetCore.Mvc": "1.1.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", - "Microsoft.NETCore.App": "1.1.0", - "CacheManager.Core": "0.9.2", - "CacheManager.Microsoft.Extensions.Configuration": "0.9.2", - "CacheManager.Microsoft.Extensions.Logging": "0.9.2", - "Consul": "0.7.2.1", - "Polly": "5.0.3" - }, + "dependencies": { + "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", + "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", + "Microsoft.Extensions.Configuration.Json": "1.1.0", + "Microsoft.Extensions.Logging": "1.1.0", + "Microsoft.Extensions.Logging.Console": "1.1.0", + "Microsoft.Extensions.Logging.Debug": "1.1.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", + "Microsoft.AspNetCore.Http": "1.1.0", + "System.Text.RegularExpressions": "4.3.0", + "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", + "Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0", + "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0", + "Microsoft.AspNetCore.Authentication.Cookies": "1.1.0", + "Microsoft.AspNetCore.Authentication.Google": "1.1.0", + "Microsoft.AspNetCore.Authentication.Facebook": "1.1.0", + "Microsoft.AspNetCore.Authentication.Twitter": "1.1.0", + "Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.1.0", + "Microsoft.AspNetCore.Authentication": "1.1.0", + "IdentityServer4.AccessTokenValidation": "1.0.2", + "Microsoft.AspNetCore.Mvc": "1.1.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", + "Microsoft.NETCore.App": "1.1.0", + "CacheManager.Core": "0.9.2", + "CacheManager.Microsoft.Extensions.Configuration": "0.9.2", + "CacheManager.Microsoft.Extensions.Logging": "0.9.2", + "Consul": "0.7.2.1", + "Polly": "5.0.3" + }, "runtimes": { "win10-x64": {}, - "osx.10.11-x64":{}, + "osx.10.11-x64": {}, "win7-x64": {} }, "frameworks": { diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index 1244fbb8..67d0c796 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"LoadBalancer":null, ,"ExceptionsAllowedBeforeBreaking":3,"DurationOfBreak":5,"TimeoutValue":5000}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"ExceptionsAllowedBeforeBreaking":3,"DurationOfBreak":5,"TimeoutValue":5000,"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} \ No newline at end of file From 616ca4f7305a2759076cd6390e7930bd8a3111b2 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Tue, 7 Feb 2017 19:50:09 +0800 Subject: [PATCH 057/113] update sdk version --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 616b2c4e..ff8d898e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@  { "projects": [ "src", "test" ], "sdk": { - "version": "1.0.0-preview2-003131" + "version": "1.0.0-preview2-003133" } } From bbb808eb5129188f74359bfc55d1354234620cc5 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Tue, 7 Feb 2017 13:46:22 +0000 Subject: [PATCH 058/113] fixed failing tests --- .../Configuration/FileConfigurationCreatorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index d9017da2..5ecf4810 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -593,7 +593,7 @@ namespace Ocelot.UnitTests.Configuration result.DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate.Value); result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); - result.UpstreamTemplate.ShouldBe(expected.UpstreamTemplate); + result.UpstreamTemplate.Value.ShouldBe(expected.UpstreamTemplate.Value); result.UpstreamTemplatePattern.ShouldBe(expected.UpstreamTemplatePattern); } } From 33ce162693bc934fd973cf01024aa63f894fc757 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Tue, 7 Feb 2017 18:30:21 +0000 Subject: [PATCH 059/113] changed upstream http method to use httpmethod class in .net --- src/Ocelot/Configuration/ReRoute.cs | 5 +- .../Finder/DownstreamRouteFinder.cs | 2 +- .../AuthenticationMiddlewareTests.cs | 4 +- .../AuthorisationMiddlewareTests.cs | 6 +- .../Cache/OutputCacheMiddlewareTests.cs | 7 ++- .../Claims/ClaimsBuilderMiddlewareTests.cs | 1 + .../InMemoryConfigurationRepositoryTests.cs | 5 +- .../DownstreamRouteFinderMiddlewareTests.cs | 8 ++- .../DownstreamRouteFinderTests.cs | 2 + .../DownstreamUrlCreatorMiddlewareTests.cs | 8 ++- ...eamUrlPathTemplateVariableReplacerTests.cs | 57 ++++++++++++++++--- ...ttpRequestHeadersBuilderMiddlewareTests.cs | 1 + .../LoadBalancer/LoadBalancerFactoryTests.cs | 4 ++ .../LoadBalancerMiddlewareTests.cs | 3 + .../QueryStringBuilderMiddlewareTests.cs | 1 + .../HttpRequestBuilderMiddlewareTests.cs | 4 +- .../RequestId/RequestIdMiddlewareTests.cs | 8 ++- 17 files changed, 105 insertions(+), 21 deletions(-) diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index dbc0d05e..419096af 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net.Http; using Ocelot.Values; namespace Ocelot.Configuration @@ -25,7 +26,7 @@ namespace Ocelot.Configuration DownstreamPort = downstreamPort; DownstreamPathTemplate = downstreamPathTemplate; UpstreamTemplate = upstreamTemplate; - UpstreamHttpMethod = upstreamHttpMethod; + UpstreamHttpMethod = new HttpMethod(upstreamHttpMethod); UpstreamTemplatePattern = upstreamTemplatePattern; IsAuthenticated = isAuthenticated; AuthenticationOptions = authenticationOptions; @@ -47,7 +48,7 @@ namespace Ocelot.Configuration public PathTemplate DownstreamPathTemplate { get; private set; } public PathTemplate UpstreamTemplate { get; private set; } public string UpstreamTemplatePattern { get; private set; } - public string UpstreamHttpMethod { get; private set; } + public HttpMethod UpstreamHttpMethod { get; private set; } public bool IsAuthenticated { get; private set; } public bool IsAuthorised { get; private set; } public AuthenticationOptions AuthenticationOptions { get; private set; } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 910f2bb2..0e591416 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -26,7 +26,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder { var configuration = await _configProvider.Get(); - var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase)); + var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod.Method, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase)); foreach (var reRoute in applicableReRoutes) { diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs index ff467aa7..f60eb83c 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs @@ -71,7 +71,9 @@ namespace Ocelot.UnitTests.Authentication [Fact] public void should_call_next_middleware_if_route_is_not_authenticated() { - this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().Build()))) + this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder() + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheUserIsAuthenticated()) .BDDfy(); diff --git a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs index 8d681e03..35435fc2 100644 --- a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs @@ -63,7 +63,11 @@ namespace Ocelot.UnitTests.Authorization [Fact] public void should_call_authorisation_service() { - this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().WithIsAuthorised(true).Build()))) + this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithIsAuthorised(true) + .WithUpstreamHttpMethod("Get") .WithUpstreamHttpMethod("Get") + .Build()))) .And(x => x.GivenTheAuthServiceReturns(new OkResponse(true))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheAuthServiceIsCalledCorrectly()) diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index 9190dbe9..66532a18 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -87,7 +87,12 @@ namespace Ocelot.UnitTests.Cache private void GivenTheDownstreamRouteIs() { - var reRoute = new ReRouteBuilder().WithIsCached(true).WithCacheOptions(new CacheOptions(100)).Build(); + var reRoute = new ReRouteBuilder() + .WithIsCached(true) + .WithCacheOptions(new CacheOptions(100)) + .WithUpstreamHttpMethod("Get") + .Build(); + var downstreamRoute = new DownstreamRoute(new List(), reRoute); _scopedRepo diff --git a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs index 8822e6b2..90a16b17 100644 --- a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs @@ -72,6 +72,7 @@ namespace Ocelot.UnitTests.Claims { new ClaimToThing("sub", "UserType", "|", 0) }) + .WithUpstreamHttpMethod("Get") .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index ec46f914..9f437204 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -84,7 +84,10 @@ namespace Ocelot.UnitTests.Configuration public List ReRoutes => new List { - new ReRouteBuilder().WithDownstreamPathTemplate(_downstreamTemplatePath).Build() + new ReRouteBuilder() + .WithDownstreamPathTemplate(_downstreamTemplatePath) + .WithUpstreamHttpMethod("Get") + .Build() }; } } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index a80a3168..e7718f37 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -61,7 +61,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder [Fact] public void should_call_scoped_data_repository_correctly() { - this.Given(x => x.GivenTheDownStreamRouteFinderReturns(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("any old string").Build()))) + this.Given(x => x.GivenTheDownStreamRouteFinderReturns( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 8abb9a61..d65024f5 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -58,6 +58,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod("Get") .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -95,6 +96,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPathForAPost") + .WithUpstreamHttpMethod("Post") .Build() ))) .BDDfy(); diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index c953e264..438710f6 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -72,7 +72,13 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator { var hostAndPort = new HostAndPort("127.0.0.1", 80); - this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("any old string").Build()))) + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithUpstreamHttpMethod("Get") + .Build()))) .And(x => x.GivenTheHostAndPortIs(hostAndPort)) .And(x => x.TheUrlReplacerReturns("/api/products/1")) .And(x => x.TheUrlBuilderReturns("http://127.0.0.1:80/api/products/1")) diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs index a7a5a89b..00445ee6 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs @@ -25,7 +25,12 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_no_template_variables() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().Build()))) + this.Given(x => x.GivenThereIsAUrlMatch( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("")) .BDDfy(); @@ -34,7 +39,13 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_no_template_variables_with_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("/") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("/")) .BDDfy(); @@ -43,7 +54,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_no_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("api").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("api") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api")) .BDDfy(); @@ -52,7 +67,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_one_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("api/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("api/") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/")) .BDDfy(); @@ -61,7 +80,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_multiple_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder().WithDownstreamPathTemplate("api/product/products/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("api/product/products/") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("api/product/products/")) .BDDfy(); @@ -75,7 +98,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new UrlPathPlaceholderNameAndValue("{productId}", "1") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, + new ReRouteBuilder() + .WithDownstreamPathTemplate("productservice/products/{productId}/") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/")) .BDDfy(); @@ -89,7 +116,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new UrlPathPlaceholderNameAndValue("{productId}", "1") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/variants").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, + new ReRouteBuilder() + .WithDownstreamPathTemplate("productservice/products/{productId}/variants") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants")) .BDDfy(); @@ -104,7 +135,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new UrlPathPlaceholderNameAndValue("{variantId}", "12") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/products/{productId}/variants/{variantId}").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, + new ReRouteBuilder() + .WithDownstreamPathTemplate("productservice/products/{productId}/variants/{variantId}") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/products/1/variants/12")) .BDDfy(); @@ -120,7 +155,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer new UrlPathPlaceholderNameAndValue("{categoryId}", "34") }; - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, new ReRouteBuilder().WithDownstreamPathTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}").Build()))) + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, + new ReRouteBuilder() + .WithDownstreamPathTemplate("productservice/category/{categoryId}/products/{productId}/variants/{variantId}") + .WithUpstreamHttpMethod("Get") + .Build()))) .When(x => x.WhenIReplaceTheTemplateVariables()) .Then(x => x.ThenTheDownstreamUrlPathIsReturned("productservice/category/34/products/1/variants/12")) .BDDfy(); diff --git a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs index 3516d26b..032a76e9 100644 --- a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs @@ -72,6 +72,7 @@ namespace Ocelot.UnitTests.Headers { new ClaimToThing("UserId", "Subject", "", 0) }) + .WithUpstreamHttpMethod("Get") .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index 9d46807e..767b4272 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -29,6 +29,7 @@ namespace Ocelot.UnitTests.LoadBalancer { var reRoute = new ReRouteBuilder() .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) + .WithUpstreamHttpMethod("Get") .Build(); this.Given(x => x.GivenAReRoute(reRoute)) @@ -43,6 +44,7 @@ namespace Ocelot.UnitTests.LoadBalancer { var reRoute = new ReRouteBuilder() .WithLoadBalancer("RoundRobin") + .WithUpstreamHttpMethod("Get") .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) .Build(); @@ -58,6 +60,7 @@ namespace Ocelot.UnitTests.LoadBalancer { var reRoute = new ReRouteBuilder() .WithLoadBalancer("LeastConnection") + .WithUpstreamHttpMethod("Get") .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) .Build(); @@ -73,6 +76,7 @@ namespace Ocelot.UnitTests.LoadBalancer { var reRoute = new ReRouteBuilder() .WithLoadBalancer("RoundRobin") + .WithUpstreamHttpMethod("Get") .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder().Build()) .Build(); diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index 5a9eec87..1d544366 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -69,6 +69,7 @@ namespace Ocelot.UnitTests.LoadBalancer { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() + .WithUpstreamHttpMethod("Get") .Build()); this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) @@ -85,6 +86,7 @@ namespace Ocelot.UnitTests.LoadBalancer { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() + .WithUpstreamHttpMethod("Get") .Build()); this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) @@ -100,6 +102,7 @@ namespace Ocelot.UnitTests.LoadBalancer { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() + .WithUpstreamHttpMethod("Get") .Build()); this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) diff --git a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs index 39b32937..f381ff1b 100644 --- a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs @@ -70,6 +70,7 @@ namespace Ocelot.UnitTests.QueryStrings { new ClaimToThing("UserId", "Subject", "", 0) }) + .WithUpstreamHttpMethod("Get") .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) diff --git a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs index e2f81abe..b46877db 100644 --- a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs @@ -67,7 +67,9 @@ namespace Ocelot.UnitTests.Request var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() - .WithRequestIdKey("LSRequestId").Build()); + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod("Get") + .Build()); this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) diff --git a/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs b/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs index 543613a8..26450ab8 100644 --- a/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs @@ -72,7 +72,9 @@ namespace Ocelot.UnitTests.RequestId var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId").Build()); + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod("Get") + .Build()); var requestId = Guid.NewGuid().ToString(); @@ -89,7 +91,9 @@ namespace Ocelot.UnitTests.RequestId var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId").Build()); + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod("Get") + .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) .When(x => x.WhenICallTheMiddleware()) From c839c17c6237ce70edb45e3ee7e9e9f5f33eaf65 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Tue, 7 Feb 2017 20:04:08 +0000 Subject: [PATCH 060/113] test commit --- test/Ocelot.AcceptanceTests/configuration.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index 78ab541b..d7db55c7 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} \ No newline at end of file From 7fffc9827abe50618457b3d8b230ccc30c1e637a Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Tue, 7 Feb 2017 20:30:26 +0000 Subject: [PATCH 061/113] auth options builders --- README.md | 10 +-- configuration-explanation.txt | 2 +- .../Middleware/AuthorisationMiddleware.cs | 2 +- .../Builder/AuthenticationOptionsBuilder.cs | 56 ++++++++++++ .../Configuration/Builder/ReRouteBuilder.cs | 83 +++++++---------- .../ServiceProviderConfiguraionBuilder.cs | 2 +- .../Creator/FileOcelotConfigurationCreator.cs | 25 ++++-- src/Ocelot/Configuration/File/FileReRoute.cs | 2 +- src/Ocelot/Configuration/ReRoute.cs | 8 +- .../Validator/FileConfigurationValidator.cs | 8 +- .../Finder/DownstreamRouteFinder.cs | 2 +- .../AuthenticationTests.cs | 10 +-- .../AuthorisationTests.cs | 4 +- test/Ocelot.AcceptanceTests/CachingTests.cs | 4 +- .../CaseSensitiveRoutingTests.cs | 12 +-- .../ClaimsToHeadersForwardingTests.cs | 2 +- .../ClaimsToQueryStringForwardingTests.cs | 2 +- .../CustomMiddlewareTests.cs | 12 +-- test/Ocelot.AcceptanceTests/RequestIdTests.cs | 6 +- .../ReturnsErrorTests.cs | 2 +- test/Ocelot.AcceptanceTests/RoutingTests.cs | 18 ++-- .../ServiceDiscoveryTests.cs | 2 +- .../Ocelot.AcceptanceTests/configuration.json | 2 +- .../ConfigurationValidationTests.cs | 12 +-- .../FileConfigurationCreatorTests.cs | 90 ++++++++++--------- .../DownstreamRouteFinderTests.cs | 10 +-- .../ServiceProviderFactoryTests.cs | 1 + 27 files changed, 222 insertions(+), 167 deletions(-) create mode 100644 src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs rename src/Ocelot/Configuration/{ => Builder}/ServiceProviderConfiguraionBuilder.cs (98%) diff --git a/README.md b/README.md index 738ffbe1..3ef6d6b6 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ Ocelot's primary functionality is to take incomeing http requests and forward th 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 UpstreamTemplate. +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. @@ -140,16 +140,16 @@ the following. "DownstreamScheme": "https", "DownstreamPort": 80, "DownstreamHost" "localhost" - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Put" } The DownstreamPathTemplate,Scheme, Port and Host make the URL that this request will be forwarded to. -The UpstreamTemplate is the URL that Ocelot will use to identity which +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 UpstreamTemplate. If it is +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. @@ -190,7 +190,7 @@ and LeastConnection algorithm you can use. If no load balancer is specified Ocel { "DownstreamPathTemplate": "/api/posts/{postId}", "DownstreamScheme": "https", - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Put", "ServiceName": "product" "LoadBalancer": "LeastConnection" diff --git a/configuration-explanation.txt b/configuration-explanation.txt index b6db84a2..d7b4077f 100644 --- a/configuration-explanation.txt +++ b/configuration-explanation.txt @@ -15,7 +15,7 @@ # The path template we are listening on for this re route, Ocelot will add a trailing # slash to this property. Then when a request is made Ocelot makes sure a trailing # slash is added, so everything matches - "UpstreamTemplate": "/identityserverexample", + "UpstreamPathTemplate": "/identityserverexample", # The method we are listening for on this re route "UpstreamHttpMethod": "Get", # Only support identity server at the moment diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs index 547fc7f7..760d2a38 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs @@ -61,7 +61,7 @@ namespace Ocelot.Authorisation.Middleware SetPipelineError(new List { new UnauthorisedError( - $"{context.User.Identity.Name} unable to access {DownstreamRoute.ReRoute.UpstreamTemplate}") + $"{context.User.Identity.Name} unable to access {DownstreamRoute.ReRoute.UpstreamPathTemplate.Value}") }); } } diff --git a/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs new file mode 100644 index 00000000..61d5e847 --- /dev/null +++ b/src/Ocelot/Configuration/Builder/AuthenticationOptionsBuilder.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; + +namespace Ocelot.Configuration.Builder +{ + public class AuthenticationOptionsBuilder + { + + private string _provider; + private string _providerRootUrl; + private string _scopeName; + private string _scopeSecret; + private bool _requireHttps; + private List _additionalScopes; + + public AuthenticationOptionsBuilder WithProvider(string provider) + { + _provider = provider; + return this; + } + + public AuthenticationOptionsBuilder WithProviderRootUrl(string providerRootUrl) + { + _providerRootUrl = providerRootUrl; + return this; + } + + public AuthenticationOptionsBuilder WithScopeName(string scopeName) + { + _scopeName = scopeName; + return this; + } + + public AuthenticationOptionsBuilder WithScopeSecret(string scopeSecret) + { + _scopeSecret = scopeSecret; + return this; + } + + public AuthenticationOptionsBuilder WithRequireHttps(bool requireHttps) + { + _requireHttps = requireHttps; + return this; + } + + public AuthenticationOptionsBuilder WithAdditionalScopes(List additionalScopes) + { + _additionalScopes = additionalScopes; + return this; + } + + public AuthenticationOptions Build() + { + return new AuthenticationOptions(_provider, _providerRootUrl, _scopeName, _requireHttps, _additionalScopes, _scopeSecret); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 78c96fb5..d6615d11 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -1,23 +1,19 @@ using System; using System.Collections.Generic; +using System.Net.Http; using Ocelot.Values; namespace Ocelot.Configuration.Builder { public class ReRouteBuilder { + private AuthenticationOptions _authenticationOptions; private string _loadBalancerKey; private string _downstreamPathTemplate; private string _upstreamTemplate; private string _upstreamTemplatePattern; private string _upstreamHttpMethod; private bool _isAuthenticated; - private string _authenticationProvider; - private string _authenticationProviderUrl; - private string _scopeName; - private List _additionalScopes; - private bool _requireHttps; - private string _scopeSecret; private List _configHeaderExtractorProperties; private List _claimToClaims; private Dictionary _routeClaimRequirement; @@ -33,11 +29,6 @@ namespace Ocelot.Configuration.Builder private string _loadBalancer; private ServiceProviderConfiguraion _serviceProviderConfiguraion; - public ReRouteBuilder() - { - _additionalScopes = new List(); - } - public ReRouteBuilder WithLoadBalancer(string loadBalancer) { _loadBalancer = loadBalancer; @@ -68,7 +59,7 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithUpstreamTemplate(string input) + public ReRouteBuilder WithUpstreamPathTemplate(string input) { _upstreamTemplate = input; return this; @@ -96,42 +87,6 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithAuthenticationProvider(string input) - { - _authenticationProvider = input; - return this; - } - - public ReRouteBuilder WithAuthenticationProviderUrl(string input) - { - _authenticationProviderUrl = input; - return this; - } - - public ReRouteBuilder WithAuthenticationProviderScopeName(string input) - { - _scopeName = input; - return this; - } - - public ReRouteBuilder WithAuthenticationProviderAdditionalScopes(List input) - { - _additionalScopes = input; - return this; - } - - public ReRouteBuilder WithRequireHttps(bool input) - { - _requireHttps = input; - return this; - } - - public ReRouteBuilder WithScopeSecret(string input) - { - _scopeSecret = input; - return this; - } - public ReRouteBuilder WithRequestIdKey(string input) { _requestIdHeaderKey = input; @@ -192,13 +147,35 @@ namespace Ocelot.Configuration.Builder return this; } + public ReRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions) + { + _authenticationOptions = authenticationOptions; + return this; + } + public ReRoute Build() { - return new ReRoute(new PathTemplate(_downstreamPathTemplate), new PathTemplate(_upstreamTemplate), _upstreamHttpMethod, _upstreamTemplatePattern, - _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, - _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, - _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _downstreamScheme, _loadBalancer, - _downstreamHost, _downstreamPort, _loadBalancerKey, _serviceProviderConfiguraion); + return new ReRoute( + new PathTemplate(_downstreamPathTemplate), + new PathTemplate(_upstreamTemplate), + new HttpMethod(_upstreamHttpMethod), + _upstreamTemplatePattern, + _isAuthenticated, + _authenticationOptions, + _configHeaderExtractorProperties, + _claimToClaims, + _routeClaimRequirement, + _isAuthorised, + _claimToQueries, + _requestIdHeaderKey, + _isCached, + _fileCacheOptions, + _downstreamScheme, + _loadBalancer, + _downstreamHost, + _downstreamPort, + _loadBalancerKey, + _serviceProviderConfiguraion); } } } diff --git a/src/Ocelot/Configuration/ServiceProviderConfiguraionBuilder.cs b/src/Ocelot/Configuration/Builder/ServiceProviderConfiguraionBuilder.cs similarity index 98% rename from src/Ocelot/Configuration/ServiceProviderConfiguraionBuilder.cs rename to src/Ocelot/Configuration/Builder/ServiceProviderConfiguraionBuilder.cs index e8609683..129acae8 100644 --- a/src/Ocelot/Configuration/ServiceProviderConfiguraionBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ServiceProviderConfiguraionBuilder.cs @@ -1,4 +1,4 @@ -namespace Ocelot.Configuration +namespace Ocelot.Configuration.Builder { public class ServiceProviderConfiguraionBuilder { diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index e02fccd5..cf6a2b54 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Net.Http; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; @@ -88,7 +90,7 @@ namespace Ocelot.Configuration.Creator { var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); - var upstreamTemplate = BuildUpstreamTemplate(fileReRoute); + var upstreamTemplatePattern = BuildUpstreamTemplate(fileReRoute); var isAuthenticated = !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); @@ -104,7 +106,7 @@ namespace Ocelot.Configuration.Creator && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); //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.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}"; + var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}"; ReRoute reRoute; @@ -132,20 +134,29 @@ namespace Ocelot.Configuration.Creator var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest); reRoute = new ReRoute(new PathTemplate(fileReRoute.DownstreamPathTemplate), - new PathTemplate(fileReRoute.UpstreamTemplate), - fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, + new PathTemplate(fileReRoute.UpstreamPathTemplate), + new HttpMethod(fileReRoute.UpstreamHttpMethod), upstreamTemplatePattern, isAuthenticated, authOptionsForRoute, claimsToHeaders, claimsToClaims, fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds) , fileReRoute.DownstreamScheme, fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, serviceProviderConfiguration); + + //reRoute = new ReRouteBuilder() + // .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) + // .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) + // .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) + // .WithUpstreamTemplatePattern(upstreamTemplatePattern) + // .WithIsAuthenticated(isAuthenticated) + //.Build(); + } else { reRoute = new ReRoute(new PathTemplate(fileReRoute.DownstreamPathTemplate), - new PathTemplate(fileReRoute.UpstreamTemplate), - fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, + new PathTemplate(fileReRoute.UpstreamPathTemplate), + new HttpMethod(fileReRoute.UpstreamHttpMethod), upstreamTemplatePattern, isAuthenticated, null, new List(), new List(), fileReRoute.RouteClaimsRequirement, isAuthorised, new List(), requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), @@ -161,7 +172,7 @@ namespace Ocelot.Configuration.Creator private string BuildUpstreamTemplate(FileReRoute reRoute) { - var upstreamTemplate = reRoute.UpstreamTemplate; + var upstreamTemplate = reRoute.UpstreamPathTemplate; upstreamTemplate = upstreamTemplate.SetLastCharacterAs('/'); diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 0d0fc7bd..6d3fea4f 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -15,7 +15,7 @@ namespace Ocelot.Configuration.File } public string DownstreamPathTemplate { get; set; } - public string UpstreamTemplate { get; set; } + public string UpstreamPathTemplate { get; set; } public string UpstreamHttpMethod { get; set; } public FileAuthenticationOptions AuthenticationOptions { get; set; } public Dictionary AddHeadersToRequest { get; set; } diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 419096af..82d13e02 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -8,7 +8,7 @@ namespace Ocelot.Configuration public class ReRoute { public ReRoute(PathTemplate downstreamPathTemplate, - PathTemplate upstreamTemplate, string upstreamHttpMethod, + PathTemplate upstreamTemplate, HttpMethod upstreamHttpMethod, string upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, @@ -25,8 +25,8 @@ namespace Ocelot.Configuration DownstreamHost = downstreamHost; DownstreamPort = downstreamPort; DownstreamPathTemplate = downstreamPathTemplate; - UpstreamTemplate = upstreamTemplate; - UpstreamHttpMethod = new HttpMethod(upstreamHttpMethod); + UpstreamPathTemplate = upstreamTemplate; + UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; IsAuthenticated = isAuthenticated; AuthenticationOptions = authenticationOptions; @@ -46,7 +46,7 @@ namespace Ocelot.Configuration public string LoadBalancerKey {get;private set;} public PathTemplate DownstreamPathTemplate { get; private set; } - public PathTemplate UpstreamTemplate { get; private set; } + public PathTemplate UpstreamPathTemplate { get; private set; } public string UpstreamTemplatePattern { get; private set; } public HttpMethod UpstreamHttpMethod { get; private set; } public bool IsAuthenticated { get; private set; } diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs index 412613eb..98301ab4 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationValidator.cs @@ -54,7 +54,7 @@ namespace Ocelot.Configuration.Validator continue; } - var error = new UnsupportedAuthenticationProviderError($"{reRoute.AuthenticationOptions?.Provider} is unsupported authentication provider, upstream template is {reRoute.UpstreamTemplate}, upstream method is {reRoute.UpstreamHttpMethod}"); + var error = new UnsupportedAuthenticationProviderError($"{reRoute.AuthenticationOptions?.Provider} is unsupported authentication provider, upstream template is {reRoute.UpstreamPathTemplate}, upstream method is {reRoute.UpstreamHttpMethod}"); errors.Add(error); } @@ -94,18 +94,18 @@ namespace Ocelot.Configuration.Validator private ConfigurationValidationResult CheckForDupliateReRoutes(FileConfiguration configuration) { var hasDupes = configuration.ReRoutes - .GroupBy(x => new { x.UpstreamTemplate, x.UpstreamHttpMethod }).Any(x => x.Skip(1).Any()); + .GroupBy(x => new { x.UpstreamPathTemplate, x.UpstreamHttpMethod }).Any(x => x.Skip(1).Any()); if (!hasDupes) { return new ConfigurationValidationResult(false); } - var dupes = configuration.ReRoutes.GroupBy(x => new { x.UpstreamTemplate, x.UpstreamHttpMethod }) + var dupes = configuration.ReRoutes.GroupBy(x => new { x.UpstreamPathTemplate, x.UpstreamHttpMethod }) .Where(x => x.Skip(1).Any()); var errors = dupes - .Select(d => new DownstreamPathTemplateAlreadyUsedError(string.Format("Duplicate DownstreamPath: {0}", d.Key.UpstreamTemplate))) + .Select(d => new DownstreamPathTemplateAlreadyUsedError(string.Format("Duplicate DownstreamPath: {0}", d.Key.UpstreamPathTemplate))) .Cast() .ToList(); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 0e591416..e504a430 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -34,7 +34,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder if (urlMatch.Data.Match) { - var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamTemplate.Value); + var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamPathTemplate.Value); return new OkResponse(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute)); } diff --git a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs index 8b14f4f1..87419ae6 100644 --- a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs @@ -47,7 +47,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = _downstreamServicePort, DownstreamHost = _downstreamServiceHost, DownstreamScheme = _downstreamServiceScheme, - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions { @@ -85,7 +85,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = _downstreamServicePort, DownstreamHost = _downstreamServiceHost, DownstreamScheme = _downstreamServiceScheme, - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions { @@ -123,7 +123,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = _downstreamServicePort, DownstreamHost = _downstreamServiceHost, DownstreamScheme = _downstreamServiceScheme, - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions { @@ -163,7 +163,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = _downstreamServicePort, DownstreamHost = _downstreamServiceHost, DownstreamScheme = _downstreamServiceScheme, - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions { @@ -203,7 +203,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = _downstreamServicePort, DownstreamHost = _downstreamServiceHost, DownstreamScheme = _downstreamServiceScheme, - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Post", AuthenticationOptions = new FileAuthenticationOptions { diff --git a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs index 1f86c6ff..a89616b9 100644 --- a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs @@ -41,7 +41,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51876, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions { @@ -98,7 +98,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51876, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions { diff --git a/test/Ocelot.AcceptanceTests/CachingTests.cs b/test/Ocelot.AcceptanceTests/CachingTests.cs index e4e628af..a8364855 100644 --- a/test/Ocelot.AcceptanceTests/CachingTests.cs +++ b/test/Ocelot.AcceptanceTests/CachingTests.cs @@ -35,7 +35,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", FileCacheOptions = new FileCacheOptions { @@ -71,7 +71,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", FileCacheOptions = new FileCacheOptions { diff --git a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs index 81824602..7b1dfc75 100644 --- a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs @@ -34,7 +34,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/products/{productId}", + UpstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get" } } @@ -61,7 +61,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/products/{productId}", + UpstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false } @@ -89,7 +89,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/products/{productId}", + UpstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -117,7 +117,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/PRODUCTS/{productId}", + UpstreamPathTemplate = "/PRODUCTS/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -145,7 +145,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/products/{productId}", + UpstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } @@ -173,7 +173,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/PRODUCTS/{productId}", + UpstreamPathTemplate = "/PRODUCTS/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } diff --git a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs index 08bbd968..2d6bf163 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs @@ -55,7 +55,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 52876, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions { diff --git a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs index 04dc25db..0c10c3e4 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs @@ -55,7 +55,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 57876, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", AuthenticationOptions = new FileAuthenticationOptions { diff --git a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs index f6f6de20..085ce2a5 100644 --- a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -49,7 +49,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 41879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } } @@ -86,7 +86,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 41879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } } @@ -123,7 +123,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 41879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } } @@ -160,7 +160,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 41879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } } @@ -197,7 +197,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 41879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } } @@ -234,7 +234,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 41879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } } diff --git a/test/Ocelot.AcceptanceTests/RequestIdTests.cs b/test/Ocelot.AcceptanceTests/RequestIdTests.cs index 9334786b..8b9cd805 100644 --- a/test/Ocelot.AcceptanceTests/RequestIdTests.cs +++ b/test/Ocelot.AcceptanceTests/RequestIdTests.cs @@ -37,7 +37,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", RequestIdKey = _steps.RequestIdKey } @@ -65,7 +65,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", RequestIdKey = _steps.RequestIdKey } @@ -95,7 +95,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } }, diff --git a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs index 81307781..f9222b86 100644 --- a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs +++ b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs @@ -30,7 +30,7 @@ namespace Ocelot.AcceptanceTests new FileReRoute { DownstreamPathTemplate = "http://localhost:53876/", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get" } } diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 4f97114f..14f5af8a 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -44,7 +44,7 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } } @@ -72,7 +72,7 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } } @@ -100,7 +100,7 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", DownstreamHost = "localhost/", DownstreamPort = 51879, - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", } } @@ -128,7 +128,7 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, - UpstreamTemplate = "/products/", + UpstreamPathTemplate = "/products/", UpstreamHttpMethod = "Get", } } @@ -156,7 +156,7 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, - UpstreamTemplate = "/products", + UpstreamPathTemplate = "/products", UpstreamHttpMethod = "Get", } } @@ -184,7 +184,7 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, - UpstreamTemplate = "/products/{productId}", + UpstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } } @@ -211,7 +211,7 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, - UpstreamTemplate = "/products/{productId}", + UpstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get" } } @@ -239,7 +239,7 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", DownstreamPort = 51879, DownstreamScheme = "http", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Post" } } @@ -264,7 +264,7 @@ namespace Ocelot.AcceptanceTests new FileReRoute { DownstreamPathTemplate = "/newThing", - UpstreamTemplate = "/newThing", + UpstreamPathTemplate = "/newThing", DownstreamScheme = "http", DownstreamHost = "localhost", DownstreamPort = 51879, diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index ef7e0dd7..fcf75b14 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -68,7 +68,7 @@ namespace Ocelot.AcceptanceTests { DownstreamPathTemplate = "/", DownstreamScheme = "http", - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", ServiceName = serviceName, LoadBalancer = "LeastConnection", diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index d7db55c7..a08843bb 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","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":41879,"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs b/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs index 8a3e24f9..382471c6 100644 --- a/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConfigurationValidationTests.cs @@ -29,7 +29,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamPathTemplate = "http://www.bbc.co.uk/api/products/{productId}", - UpstreamTemplate = "http://asdf.com" + UpstreamPathTemplate = "http://asdf.com" } } })) @@ -48,7 +48,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamPathTemplate = "/api/products/", - UpstreamTemplate = "http://asdf.com" + UpstreamPathTemplate = "http://asdf.com" } } })) @@ -67,7 +67,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamPathTemplate = "/api/products/", - UpstreamTemplate = "http://asdf.com", + UpstreamPathTemplate = "http://asdf.com", AuthenticationOptions = new FileAuthenticationOptions { Provider = "IdentityServer" @@ -90,7 +90,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamPathTemplate = "/api/products/", - UpstreamTemplate = "http://asdf.com", + UpstreamPathTemplate = "http://asdf.com", AuthenticationOptions = new FileAuthenticationOptions { Provider = "BootyBootyBottyRockinEverywhere" @@ -114,12 +114,12 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamPathTemplate = "/api/products/", - UpstreamTemplate = "http://asdf.com" + UpstreamPathTemplate = "http://asdf.com" }, new FileReRoute { DownstreamPathTemplate = "http://www.bbc.co.uk", - UpstreamTemplate = "http://asdf.com" + UpstreamPathTemplate = "http://asdf.com" } } })) diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 5ecf4810..2d2a48ef 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -53,7 +53,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamHost = "127.0.0.1", - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } @@ -78,7 +78,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamHost = "127.0.0.1", - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } @@ -91,7 +91,7 @@ namespace Ocelot.UnitTests.Configuration new ReRouteBuilder() .WithDownstreamHost("127.0.0.1") .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .Build() @@ -109,7 +109,7 @@ namespace Ocelot.UnitTests.Configuration new FileReRoute { DownstreamScheme = "https", - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } @@ -122,7 +122,7 @@ namespace Ocelot.UnitTests.Configuration new ReRouteBuilder() .WithDownstreamScheme("https") .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .Build() @@ -139,7 +139,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false, @@ -161,7 +161,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .WithServiceName("ProductService") @@ -184,7 +184,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false, @@ -197,7 +197,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder() @@ -217,7 +217,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false @@ -230,7 +230,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .Build() @@ -247,7 +247,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get" } @@ -259,7 +259,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") .Build() @@ -276,7 +276,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -289,7 +289,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") .Build() @@ -306,7 +306,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -323,7 +323,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") .WithRequestIdKey("blahhhh") @@ -341,7 +341,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -354,7 +354,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") .Build() @@ -365,18 +365,23 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void should_create_with_headers_to_extract() { + var authenticationOptions = new AuthenticationOptionsBuilder() + .WithProvider("IdentityServer") + .WithProviderRootUrl("http://localhost:51888") + .WithRequireHttps(false) + .WithScopeSecret("secret") + .WithScopeName("api") + .WithAdditionalScopes(new List()) + .Build(); + var expected = new List { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") - .WithAuthenticationProvider("IdentityServer") - .WithAuthenticationProviderUrl("http://localhost:51888") - .WithRequireHttps(false) - .WithScopeSecret("secret") - .WithAuthenticationProviderScopeName("api") + .WithAuthenticationOptions(authenticationOptions) .WithClaimsToHeaders(new List { new ClaimToThing("CustomerId", "CustomerId", "", 0), @@ -390,7 +395,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, @@ -429,18 +434,23 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void should_create_with_authentication_properties() { + var authenticationOptions = new AuthenticationOptionsBuilder() + .WithProvider("IdentityServer") + .WithProviderRootUrl("http://localhost:51888") + .WithRequireHttps(false) + .WithScopeSecret("secret") + .WithScopeName("api") + .WithAdditionalScopes(new List()) + .Build(); + var expected = new List { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}") + .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/$") - .WithAuthenticationProvider("IdentityServer") - .WithAuthenticationProviderUrl("http://localhost:51888") - .WithRequireHttps(false) - .WithScopeSecret("secret") - .WithAuthenticationProviderScopeName("api") + .WithAuthenticationOptions(authenticationOptions) .Build() }; @@ -450,7 +460,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}", + UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, @@ -483,7 +493,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}/variants/{variantId}", + UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -496,7 +506,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}/variants/{variantId}") + .WithUpstreamPathTemplate("/api/products/{productId}/variants/{variantId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$") .Build() @@ -513,7 +523,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/api/products/{productId}/variants/{variantId}/", + UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}/", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -526,7 +536,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") - .WithUpstreamTemplate("/api/products/{productId}/variants/{variantId}/") + .WithUpstreamPathTemplate("/api/products/{productId}/variants/{variantId}/") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$") .Build() @@ -543,7 +553,7 @@ namespace Ocelot.UnitTests.Configuration { new FileReRoute { - UpstreamTemplate = "/", + UpstreamPathTemplate = "/", DownstreamPathTemplate = "/api/products/", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true @@ -556,7 +566,7 @@ namespace Ocelot.UnitTests.Configuration { new ReRouteBuilder() .WithDownstreamPathTemplate("/api/products/") - .WithUpstreamTemplate("/") + .WithUpstreamPathTemplate("/") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("/$") .Build() @@ -593,7 +603,7 @@ namespace Ocelot.UnitTests.Configuration result.DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate.Value); result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); - result.UpstreamTemplate.Value.ShouldBe(expected.UpstreamTemplate.Value); + result.UpstreamPathTemplate.Value.ShouldBe(expected.UpstreamPathTemplate.Value); result.UpstreamTemplatePattern.ShouldBe(expected.UpstreamTemplatePattern); } } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index d65024f5..81fc7022 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -45,7 +45,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder { new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamTemplate("someUpstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("someUpstreamPath") .Build() @@ -77,13 +77,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder { new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") - .WithUpstreamTemplate("someUpstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("") .Build(), new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPathForAPost") - .WithUpstreamTemplate("someUpstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod("Post") .WithUpstreamTemplatePattern("") .Build() @@ -110,7 +110,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder { new ReRouteBuilder() .WithDownstreamPathTemplate("somPath") - .WithUpstreamTemplate("somePath") + .WithUpstreamPathTemplate("somePath") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("somePath") .Build(), @@ -145,7 +145,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void ThenTheUrlMatcherIsCalledCorrectly() { _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamTemplate.Value), Times.Once); + .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); } private void GivenTheUrlMatcherReturns(Response match) diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index 7ba4608f..b3053afa 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -1,4 +1,5 @@ using Ocelot.Configuration; +using Ocelot.Configuration.Builder; using Ocelot.ServiceDiscovery; using Shouldly; using TestStack.BDDfy; From caae826d7721e26660aaf3fac09840fa43fbd433 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Wed, 8 Feb 2017 09:25:32 +0800 Subject: [PATCH 062/113] Refactor qos as options --- .../Configuration/Builder/ReRouteBuilder.cs | 22 +- .../Creator/FileOcelotConfigurationCreator.cs | 10 +- .../Configuration/File/FileQoSOptions.cs | 16 + src/Ocelot/Configuration/File/FileReRoute.cs | 5 +- .../QoS.cs => Configuration/QoSOptions.cs} | 8 +- src/Ocelot/Configuration/ReRoute.cs | 12 +- .../Request/Builder/HttpRequestCreator.cs | 5 +- src/Ocelot/Request/Builder/IRequestCreator.cs | 6 +- src/Ocelot/Request/Builder/RequestBuilder.cs | 14 +- .../HttpRequestBuilderMiddleware.cs | 2 +- src/Ocelot/Request/Request.cs | 9 +- src/Ocelot/Requester/HttpClientBuilder.cs | 9 +- .../Requester/HttpClientHttpRequester.cs | 7 +- .../AuthenticationTests.cs | 12 +- .../AuthorisationTests.cs | 3 - test/Ocelot.AcceptanceTests/CachingTests.cs | 6 - .../CaseSensitiveRoutingTests.cs | 18 - .../ClaimsToHeadersForwardingTests.cs | 3 - .../ClaimsToQueryStringForwardingTests.cs | 3 - .../CustomMiddlewareTests.cs | 20 +- test/Ocelot.AcceptanceTests/RequestIdTests.cs | 16 +- test/Ocelot.AcceptanceTests/RoutingTests.cs | 35 +- .../Ocelot.AcceptanceTests/configuration.json | 2 +- test/Ocelot.ManualTest/configuration.json | 551 ++++++++++-------- .../LoadBalancerMiddlewareTests.cs | 1 - .../HttpRequestBuilderMiddlewareTests.cs | 5 +- .../Request/RequestBuilderTests.cs | 27 +- .../Requester/HttpRequesterMiddlewareTests.cs | 2 +- .../ConfigurationServiceProviderTests.cs | 1 - 29 files changed, 408 insertions(+), 422 deletions(-) create mode 100644 src/Ocelot/Configuration/File/FileQoSOptions.cs rename src/Ocelot/{Values/QoS.cs => Configuration/QoSOptions.cs} (74%) diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 9420c3f8..568e7c68 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -33,12 +33,11 @@ namespace Ocelot.Configuration.Builder private string _downstreamScheme; private string _downstreamHost; private int _dsPort; - private int _exceptionsAllowedBeforeBreaking; - private int _durationOfBreak; - private int _timeoutValue; private string _loadBalancer; private string _serviceProviderHost; private int _serviceProviderPort; + private bool _useQos; + private QoSOptions _qosOptions; public ReRouteBuilder() @@ -206,23 +205,18 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithExceptionsAllowedBeforeBreaking(int exceptionsAllowedBeforeBreaking) + public ReRouteBuilder WithIsQos(bool input) { - _exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; + _useQos = input; return this; } - public ReRouteBuilder WithDurationOfBreak(int durationOfBreak) + public ReRouteBuilder WithQosOptions(QoSOptions input) { - _durationOfBreak = durationOfBreak; - return this; - } - - public ReRouteBuilder WithTimeoutValue(int timeoutValue) - { - _timeoutValue = timeoutValue; + _qosOptions = input; return this; } + public ReRouteBuilder WithLoadBalancerKey(string loadBalancerKey) { @@ -250,7 +244,7 @@ namespace Ocelot.Configuration.Builder _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _downstreamScheme, _loadBalancer, _downstreamHost, _dsPort, _loadBalancerKey, new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _dsPort, _useServiceDiscovery, _serviceDiscoveryProvider, _serviceProviderHost, _serviceProviderPort), - _exceptionsAllowedBeforeBreaking,_durationOfBreak, _timeoutValue); + _useQos,_qosOptions); } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 3ff3966c..f550edac 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -96,6 +96,8 @@ namespace Ocelot.Configuration.Creator var isCached = fileReRoute.FileCacheOptions.TtlSeconds > 0; + var isQos = fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions.TimeoutValue >0; + var requestIdKey = globalRequestIdConfiguration ? globalConfiguration.RequestIdKey : fileReRoute.RequestIdKey; @@ -135,8 +137,8 @@ namespace Ocelot.Configuration.Creator requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds) , fileReRoute.DownstreamScheme, fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, - serviceProviderConfiguration, fileReRoute.ExceptionsAllowedBeforeBreaking, - fileReRoute.DurationOfBreak, fileReRoute.TimeoutValue); + serviceProviderConfiguration, isQos, + new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue)); } else { @@ -148,8 +150,8 @@ namespace Ocelot.Configuration.Creator requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), fileReRoute.DownstreamScheme, fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, - serviceProviderConfiguration, fileReRoute.ExceptionsAllowedBeforeBreaking, - fileReRoute.DurationOfBreak, fileReRoute.TimeoutValue); + serviceProviderConfiguration, isQos, + new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue)); } var loadBalancer = await _loadBalanceFactory.Get(reRoute); diff --git a/src/Ocelot/Configuration/File/FileQoSOptions.cs b/src/Ocelot/Configuration/File/FileQoSOptions.cs new file mode 100644 index 00000000..dfac3ac6 --- /dev/null +++ b/src/Ocelot/Configuration/File/FileQoSOptions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Configuration.File +{ + public class FileQoSOptions + { + public int ExceptionsAllowedBeforeBreaking { get; set; } + + public int DurationOfBreak { get; set; } + + public int TimeoutValue { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index d16f0fab..9ab898ea 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -12,6 +12,7 @@ namespace Ocelot.Configuration.File AddQueriesToRequest = new Dictionary(); AuthenticationOptions = new FileAuthenticationOptions(); FileCacheOptions = new FileCacheOptions(); + QoSOptions = new FileQoSOptions(); } public string DownstreamPathTemplate { get; set; } @@ -29,9 +30,7 @@ namespace Ocelot.Configuration.File public string DownstreamScheme {get;set;} public string DownstreamHost {get;set;} public int DownstreamPort { get; set; } - public int ExceptionsAllowedBeforeBreaking { get; set; } - public int DurationOfBreak { get; set; } - public int TimeoutValue { get; set; } + public FileQoSOptions QoSOptions { get; set; } public string LoadBalancer {get;set;} } } \ No newline at end of file diff --git a/src/Ocelot/Values/QoS.cs b/src/Ocelot/Configuration/QoSOptions.cs similarity index 74% rename from src/Ocelot/Values/QoS.cs rename to src/Ocelot/Configuration/QoSOptions.cs index b3e8df6a..8584c57e 100644 --- a/src/Ocelot/Values/QoS.cs +++ b/src/Ocelot/Configuration/QoSOptions.cs @@ -4,17 +4,18 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -namespace Ocelot.Values +namespace Ocelot.Configuration { - public class QoS + public class QoSOptions { - public QoS(int exceptionsAllowedBeforeBreaking, int durationofBreak, int timeoutValue, TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) + public QoSOptions(int exceptionsAllowedBeforeBreaking, int durationofBreak, int timeoutValue, TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) { ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak); TimeoutValue = TimeSpan.FromMilliseconds(timeoutValue); TimeoutStrategy = timeoutStrategy; } + public int ExceptionsAllowedBeforeBreaking { get; private set; } @@ -23,5 +24,6 @@ namespace Ocelot.Values public TimeSpan TimeoutValue { get; private set; } public TimeoutStrategy TimeoutStrategy { get; private set; } + } } diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 7065361b..c38d55d5 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -17,7 +17,7 @@ namespace Ocelot.Configuration string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string downstreamScheme, string loadBalancer, string downstreamHost, int downstreamPort, string loadBalancerKey, ServiceProviderConfiguraion serviceProviderConfiguraion, - int exceptionsAllowedBeforeBreaking =3, int durationofBreak =8, int timeoutValue = 5000) + bool isQos,QoSOptions qos) { LoadBalancerKey = loadBalancerKey; ServiceProviderConfiguraion = serviceProviderConfiguraion; @@ -42,9 +42,8 @@ namespace Ocelot.Configuration ClaimsToHeaders = configurationHeaderExtractorProperties ?? new List(); DownstreamScheme = downstreamScheme; - ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; - DurationOfBreak = durationofBreak; - TimeoutValue = timeoutValue; + IsQos = isQos; + QosOptions = qos; } public string LoadBalancerKey {get;private set;} @@ -63,9 +62,8 @@ namespace Ocelot.Configuration public bool IsCached { get; private set; } public CacheOptions FileCacheOptions { get; private set; } public string DownstreamScheme {get;private set;} - public int ExceptionsAllowedBeforeBreaking { get; private set; } - public int DurationOfBreak { get; private set; } - public int TimeoutValue { get; private set; } + public bool IsQos { get; private set; } + public QoSOptions QosOptions { get; private set; } public string LoadBalancer {get;private set;} public string DownstreamHost { get; private set; } public int DownstreamPort { get; private set; } diff --git a/src/Ocelot/Request/Builder/HttpRequestCreator.cs b/src/Ocelot/Request/Builder/HttpRequestCreator.cs index 27ba4c07..f326933c 100644 --- a/src/Ocelot/Request/Builder/HttpRequestCreator.cs +++ b/src/Ocelot/Request/Builder/HttpRequestCreator.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Ocelot.Responses; +using Ocelot.Configuration; namespace Ocelot.Request.Builder { @@ -16,7 +17,8 @@ namespace Ocelot.Request.Builder QueryString queryString, string contentType, RequestId.RequestId requestId, - Values.QoS qos) + bool isQos, + QoSOptions qos) { var request = await new RequestBuilder() .WithHttpMethod(httpMethod) @@ -27,6 +29,7 @@ namespace Ocelot.Request.Builder .WithHeaders(headers) .WithRequestId(requestId) .WithCookies(cookies) + .WithIsQos(isQos) .WithQos(qos) .Build(); diff --git a/src/Ocelot/Request/Builder/IRequestCreator.cs b/src/Ocelot/Request/Builder/IRequestCreator.cs index 858636b5..3417d933 100644 --- a/src/Ocelot/Request/Builder/IRequestCreator.cs +++ b/src/Ocelot/Request/Builder/IRequestCreator.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Ocelot.Responses; +using Ocelot.Configuration; namespace Ocelot.Request.Builder { @@ -13,8 +14,9 @@ namespace Ocelot.Request.Builder IHeaderDictionary headers, IRequestCookieCollection cookies, QueryString queryString, - string contentType, + string contentType, RequestId.RequestId requestId, - Values.QoS qos); + bool isQos, + QoSOptions qos); } } diff --git a/src/Ocelot/Request/Builder/RequestBuilder.cs b/src/Ocelot/Request/Builder/RequestBuilder.cs index d7f35ef7..e0201bc8 100644 --- a/src/Ocelot/Request/Builder/RequestBuilder.cs +++ b/src/Ocelot/Request/Builder/RequestBuilder.cs @@ -8,6 +8,7 @@ using System.Net.Http.Headers; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; +using Ocelot.Configuration; namespace Ocelot.Request.Builder { @@ -22,7 +23,8 @@ namespace Ocelot.Request.Builder private RequestId.RequestId _requestId; private IRequestCookieCollection _cookies; private readonly string[] _unsupportedHeaders = {"host"}; - private Values.QoS _qos; + private bool _isQos; + private QoSOptions _qos; public RequestBuilder WithHttpMethod(string httpMethod) { @@ -72,7 +74,13 @@ namespace Ocelot.Request.Builder return this; } - public RequestBuilder WithQos(Values.QoS qos) + public RequestBuilder WithIsQos(bool isqos) + { + _isQos = isqos; + return this; + } + + public RequestBuilder WithQos(QoSOptions qos) { _qos = qos; return this; @@ -97,7 +105,7 @@ namespace Ocelot.Request.Builder var cookieContainer = CreateCookieContainer(uri); - return new Request(httpRequestMessage, cookieContainer, _qos); + return new Request(httpRequestMessage, cookieContainer,_isQos,_qos); } private Uri CreateUri() diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs index a875ea8c..aafa2f3d 100644 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs +++ b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs @@ -33,7 +33,7 @@ namespace Ocelot.Request.Middleware .Build(context.Request.Method, DownstreamUrl, context.Request.Body, context.Request.Headers, context.Request.Cookies, context.Request.QueryString, context.Request.ContentType, new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier), - new Values.QoS(DownstreamRoute.ReRoute.ExceptionsAllowedBeforeBreaking, DownstreamRoute.ReRoute.DurationOfBreak, DownstreamRoute.ReRoute.TimeoutValue)); + DownstreamRoute.ReRoute.IsQos,DownstreamRoute.ReRoute.QosOptions); if (buildResult.IsError) { diff --git a/src/Ocelot/Request/Request.cs b/src/Ocelot/Request/Request.cs index 60e23b1e..a1e62834 100644 --- a/src/Ocelot/Request/Request.cs +++ b/src/Ocelot/Request/Request.cs @@ -1,4 +1,5 @@ -using Ocelot.Values; +using Ocelot.Configuration; +using Ocelot.Values; using System.Net; using System.Net.Http; @@ -6,15 +7,17 @@ namespace Ocelot.Request { public class Request { - public Request(HttpRequestMessage httpRequestMessage, CookieContainer cookieContainer, QoS qos) + public Request(HttpRequestMessage httpRequestMessage, CookieContainer cookieContainer,bool isQos, QoSOptions qos) { HttpRequestMessage = httpRequestMessage; CookieContainer = cookieContainer; + IsQos = isQos; Qos = qos; } public HttpRequestMessage HttpRequestMessage { get; private set; } public CookieContainer CookieContainer { get; private set; } - public QoS Qos { get; private set; } + public bool IsQos { get; private set; } + public QoSOptions Qos { get; private set; } } } diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index ebf19d24..c3c98ac0 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -1,4 +1,5 @@ -using Ocelot.Logging; +using Ocelot.Configuration; +using Ocelot.Logging; using Ocelot.Values; using Polly.Timeout; using System; @@ -13,15 +14,15 @@ namespace Ocelot.Requester { private readonly Dictionary> handlers = new Dictionary>(); - public HttpClientBuilder WithCircuitBreaker(QoS qos, IOcelotLogger logger, HttpMessageHandler innerHandler) + public HttpClientBuilder WithCircuitBreaker(QoSOptions qos, IOcelotLogger logger, HttpMessageHandler innerHandler) { handlers.Add(5000, () => new CircuitBreakingDelegatingHandler(qos.ExceptionsAllowedBeforeBreaking, qos.DurationOfBreak, qos.TimeoutValue, qos.TimeoutStrategy, logger, innerHandler)); return this; } - internal HttpClient Build() + internal HttpClient Build(HttpMessageHandler innerHandler) { - return handlers.Any() ? new HttpClient(CreateHttpMessageHandler()) : new HttpClient(); + return handlers.Any() ? new HttpClient(CreateHttpMessageHandler()) : new HttpClient(innerHandler); } private HttpMessageHandler CreateHttpMessageHandler() diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index 35c405d1..77ab90cc 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -23,8 +23,11 @@ namespace Ocelot.Requester using (var handler = new HttpClientHandler { CookieContainer = request.CookieContainer }) { - builder.WithCircuitBreaker(request.Qos, _logger, handler); - using (var httpClient = builder.Build()) + if (request.IsQos) + { + builder.WithCircuitBreaker(request.Qos, _logger, handler); + } + using (var httpClient = builder.Build(handler)) { try { diff --git a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs index 4a23282c..98458f9b 100644 --- a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs @@ -125,9 +125,6 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, AuthenticationOptions = new FileAuthenticationOptions { AdditionalScopes = new List(), @@ -168,9 +165,7 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, + AuthenticationOptions = new FileAuthenticationOptions { AdditionalScopes = new List(), @@ -211,10 +206,7 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = _downstreamServiceScheme, UpstreamTemplate = "/", UpstreamHttpMethod = "Post", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, - AuthenticationOptions = new FileAuthenticationOptions + AuthenticationOptions = new FileAuthenticationOptions { AdditionalScopes = new List(), Provider = "IdentityServer", diff --git a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs index 17f99528..1f86c6ff 100644 --- a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs @@ -43,9 +43,6 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, AuthenticationOptions = new FileAuthenticationOptions { AdditionalScopes = new List(), diff --git a/test/Ocelot.AcceptanceTests/CachingTests.cs b/test/Ocelot.AcceptanceTests/CachingTests.cs index 46f5dc3c..e4e628af 100644 --- a/test/Ocelot.AcceptanceTests/CachingTests.cs +++ b/test/Ocelot.AcceptanceTests/CachingTests.cs @@ -37,9 +37,6 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, FileCacheOptions = new FileCacheOptions { TtlSeconds = 100 @@ -76,9 +73,6 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, FileCacheOptions = new FileCacheOptions { TtlSeconds = 1 diff --git a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs index 9cb8e72c..ae545add 100644 --- a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs @@ -36,9 +36,6 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000 } } }; @@ -67,9 +64,6 @@ namespace Ocelot.AcceptanceTests UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false, - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, } } }; @@ -98,9 +92,6 @@ namespace Ocelot.AcceptanceTests UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000 } } }; @@ -129,9 +120,6 @@ namespace Ocelot.AcceptanceTests UpstreamTemplate = "/PRODUCTS/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, } } }; @@ -160,9 +148,6 @@ namespace Ocelot.AcceptanceTests UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, } } }; @@ -191,9 +176,6 @@ namespace Ocelot.AcceptanceTests UpstreamTemplate = "/PRODUCTS/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000 } } }; diff --git a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs index 4c036eac..08bbd968 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs @@ -57,9 +57,6 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, AuthenticationOptions = new FileAuthenticationOptions { AdditionalScopes = new List diff --git a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs index fe8ff799..04dc25db 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs @@ -57,9 +57,6 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, AuthenticationOptions = new FileAuthenticationOptions { AdditionalScopes = new List diff --git a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs index 1ad929de..cee75cef 100644 --- a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -51,9 +51,6 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, } } }; @@ -91,9 +88,7 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, + } } }; @@ -131,9 +126,7 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, + } } }; @@ -171,9 +164,6 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, } } }; @@ -211,9 +201,6 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, } } }; @@ -251,9 +238,6 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, } } }; diff --git a/test/Ocelot.AcceptanceTests/RequestIdTests.cs b/test/Ocelot.AcceptanceTests/RequestIdTests.cs index 2273eb2c..63a40ead 100644 --- a/test/Ocelot.AcceptanceTests/RequestIdTests.cs +++ b/test/Ocelot.AcceptanceTests/RequestIdTests.cs @@ -40,10 +40,7 @@ namespace Ocelot.AcceptanceTests UpstreamTemplate = "/", UpstreamHttpMethod = "Get", RequestIdKey = _steps.RequestIdKey, - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, - } + } } }; @@ -70,10 +67,7 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - RequestIdKey = _steps.RequestIdKey, - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, + } } }; @@ -101,11 +95,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", DownstreamHost = "localhost", - UpstreamTemplate = "/", - UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, + UpstreamTemplate = "/", } }, GlobalConfiguration = new FileGlobalConfiguration diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index acfec46c..479c69ff 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -46,9 +46,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, + } } }; @@ -77,9 +75,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, + } } }; @@ -108,9 +104,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, UpstreamTemplate = "/", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, + } } }; @@ -139,9 +133,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, UpstreamTemplate = "/products/", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, + } } }; @@ -170,9 +162,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, UpstreamTemplate = "/products", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, + } } }; @@ -201,9 +191,11 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", + QoSOptions = new FileQoSOptions() + { ExceptionsAllowedBeforeBreaking = 3, DurationOfBreak =5, - TimeoutValue = 5000, + TimeoutValue = 5000 } } } }; @@ -231,9 +223,6 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, UpstreamTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, } } }; @@ -261,10 +250,7 @@ namespace Ocelot.AcceptanceTests DownstreamPort = 51879, DownstreamScheme = "http", UpstreamTemplate = "/", - UpstreamHttpMethod = "Post", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, + UpstreamHttpMethod = "Post", } } }; @@ -293,9 +279,6 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", DownstreamPort = 51879, UpstreamHttpMethod = "Get", - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak =5, - TimeoutValue = 5000, } } }; diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index 67d0c796..b155bee6 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"ExceptionsAllowedBeforeBreaking":3,"DurationOfBreak":5,"TimeoutValue":5000,"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} \ No newline at end of file diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index a887f3fb..9b5fcb7e 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -1,268 +1,305 @@ { - "ReRoutes": [ - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "http", - "DownstreamHost": "localhost", - "DownstreamPort": 52876, - "UpstreamTemplate": "/identityserverexample", - "UpstreamHttpMethod": "Get", - "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": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts", - "UpstreamHttpMethod": "Get", - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Get", + "ReRoutes": [ + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHost": "localhost", + "DownstreamPort": 52876, + "UpstreamTemplate": "/identityserverexample", + "UpstreamHttpMethod": "Get", + "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, "DurationOfBreak": 10, "TimeoutValue": 5000 }, - { - "DownstreamPathTemplate": "/posts/{postId}/comments", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}/comments", - "UpstreamHttpMethod": "Get", + "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": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/posts", + "UpstreamHttpMethod": "Get", + "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, "DurationOfBreak": 10, "TimeoutValue": 5000 - }, - { - "DownstreamPathTemplate": "/comments", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/comments", - "UpstreamHttpMethod": "Get", - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - { - "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts", - "UpstreamHttpMethod": "Post", - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Put", - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Patch", - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - { - "DownstreamPathTemplate": "/posts/{postId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Delete", - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000 - }, - { - "DownstreamPathTemplate": "/api/products", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/products", - "UpstreamHttpMethod": "Get", - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/products/{productId}", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", - "UpstreamHttpMethod": "Get", - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/products", - "DownstreamScheme": "http", - "DownstreamHost": "products20161126090340.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamTemplate": "/products", - "UpstreamHttpMethod": "Post", - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/products/{productId}", - "DownstreamScheme": "http", - "DownstreamHost": "products20161126090340.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", - "UpstreamHttpMethod": "Put", - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/products/{productId}", - "DownstreamScheme": "http", - "DownstreamHost": "products20161126090340.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", - "UpstreamHttpMethod": "Delete", - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamTemplate": "/customers", - "UpstreamHttpMethod": "Get", - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers/{customerId}", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", - "UpstreamHttpMethod": "Get", - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamTemplate": "/customers", - "UpstreamHttpMethod": "Post", - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers/{customerId}", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", - "UpstreamHttpMethod": "Put", - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/api/customers/{customerId}", - "DownstreamScheme": "http", - "DownstreamHost": "customers20161126090811.azurewebsites.net", - "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", - "UpstreamHttpMethod": "Delete", - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000, - "FileCacheOptions": { "TtlSeconds": 15 } - }, - { - "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "http", - "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts/", - "UpstreamHttpMethod": "Get", - "ExceptionsAllowedBeforeBreaking": 3, - "DurationOfBreak": 10, - "TimeoutValue": 5000, - "FileCacheOptions": { "TtlSeconds": 15 } } - ], - "GlobalConfiguration": { - "RequestIdKey": "OcRequestId" + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Get", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}/comments", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/posts/{postId}/comments", + "UpstreamHttpMethod": "Get", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/comments", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/comments", + "UpstreamHttpMethod": "Get", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/posts", + "UpstreamHttpMethod": "Post", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Put", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Patch", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/posts/{postId}", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/posts/{postId}", + "UpstreamHttpMethod": "Delete", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/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, + "UpstreamTemplate": "/products/{productId}", + "UpstreamHttpMethod": "Get", + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/api/products", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamTemplate": "/products", + "UpstreamHttpMethod": "Post", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + } + }, + { + "DownstreamPathTemplate": "/api/products/{productId}", + "DownstreamScheme": "http", + "DownstreamHost": "products20161126090340.azurewebsites.net", + "DownstreamPort": 80, + "UpstreamTemplate": "/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, + "UpstreamTemplate": "/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, + "UpstreamTemplate": "/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, + "UpstreamTemplate": "/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, + "UpstreamTemplate": "/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, + "UpstreamTemplate": "/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, + "UpstreamTemplate": "/customers/{customerId}", + "UpstreamHttpMethod": "Delete", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } + }, + { + "DownstreamPathTemplate": "/posts", + "DownstreamScheme": "http", + "DownstreamHost": "jsonplaceholder.typicode.com", + "DownstreamPort": 80, + "UpstreamTemplate": "/posts/", + "UpstreamHttpMethod": "Get", + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 3, + "DurationOfBreak": 10, + "TimeoutValue": 5000 + }, + "FileCacheOptions": { "TtlSeconds": 15 } } + ], + + "GlobalConfiguration": { + "RequestIdKey": "OcRequestId" + } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index 5a9eec87..bd7667bf 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -29,7 +29,6 @@ namespace Ocelot.UnitTests.LoadBalancer private readonly HttpClient _client; private HttpResponseMessage _result; private HostAndPort _hostAndPort; - private OkResponse _request; private OkResponse _downstreamUrl; private OkResponse _downstreamRoute; private ErrorResponse _getLoadBalancerHouseError; diff --git a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs index 406e7cb6..6d4837d8 100644 --- a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs @@ -19,6 +19,7 @@ using Ocelot.Request.Middleware; using Ocelot.Responses; using TestStack.BDDfy; using Xunit; +using Ocelot.Configuration; namespace Ocelot.UnitTests.Request { @@ -72,7 +73,7 @@ namespace Ocelot.UnitTests.Request this.Given(x => x.GivenTheDownStreamUrlIs("any old string")) .And(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), new CookieContainer(),new Values.QoS(3, 8 ,5000, Polly.Timeout.TimeoutStrategy.Pessimistic)))) + .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), new CookieContainer(), true, new QoSOptions(3, 8 ,5000, Polly.Timeout.TimeoutStrategy.Pessimistic)))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); @@ -91,7 +92,7 @@ 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(), 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 55d18c38..c317c8d7 100644 --- a/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs +++ b/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs @@ -10,6 +10,7 @@ using Ocelot.Responses; using Shouldly; using TestStack.BDDfy; using Xunit; +using Ocelot.Configuration; namespace Ocelot.UnitTests.Request { @@ -25,7 +26,8 @@ namespace Ocelot.UnitTests.Request private readonly IRequestCreator _requestCreator; private Response _result; private Ocelot.RequestId.RequestId _requestId; - private Ocelot.Values.QoS _qos; + private bool _isQos; + private QoSOptions _qos; public RequestBuilderTests() { @@ -38,7 +40,7 @@ namespace Ocelot.UnitTests.Request { this.Given(x => x.GivenIHaveHttpMethod("GET")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) - .And(x=> x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x=> x.GivenTheQos(true,new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectDownstreamUrlIsUsed("http://www.bbc.co.uk/")) .BDDfy(); @@ -49,7 +51,7 @@ namespace Ocelot.UnitTests.Request { this.Given(x => x.GivenIHaveHttpMethod("POST")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) - .And(x => x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x => x.GivenTheQos(true,new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectHttpMethodIsUsed(HttpMethod.Post)) @@ -63,7 +65,7 @@ namespace Ocelot.UnitTests.Request .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) .And(x => x.GivenTheContentTypeIs("application/json")) - .And(x => x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectContentIsUsed(new StringContent("Hi from Tom"))) @@ -77,7 +79,7 @@ namespace Ocelot.UnitTests.Request .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) .And(x => x.GivenTheContentTypeIs("application/json")) - .And(x => x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary @@ -96,7 +98,7 @@ namespace Ocelot.UnitTests.Request .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) .And(x => x.GivenTheContentTypeIs("application/json; charset=utf-8")) - .And(x => x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary @@ -117,7 +119,7 @@ namespace Ocelot.UnitTests.Request { {"ChopSticks", "Bubbles" } })) - .And(x => x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary @@ -136,7 +138,7 @@ namespace Ocelot.UnitTests.Request .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary())) .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", requestId))) - .And(x => x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary { @@ -155,7 +157,7 @@ namespace Ocelot.UnitTests.Request {"RequestId", "534534gv54gv45g" } })) .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", Guid.NewGuid().ToString()))) - .And(x => x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary { @@ -175,7 +177,7 @@ namespace Ocelot.UnitTests.Request .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary())) .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId(requestIdKey, requestIdValue))) - .And(x => x.GivenTheQos(new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheRequestIdIsNotInTheHeaders()) .BDDfy(); @@ -186,8 +188,9 @@ namespace Ocelot.UnitTests.Request _requestId = requestId; } - private void GivenTheQos(Ocelot.Values.QoS qos) + private void GivenTheQos(bool isQos, QoSOptions qos) { + _isQos = isQos; _qos = qos; } @@ -301,7 +304,7 @@ namespace Ocelot.UnitTests.Request private void WhenICreateARequest() { _result = _requestCreator.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers, - _cookies, _query, _contentType, _requestId, _qos).Result; + _cookies, _query, _contentType, _requestId,_isQos,_qos).Result; } diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index 66c7491e..d2d62923 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -61,7 +61,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(), new Values.QoS(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic)))) + this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),new CookieContainer(),true, new Ocelot.Configuration.QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic)))) .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) .And(x => x.GivenTheScopedRepoReturns()) .When(x => x.WhenICallTheMiddleware()) diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs index f1e732e7..282f8ec1 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs @@ -10,7 +10,6 @@ namespace Ocelot.UnitTests.ServiceDiscovery public class ConfigurationServiceProviderTests { private ConfigurationServiceProvider _serviceProvider; - private HostAndPort _hostAndPort; private List _result; private List _expected; From dbe28d38bca82f9e9ba061bd009c1c3fb8601992 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 8 Feb 2017 07:37:04 +0000 Subject: [PATCH 063/113] tidying up configuration creation --- .../Creator/FileOcelotConfigurationCreator.cs | 69 ++++++++----------- .../AuthenticationHandlerFactoryTests.cs | 12 +++- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index cf6a2b54..40c2bc02 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -108,8 +108,6 @@ namespace Ocelot.Configuration.Creator //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}"; - ReRoute reRoute; - var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; var serviceProviderConfiguration = new ServiceProviderConfiguraionBuilder() @@ -122,48 +120,41 @@ namespace Ocelot.Configuration.Creator .WithServiceDiscoveryProviderPort(serviceProviderPort) .Build(); - if (isAuthenticated) - { - var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider, - fileReRoute.AuthenticationOptions.ProviderRootUrl, fileReRoute.AuthenticationOptions.ScopeName, - fileReRoute.AuthenticationOptions.RequireHttps, fileReRoute.AuthenticationOptions.AdditionalScopes, - fileReRoute.AuthenticationOptions.ScopeSecret); + var authOptionsForRoute = 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(); var claimsToHeaders = GetAddThingsToRequest(fileReRoute.AddHeadersToRequest); var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest); var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest); - reRoute = new ReRoute(new PathTemplate(fileReRoute.DownstreamPathTemplate), - new PathTemplate(fileReRoute.UpstreamPathTemplate), - new HttpMethod(fileReRoute.UpstreamHttpMethod), upstreamTemplatePattern, isAuthenticated, - authOptionsForRoute, claimsToHeaders, claimsToClaims, - fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, - requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds) - , fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, - serviceProviderConfiguration); - - //reRoute = new ReRouteBuilder() - // .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) - // .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) - // .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) - // .WithUpstreamTemplatePattern(upstreamTemplatePattern) - // .WithIsAuthenticated(isAuthenticated) - //.Build(); - - } - else - { - reRoute = new ReRoute(new PathTemplate(fileReRoute.DownstreamPathTemplate), - new PathTemplate(fileReRoute.UpstreamPathTemplate), - new HttpMethod(fileReRoute.UpstreamHttpMethod), upstreamTemplatePattern, isAuthenticated, - null, new List(), new List(), - fileReRoute.RouteClaimsRequirement, isAuthorised, new List(), - requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), - fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, - serviceProviderConfiguration); - } + var reRoute = new ReRouteBuilder() + .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) + .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) + .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) + .WithUpstreamTemplatePattern(upstreamTemplatePattern) + .WithIsAuthenticated(isAuthenticated) + .WithAuthenticationOptions(authOptionsForRoute) + .WithClaimsToHeaders(claimsToHeaders) + .WithClaimsToClaims(claimsToClaims) + .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) + .WithIsAuthorised(isAuthorised) + .WithClaimsToQueries(claimsToQueries) + .WithRequestIdKey(requestIdKey) + .WithIsCached(isCached) + .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)) + .WithDownstreamScheme(fileReRoute.DownstreamScheme) + .WithLoadBalancer(fileReRoute.LoadBalancer) + .WithDownstreamHost(fileReRoute.DownstreamHost) + .WithDownstreamPort(fileReRoute.DownstreamPort) + .WithLoadBalancerKey(loadBalancerKey) + .WithServiceProviderConfiguraion(serviceProviderConfiguration) + .Build(); var loadBalancer = await _loadBalanceFactory.Get(reRoute); _loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer); diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs index e76a2b28..229da4a9 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs @@ -6,6 +6,7 @@ using Moq; using Ocelot.Authentication.Handler; using Ocelot.Authentication.Handler.Creator; using Ocelot.Authentication.Handler.Factory; +using Ocelot.Configuration.Builder; using Ocelot.Errors; using Ocelot.Responses; using Shouldly; @@ -33,7 +34,11 @@ namespace Ocelot.UnitTests.Authentication [Fact] public void should_return_identity_server_access_token_handler() { - this.Given(x => x.GivenTheAuthenticationOptionsAre(new AuthenticationOptions("IdentityServer", "","",false, new List(), ""))) + var authenticationOptions = new AuthenticationOptionsBuilder() + .WithProvider("IdentityServer") + .Build(); + + this.Given(x => x.GivenTheAuthenticationOptionsAre(authenticationOptions)) .And(x => x.GivenTheCreatorReturns()) .When(x => x.WhenIGetFromTheFactory()) .Then(x => x.ThenTheHandlerIsReturned("IdentityServer")) @@ -43,7 +48,10 @@ namespace Ocelot.UnitTests.Authentication [Fact] public void should_return_error_if_cannot_create_handler() { - this.Given(x => x.GivenTheAuthenticationOptionsAre(new AuthenticationOptions("IdentityServer", "", "", false, new List(), ""))) + var authenticationOptions = new AuthenticationOptionsBuilder() + .Build(); + + this.Given(x => x.GivenTheAuthenticationOptionsAre(authenticationOptions)) .And(x => x.GivenTheCreatorReturnsAnError()) .When(x => x.WhenIGetFromTheFactory()) .Then(x => x.ThenAnErrorResponseIsReturned()) From 26ac0fd3f45b76997ab75f164ba8feea2ba8e2c0 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 8 Feb 2017 07:49:50 +0000 Subject: [PATCH 064/113] more refactoring --- .../Creator/FileOcelotConfigurationCreator.cs | 117 +++++++++++------- 1 file changed, 70 insertions(+), 47 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 40c2bc02..7439e01c 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -90,8 +90,6 @@ namespace Ocelot.Configuration.Creator { var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); - var upstreamTemplatePattern = BuildUpstreamTemplate(fileReRoute); - var isAuthenticated = !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); var isAuthorised = fileReRoute.RouteClaimsRequirement?.Count > 0; @@ -102,15 +100,80 @@ namespace Ocelot.Configuration.Creator ? globalConfiguration.RequestIdKey : fileReRoute.RequestIdKey; + var loadBalancerKey = BuildLoadBalancerKey(fileReRoute); + + var upstreamTemplatePattern = BuildUpstreamTemplate(fileReRoute); + + var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration); + + var authOptionsForRoute = BuildAuthenticationOptions(fileReRoute); + + var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest); + + var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest); + + var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest); + + var reRoute = new ReRouteBuilder() + .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) + .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) + .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) + .WithUpstreamTemplatePattern(upstreamTemplatePattern) + .WithIsAuthenticated(isAuthenticated) + .WithAuthenticationOptions(authOptionsForRoute) + .WithClaimsToHeaders(claimsToHeaders) + .WithClaimsToClaims(claimsToClaims) + .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) + .WithIsAuthorised(isAuthorised) + .WithClaimsToQueries(claimsToQueries) + .WithRequestIdKey(requestIdKey) + .WithIsCached(isCached) + .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)) + .WithDownstreamScheme(fileReRoute.DownstreamScheme) + .WithLoadBalancer(fileReRoute.LoadBalancer) + .WithDownstreamHost(fileReRoute.DownstreamHost) + .WithDownstreamPort(fileReRoute.DownstreamPort) + .WithLoadBalancerKey(loadBalancerKey) + .WithServiceProviderConfiguraion(serviceProviderConfiguration) + .Build(); + + await SetupLoadBalancer(reRoute); + return reRoute; + } + + private string BuildLoadBalancerKey(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}"; + 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); + _loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer); + } + + private ServiceProviderConfiguraion BuildServiceProviderConfiguration(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) + { var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName) && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); - //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}"; - var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; - var serviceProviderConfiguration = new ServiceProviderConfiguraionBuilder() + return new ServiceProviderConfiguraionBuilder() .WithServiceName(fileReRoute.ServiceName) .WithDownstreamHost(fileReRoute.DownstreamHost) .WithDownstreamPort(fileReRoute.DownstreamPort) @@ -119,46 +182,6 @@ namespace Ocelot.Configuration.Creator .WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host) .WithServiceDiscoveryProviderPort(serviceProviderPort) .Build(); - - var authOptionsForRoute = 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(); - - var claimsToHeaders = GetAddThingsToRequest(fileReRoute.AddHeadersToRequest); - var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest); - var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest); - - var reRoute = new ReRouteBuilder() - .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) - .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) - .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) - .WithUpstreamTemplatePattern(upstreamTemplatePattern) - .WithIsAuthenticated(isAuthenticated) - .WithAuthenticationOptions(authOptionsForRoute) - .WithClaimsToHeaders(claimsToHeaders) - .WithClaimsToClaims(claimsToClaims) - .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) - .WithIsAuthorised(isAuthorised) - .WithClaimsToQueries(claimsToQueries) - .WithRequestIdKey(requestIdKey) - .WithIsCached(isCached) - .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)) - .WithDownstreamScheme(fileReRoute.DownstreamScheme) - .WithLoadBalancer(fileReRoute.LoadBalancer) - .WithDownstreamHost(fileReRoute.DownstreamHost) - .WithDownstreamPort(fileReRoute.DownstreamPort) - .WithLoadBalancerKey(loadBalancerKey) - .WithServiceProviderConfiguraion(serviceProviderConfiguration) - .Build(); - - var loadBalancer = await _loadBalanceFactory.Get(reRoute); - _loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer); - return reRoute; } private string BuildUpstreamTemplate(FileReRoute reRoute) @@ -192,7 +215,7 @@ namespace Ocelot.Configuration.Creator return route; } - private List GetAddThingsToRequest(Dictionary thingBeingAdded) + private List BuildAddThingsToRequest(Dictionary thingBeingAdded) { var claimsToTHings = new List(); From 9d790a449b77dd90fb1f240ca3c55708e3e88af4 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 8 Feb 2017 18:23:07 +0000 Subject: [PATCH 065/113] more refactoring of fileconfig creator --- .../Creator/FileOcelotConfigurationCreator.cs | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 7439e01c..305dfc66 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -88,17 +88,13 @@ namespace Ocelot.Configuration.Creator private async Task SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) { - var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); + var isAuthenticated = IsAuthenticated(fileReRoute); - var isAuthenticated = !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); + var isAuthorised = IsAuthenticated(fileReRoute); - var isAuthorised = fileReRoute.RouteClaimsRequirement?.Count > 0; + var isCached = IsCached(fileReRoute); - var isCached = fileReRoute.FileCacheOptions.TtlSeconds > 0; - - var requestIdKey = globalRequestIdConfiguration - ? globalConfiguration.RequestIdKey - : fileReRoute.RequestIdKey; + var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration); var loadBalancerKey = BuildLoadBalancerKey(fileReRoute); @@ -141,6 +137,32 @@ namespace Ocelot.Configuration.Creator return reRoute; } + 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 BuildRequestId(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) + { + var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); + + var requestIdKey = globalRequestIdConfiguration + ? globalConfiguration.RequestIdKey + : fileReRoute.RequestIdKey; + + return requestIdKey; + } + private string BuildLoadBalancerKey(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 From f7fe7c0f497d71e69f22989ca19e5cdfe9ecb2ad Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 8 Feb 2017 18:47:35 +0000 Subject: [PATCH 066/113] more refactoring --- build.cake | 2 +- src/.DS_Store | Bin 0 -> 6148 bytes src/Ocelot/.DS_Store | Bin 0 -> 8196 bytes src/Ocelot/Authentication/Handler/.DS_Store | Bin 0 -> 6148 bytes .../Creator/AuthenticationHandlerCreator.cs | 2 +- .../Creator/IAuthenticationHandlerCreator.cs | 2 +- .../Factory/AuthenticationHandlerFactory.cs | 2 +- src/Ocelot/Authorisation/ClaimsAuthoriser.cs | 1 - .../Creator/FileOcelotConfigurationCreator.cs | 4 ---- .../AuthenticationHandlerFactoryTests.cs | 4 ++-- .../ErrorsToHttpStatusCodeMapperTests.cs | 3 --- 11 files changed, 6 insertions(+), 14 deletions(-) create mode 100644 src/.DS_Store create mode 100644 src/Ocelot/.DS_Store create mode 100644 src/Ocelot/Authentication/Handler/.DS_Store diff --git a/build.cake b/build.cake index 1a1f005d..0f031972 100644 --- a/build.cake +++ b/build.cake @@ -42,7 +42,7 @@ var nugetFeedStableSymbolsUploadUrl = "https://www.nuget.org/api/v2/package"; // internal build variables - don't change these. var releaseTag = ""; -var committedVersion = "0.0.0-dev"; +string committedVersion = "0.0.0-dev"; var buildVersion = committedVersion; var target = Argument("target", "Default"); diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e6c4e75f3d6ddeee04f8f8aa9d10d754c345d1f2 GIT binary patch literal 6148 zcmeHK&1%~~5Z-m%M2ce2A)%!-pnG$0+q9uK;Ut$(h(W;pV;+D&>Nex}K?Ve#%8I#~nEod3FA4;eNQ)E!hZCyQyG3Uh#|Yr#sPPZx8I4fT$p^hDy+8YYhqk})v;e^HpUF#Td49qjIVje@>{}2Cs|DR8CPmTe{z<w=R4f?zIu<3s4k{s}0_yfT6cyh{dh=4yYFJM`!@X7Hfmx0dWTbO@nJ3 I1Amo)pQC1So&W#< literal 0 HcmV?d00001 diff --git a/src/Ocelot/.DS_Store b/src/Ocelot/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..996c69e40ae6edfe8644ddf224acf46e45ffdbc9 GIT binary patch literal 8196 zcmeHM-EJF26h0HDu^ro$5viyUNPAg{stQmj+!;4P1Qj*ZZHh!u*tNIGmSs2WuH!~g zBwrw2fE(U{2jBrfP$V7zam$Z*i}0N@n`mb3v^SNgU?$o*oAvjdIp;fPcQXzVss4EM zD$zwEYT#hoZDE=-_;WsH#zJ|;gH_-Yg_KZ15e>;C#Za1#&pn32_DVa|R?|soI;nDHRqjxftPY+ha8f;O-Ru-_3d}0t;nI0}hsM~q z?^^qJ-p8kuY{cm>h|>T#zO~-PaS4YyKF0h6ZZOIBG*`VC?8Q-Dtgim17HW& za;x2Gb-L|Vr}fETH=PU$zZmCzKilrUG>ArF;AeNEQL%eFOxELYKTK|BH~b>ZK4$Rk zG>rk!=YFsig_G^>%JnoU{3r=C@uz*W|Nh6Hq7$j|!9^?j_tW{WC#kqU$dWisc7S6aozrTil%YmB zzH6CZt6zY=bSwjBL^TS4uT)gss-Vu%b$A?dH`ujTzgCb-K*qpE^f~1=R{G={W9<&G z=q9=k^bWc$du-Gu^LbiBU+zh7j$|Iv0d2t71N=6epSxsbdbt2EDNVr2!N|b$5%n!x zAv%XOuJ9!Ozm53Y zCpg5zJ#_`$m4jEgTO<>c_uLGRS02)5XL(SZm+4`dHYGyt@C++Qu$8+>vNSnu`Vf7~ z_yZb%!_NVzUgdF?3m+L-*3t1HeIZ=j2ey`+n}u%LXkO1|DCF{3#s-Zl4Ag55BZrFo zbeMDhNe+*U9CTDCjC@ut$-`uHp%H7f50$u=*I;v;rMMy*R$}C=On#H@NL^y^&a98c zSfVYtd$>lGT5>+Mw~T!1=zspI+*DhkV3O@PtZc_&PybZ6SmF^1?h`UPvR z@CJN2#*&gw?f&m3jqU3X@yaZWwDyJmZM46>BeH>`_5G z>;0)tuv=vm3u2FXow~dhill|ACsr zgcg168So5@Gq7yC6*>P8zrO#EPx3v_fM?)eF= k3M+RUYeSCW4HgxS1=1jf4s(mtQ2ZYOrNLL8fgfey1AVE?-~a#s literal 0 HcmV?d00001 diff --git a/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs b/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs index 65260d64..67f2ebbd 100644 --- a/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs +++ b/src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs @@ -12,7 +12,7 @@ namespace Ocelot.Authentication.Handler.Creator /// public class AuthenticationHandlerCreator : IAuthenticationHandlerCreator { - public Response CreateIdentityServerAuthenticationHandler(IApplicationBuilder app, AuthenticationOptions authOptions) + public Response Create(IApplicationBuilder app, AuthenticationOptions authOptions) { var builder = app.New(); diff --git a/src/Ocelot/Authentication/Handler/Creator/IAuthenticationHandlerCreator.cs b/src/Ocelot/Authentication/Handler/Creator/IAuthenticationHandlerCreator.cs index 6baa0385..9d92c81d 100644 --- a/src/Ocelot/Authentication/Handler/Creator/IAuthenticationHandlerCreator.cs +++ b/src/Ocelot/Authentication/Handler/Creator/IAuthenticationHandlerCreator.cs @@ -8,6 +8,6 @@ namespace Ocelot.Authentication.Handler.Creator public interface IAuthenticationHandlerCreator { - Response CreateIdentityServerAuthenticationHandler(IApplicationBuilder app, AuthenticationOptions authOptions); + Response Create(IApplicationBuilder app, AuthenticationOptions authOptions); } } diff --git a/src/Ocelot/Authentication/Handler/Factory/AuthenticationHandlerFactory.cs b/src/Ocelot/Authentication/Handler/Factory/AuthenticationHandlerFactory.cs index 6379cc1f..60253816 100644 --- a/src/Ocelot/Authentication/Handler/Factory/AuthenticationHandlerFactory.cs +++ b/src/Ocelot/Authentication/Handler/Factory/AuthenticationHandlerFactory.cs @@ -19,7 +19,7 @@ namespace Ocelot.Authentication.Handler.Factory public Response Get(IApplicationBuilder app, AuthenticationOptions authOptions) { - var handler = _creator.CreateIdentityServerAuthenticationHandler(app, authOptions); + var handler = _creator.Create(app, authOptions); if (!handler.IsError) { diff --git a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs index cb7849e9..96f7acd6 100644 --- a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs +++ b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using System.Security.Claims; using Ocelot.Errors; using Ocelot.Responses; diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 305dfc66..357d6296 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -55,10 +55,6 @@ namespace Ocelot.Configuration.Creator return new OkResponse(config); } - /// - /// This method is meant to be tempoary to convert a config to an ocelot config...probably wont keep this but we will see - /// will need a refactor at some point as its crap - /// private async Task SetUpConfiguration() { var response = _configurationValidator.IsValid(_options.Value); diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs index 229da4a9..8bf53607 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationHandlerFactoryTests.cs @@ -66,7 +66,7 @@ namespace Ocelot.UnitTests.Authentication private void GivenTheCreatorReturnsAnError() { _creator - .Setup(x => x.CreateIdentityServerAuthenticationHandler(It.IsAny(), It.IsAny())) + .Setup(x => x.Create(It.IsAny(), It.IsAny())) .Returns(new ErrorResponse(new List { new UnableToCreateAuthenticationHandlerError($"Unable to create authentication handler for xxx") @@ -76,7 +76,7 @@ namespace Ocelot.UnitTests.Authentication private void GivenTheCreatorReturns() { _creator - .Setup(x => x.CreateIdentityServerAuthenticationHandler(It.IsAny(), It.IsAny())) + .Setup(x => x.Create(It.IsAny(), It.IsAny())) .Returns(new OkResponse(x => Task.CompletedTask)); } diff --git a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs index 78f78235..cb8198dc 100644 --- a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs +++ b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs @@ -1,7 +1,4 @@ using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using Microsoft.AspNetCore.Http; using Ocelot.Errors; using Ocelot.Middleware; using Ocelot.Responder; From 48a62382fe5f79e870e7ed809c3cbaffb180df68 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 8 Feb 2017 18:53:52 +0000 Subject: [PATCH 067/113] changes missed --- .../Authentication/Middleware/AuthenticationMiddleware.cs | 1 - src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs index ad30e166..037deeee 100644 --- a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs +++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.Authentication.Handler.Factory; using Ocelot.Configuration; using Ocelot.Errors; diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs index 760d2a38..a86643a4 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Logging; -using Ocelot.Infrastructure.RequestData; +using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Responses; From 18e0f40c9515bc3d35c19930842a8b799666b2d8 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Thu, 9 Feb 2017 08:30:32 +0800 Subject: [PATCH 068/113] Fix should_use_global_request_id_and_forward not pass This test needs UpstreamHttpMethod = "Get", --- test/Ocelot.AcceptanceTests/RequestIdTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Ocelot.AcceptanceTests/RequestIdTests.cs b/test/Ocelot.AcceptanceTests/RequestIdTests.cs index 63a40ead..4ad5bd8c 100644 --- a/test/Ocelot.AcceptanceTests/RequestIdTests.cs +++ b/test/Ocelot.AcceptanceTests/RequestIdTests.cs @@ -96,6 +96,7 @@ namespace Ocelot.AcceptanceTests DownstreamScheme = "http", DownstreamHost = "localhost", UpstreamTemplate = "/", + UpstreamHttpMethod ="Get" } }, GlobalConfiguration = new FileGlobalConfiguration From e1d5ef3aaefe567af4100de35afacbdde32936bc Mon Sep 17 00:00:00 2001 From: geffzhang Date: Sat, 11 Feb 2017 16:32:30 +0800 Subject: [PATCH 069/113] implement Request Rate limit, this feature is options --- global.json | 2 +- .../Configuration/Builder/ReRouteBuilder.cs | 18 ++- .../Creator/FileOcelotConfigurationCreator.cs | 21 ++- .../File/FileRateLimitOptions.cs | 66 ++++++++ src/Ocelot/Configuration/File/FileReRoute.cs | 2 + src/Ocelot/Configuration/QoSOptions.cs | 3 - src/Ocelot/Configuration/RateLimitOptions.cs | 76 +++++++++ src/Ocelot/Configuration/ReRoute.cs | 6 +- .../ServiceCollectionExtensions.cs | 2 + .../Middleware/OcelotMiddlewareExtensions.cs | 4 + .../RateLimit/ClientRateLimitProcessor.cs | 36 +++++ src/Ocelot/RateLimit/ClientRequestIdentity.cs | 11 ++ .../RateLimit/IRateLimitCounterHandler.cs | 15 ++ .../MemoryCacheRateLimitCounterHandler.cs | 45 ++++++ .../Middleware/ClientRateLimitMiddleware.cs | 142 +++++++++++++++++ .../RateLimitMiddlewareExtensions.cs | 16 ++ src/Ocelot/RateLimit/RateLimitCore.cs | 123 +++++++++++++++ src/Ocelot/RateLimit/RateLimitCounter.cs | 17 ++ src/Ocelot/RateLimit/RateLimitHeaders.cs | 19 +++ test/Ocelot.AcceptanceTests/Steps.cs | 3 +- .../Ocelot.AcceptanceTests/configuration.json | 2 +- test/Ocelot.AcceptanceTests/project.json | 3 +- test/Ocelot.ManualTest/Startup.cs | 2 +- .../ClientRateLimitMiddlewareTests.cs | 146 ++++++++++++++++++ 24 files changed, 766 insertions(+), 14 deletions(-) create mode 100644 src/Ocelot/Configuration/File/FileRateLimitOptions.cs create mode 100644 src/Ocelot/Configuration/RateLimitOptions.cs create mode 100644 src/Ocelot/RateLimit/ClientRateLimitProcessor.cs create mode 100644 src/Ocelot/RateLimit/ClientRequestIdentity.cs create mode 100644 src/Ocelot/RateLimit/IRateLimitCounterHandler.cs create mode 100644 src/Ocelot/RateLimit/MemoryCacheRateLimitCounterHandler.cs create mode 100644 src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs create mode 100644 src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs create mode 100644 src/Ocelot/RateLimit/RateLimitCore.cs create mode 100644 src/Ocelot/RateLimit/RateLimitCounter.cs create mode 100644 src/Ocelot/RateLimit/RateLimitHeaders.cs create mode 100644 test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs diff --git a/global.json b/global.json index ff8d898e..616b2c4e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@  { "projects": [ "src", "test" ], "sdk": { - "version": "1.0.0-preview2-003133" + "version": "1.0.0-preview2-003131" } } diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 568e7c68..028b89e3 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -38,7 +38,8 @@ namespace Ocelot.Configuration.Builder private int _serviceProviderPort; private bool _useQos; private QoSOptions _qosOptions; - + public bool _enableRateLimiting; + public RateLimitOptions _rateLimitOptions; public ReRouteBuilder() { @@ -236,6 +237,19 @@ namespace Ocelot.Configuration.Builder return this; } + public ReRouteBuilder WithEnableRateLimiting(bool input) + { + _enableRateLimiting = input; + return this; + } + + public ReRouteBuilder WithRateLimitOptions(RateLimitOptions input) + { + _rateLimitOptions = input; + return this; + } + + public ReRoute Build() { return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, @@ -244,7 +258,7 @@ namespace Ocelot.Configuration.Builder _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _downstreamScheme, _loadBalancer, _downstreamHost, _dsPort, _loadBalancerKey, new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _dsPort, _useServiceDiscovery, _serviceDiscoveryProvider, _serviceProviderHost, _serviceProviderPort), - _useQos,_qosOptions); + _useQos,_qosOptions,_enableRateLimiting,_rateLimitOptions); } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index f550edac..a7244bb1 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -109,8 +109,21 @@ namespace Ocelot.Configuration.Creator var loadBalancerKey = $"{fileReRoute.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}"; ReRoute reRoute; - + var enableRateLimiting = (fileReRoute.RateLimitOptions!= null && fileReRoute.RateLimitOptions.EnableRateLimiting)? true: false; + RateLimitOptions rateLimitOption = null; + if (enableRateLimiting) + { + rateLimitOption = new RateLimitOptions(enableRateLimiting, fileReRoute.RateLimitOptions.ClientIdHeader, + fileReRoute.RateLimitOptions.ClientWhitelist,fileReRoute.RateLimitOptions.DisableRateLimitHeaders, + fileReRoute.RateLimitOptions.QuotaExceededMessage,fileReRoute.RateLimitOptions.RateLimitCounterPrefix, + new RateLimitRule() + { + Limit = fileReRoute.RateLimitOptions.RateLimitRule.Limit, + Period = fileReRoute.RateLimitOptions.RateLimitRule.Period, + PeriodTimespan = TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.RateLimitRule.PeriodTimespan) + }); + } var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; var serviceProviderConfiguration = new ServiceProviderConfiguraion(fileReRoute.ServiceName, @@ -138,7 +151,8 @@ namespace Ocelot.Configuration.Creator , fileReRoute.DownstreamScheme, fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, serviceProviderConfiguration, isQos, - new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue)); + new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue), + enableRateLimiting, rateLimitOption); } else { @@ -151,7 +165,8 @@ namespace Ocelot.Configuration.Creator fileReRoute.DownstreamScheme, fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, serviceProviderConfiguration, isQos, - new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue)); + new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue), + enableRateLimiting, rateLimitOption); } var loadBalancer = await _loadBalanceFactory.Get(reRoute); diff --git a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs new file mode 100644 index 00000000..bc555f33 --- /dev/null +++ b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Configuration.File +{ + public class FileRateLimitOptions + { + public FileRateLimitOptions() + { + RateLimitRule = new FileRateLimitRule(); + ClientWhitelist = new List(); + } + + public FileRateLimitRule RateLimitRule { get; set; } + + public List ClientWhitelist { get; set; } + + /// + /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId + /// + public string ClientIdHeader { get; set; } = "ClientId"; + + /// + /// Gets or sets the policy prefix, used to compose the client policy cache key + /// + public string ClientPolicyPrefix { get; set; } = "crlp"; + + /// + /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. + /// If none specified the default will be: + /// API calls quota exceeded! maximum admitted {0} per {1} + /// + public string QuotaExceededMessage { get; set; } + + /// + /// Gets or sets the counter prefix, used to compose the rate limit counter cache key + /// + public string RateLimitCounterPrefix { get; set; } = "ocelot"; + + /// + /// Enables endpoint rate limiting based URL path and HTTP verb + /// + public bool EnableRateLimiting { get; set; } + + /// + /// Disables X-Rate-Limit and Rety-After headers + /// + public bool DisableRateLimitHeaders { get; set; } + } + + public class FileRateLimitRule + { + /// + /// Rate limit period as in 1s, 1m, 1h + /// + public string Period { get; set; } + + public int PeriodTimespan { get; set; } + /// + /// Maximum number of requests that a client can make in a defined period + /// + public long Limit { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 9ab898ea..69b13e6a 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -13,6 +13,7 @@ namespace Ocelot.Configuration.File AuthenticationOptions = new FileAuthenticationOptions(); FileCacheOptions = new FileCacheOptions(); QoSOptions = new FileQoSOptions(); + RateLimitOptions = new FileRateLimitOptions(); } public string DownstreamPathTemplate { get; set; } @@ -32,5 +33,6 @@ namespace Ocelot.Configuration.File public int DownstreamPort { get; set; } public FileQoSOptions QoSOptions { get; set; } public string LoadBalancer {get;set;} + public FileRateLimitOptions RateLimitOptions { get; set; } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/QoSOptions.cs b/src/Ocelot/Configuration/QoSOptions.cs index 8584c57e..27b862af 100644 --- a/src/Ocelot/Configuration/QoSOptions.cs +++ b/src/Ocelot/Configuration/QoSOptions.cs @@ -1,8 +1,5 @@ using Polly.Timeout; using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; namespace Ocelot.Configuration { diff --git a/src/Ocelot/Configuration/RateLimitOptions.cs b/src/Ocelot/Configuration/RateLimitOptions.cs new file mode 100644 index 00000000..51765874 --- /dev/null +++ b/src/Ocelot/Configuration/RateLimitOptions.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Configuration +{ + /// + /// RateLimit Options + /// + public class RateLimitOptions + { + public RateLimitOptions(bool enbleRateLimiting, string clientIdHeader, List clientWhitelist,bool disableRateLimitHeaders, + string quotaExceededMessage, string rateLimitCounterPrefix, RateLimitRule rateLimitRule) + { + EnableRateLimiting = enbleRateLimiting; + ClientIdHeader = clientIdHeader; + ClientWhitelist = clientWhitelist; + DisableRateLimitHeaders = disableRateLimitHeaders; + QuotaExceededMessage = quotaExceededMessage; + RateLimitCounterPrefix = rateLimitCounterPrefix; + RateLimitRule = rateLimitRule; + } + + + public RateLimitRule RateLimitRule { get; private set; } + + public List ClientWhitelist { get; private set; } + + /// + /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId + /// + public string ClientIdHeader { get; private set; } = "ClientId"; + + /// + /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) + /// + public int HttpStatusCode { get; private set; } = 429; + + /// + /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. + /// If none specified the default will be: + /// API calls quota exceeded! maximum admitted {0} per {1} + /// + public string QuotaExceededMessage { get; private set; } + + /// + /// Gets or sets the counter prefix, used to compose the rate limit counter cache key + /// + public string RateLimitCounterPrefix { get; private set; } = "ocelot"; + + /// + /// Enables endpoint rate limiting based URL path and HTTP verb + /// + public bool EnableRateLimiting { get; private set; } + + /// + /// Disables X-Rate-Limit and Rety-After headers + /// + public bool DisableRateLimitHeaders { get; private set; } + } + + public class RateLimitRule + { + /// + /// Rate limit period as in 1s, 1m, 1h + /// + public string Period { get; set; } + + public TimeSpan? PeriodTimespan { get; set; } + /// + /// Maximum number of requests that a client can make in a defined period + /// + public long Limit { get; set; } + } +} diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index c38d55d5..38e28af9 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -17,7 +17,7 @@ namespace Ocelot.Configuration string requestIdKey, bool isCached, CacheOptions fileCacheOptions, string downstreamScheme, string loadBalancer, string downstreamHost, int downstreamPort, string loadBalancerKey, ServiceProviderConfiguraion serviceProviderConfiguraion, - bool isQos,QoSOptions qos) + bool isQos,QoSOptions qos, bool enableRateLimit, RateLimitOptions ratelimitOptions) { LoadBalancerKey = loadBalancerKey; ServiceProviderConfiguraion = serviceProviderConfiguraion; @@ -44,6 +44,8 @@ namespace Ocelot.Configuration DownstreamScheme = downstreamScheme; IsQos = isQos; QosOptions = qos; + EnableEndpointRateLimiting = enableRateLimit; + RateLimitOptions = ratelimitOptions; } public string LoadBalancerKey {get;private set;} @@ -68,5 +70,7 @@ namespace Ocelot.Configuration public string DownstreamHost { get; private set; } public int DownstreamPort { get; private set; } public ServiceProviderConfiguraion ServiceProviderConfiguraion { get; private set; } + public bool EnableEndpointRateLimiting { get; private set; } + public RateLimitOptions RateLimitOptions { get; private set; } } } \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 0a6bd42c..e83ad30c 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -30,6 +30,7 @@ using Ocelot.Request.Builder; using Ocelot.Requester; using Ocelot.Responder; using Ocelot.ServiceDiscovery; +using Ocelot.RateLimit; namespace Ocelot.DependencyInjection { @@ -84,6 +85,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc // could maybe use a scoped data repository diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 352aa501..4d9f643c 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -11,6 +11,7 @@ using Ocelot.Request.Middleware; using Ocelot.Requester.Middleware; using Ocelot.RequestId.Middleware; using Ocelot.Responder.Middleware; +using Ocelot.RateLimit.Middleware; namespace Ocelot.Middleware { @@ -57,6 +58,9 @@ namespace Ocelot.Middleware // Then we get the downstream route information builder.UseDownstreamRouteFinderMiddleware(); + // We check whether the request is ratelimit, and if there is no continue processing + builder.UseRateLimiting(); + // Now we can look for the requestId builder.UseRequestIdMiddleware(); diff --git a/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs b/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs new file mode 100644 index 00000000..38141b49 --- /dev/null +++ b/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs @@ -0,0 +1,36 @@ +using Ocelot.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public class ClientRateLimitProcessor + { + private readonly IRateLimitCounterHandler _counterHandler; + private readonly RateLimitCore _core; + + public ClientRateLimitProcessor(IRateLimitCounterHandler counterHandler) + { + _counterHandler = counterHandler; + _core = new RateLimitCore(_counterHandler); + } + + public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + return _core.ProcessRequest(requestIdentity, option); + } + + public string RetryAfterFrom(DateTime timestamp, RateLimitRule rule) + { + return _core.RetryAfterFrom(timestamp, rule); + } + + public RateLimitHeaders GetRateLimitHeaders(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + return _core.GetRateLimitHeaders(requestIdentity, option); + } + + } +} diff --git a/src/Ocelot/RateLimit/ClientRequestIdentity.cs b/src/Ocelot/RateLimit/ClientRequestIdentity.cs new file mode 100644 index 00000000..9cceeb9e --- /dev/null +++ b/src/Ocelot/RateLimit/ClientRequestIdentity.cs @@ -0,0 +1,11 @@ +namespace Ocelot.RateLimit +{ + public class ClientRequestIdentity + { + public string ClientId { get; set; } + + public string Path { get; set; } + + public string HttpVerb { get; set; } + } +} \ No newline at end of file diff --git a/src/Ocelot/RateLimit/IRateLimitCounterHandler.cs b/src/Ocelot/RateLimit/IRateLimitCounterHandler.cs new file mode 100644 index 00000000..cb745a44 --- /dev/null +++ b/src/Ocelot/RateLimit/IRateLimitCounterHandler.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public interface IRateLimitCounterHandler + { + bool Exists(string id); + RateLimitCounter? Get(string id); + void Remove(string id); + void Set(string id, RateLimitCounter counter, TimeSpan expirationTime); + } +} diff --git a/src/Ocelot/RateLimit/MemoryCacheRateLimitCounterHandler.cs b/src/Ocelot/RateLimit/MemoryCacheRateLimitCounterHandler.cs new file mode 100644 index 00000000..9756f2ae --- /dev/null +++ b/src/Ocelot/RateLimit/MemoryCacheRateLimitCounterHandler.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.Caching.Memory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public class MemoryCacheRateLimitCounterHandler : IRateLimitCounterHandler + { + private readonly IMemoryCache _memoryCache; + + public MemoryCacheRateLimitCounterHandler(IMemoryCache memoryCache) + { + _memoryCache = memoryCache; + } + + public void Set(string id, RateLimitCounter counter, TimeSpan expirationTime) + { + _memoryCache.Set(id, counter, new MemoryCacheEntryOptions().SetAbsoluteExpiration(expirationTime)); + } + + public bool Exists(string id) + { + RateLimitCounter counter; + return _memoryCache.TryGetValue(id, out counter); + } + + public RateLimitCounter? Get(string id) + { + RateLimitCounter counter; + if (_memoryCache.TryGetValue(id, out counter)) + { + return counter; + } + + return null; + } + + public void Remove(string id) + { + _memoryCache.Remove(id); + } + } +} diff --git a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs new file mode 100644 index 00000000..64ac40c0 --- /dev/null +++ b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs @@ -0,0 +1,142 @@ +using Ocelot.Middleware; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Infrastructure.RequestData; +using Microsoft.AspNetCore.Http; +using Ocelot.Logging; +using Ocelot.Configuration; + +namespace Ocelot.RateLimit.Middleware +{ + public class ClientRateLimitMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IOcelotLogger _logger; + private readonly IRateLimitCounterHandler _counterHandler; + private readonly ClientRateLimitProcessor _processor; + + public ClientRateLimitMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IRequestScopedDataRepository requestScopedDataRepository, + IRateLimitCounterHandler counterHandler) + : base(requestScopedDataRepository) + { + _next = next; + _logger = loggerFactory.CreateLogger(); + _counterHandler = counterHandler; + _processor = new ClientRateLimitProcessor(counterHandler); + } + + public async Task Invoke(HttpContext context) + { + _logger.LogDebug("started calling RateLimit middleware"); + var options = DownstreamRoute.ReRoute.RateLimitOptions; + // check if rate limiting is enabled + if (!DownstreamRoute.ReRoute.EnableEndpointRateLimiting) + { + await _next.Invoke(context); + return; + } + // compute identity from request + var identity = SetIdentity(context, options); + + // check white list + if (IsWhitelisted(identity, options)) + { + await _next.Invoke(context); + return; + } + + var rule = options.RateLimitRule; + if (rule.Limit > 0) + { + // increment counter + var counter = _processor.ProcessRequest(identity, options); + + // check if limit is reached + if (counter.TotalRequests > rule.Limit) + { + //compute retry after value + var retryAfter = _processor.RetryAfterFrom(counter.Timestamp, rule); + + // log blocked request + LogBlockedRequest(context, identity, counter, rule); + + // break execution + await ReturnQuotaExceededResponse(context, options, retryAfter); + return; + } + } + //set X-Rate-Limit headers for the longest period + if (!options.DisableRateLimitHeaders) + { + var headers = _processor.GetRateLimitHeaders(identity, options); + headers.Context = context; + + context.Response.OnStarting(SetRateLimitHeaders, state: headers); + } + + await _next.Invoke(context); + } + + public virtual ClientRequestIdentity SetIdentity(HttpContext httpContext, RateLimitOptions option) + { + var clientId = "client"; + if (httpContext.Request.Headers.Keys.Contains(option.ClientIdHeader)) + { + clientId = httpContext.Request.Headers[option.ClientIdHeader].First(); + } + + return new ClientRequestIdentity + { + Path = httpContext.Request.Path.ToString().ToLowerInvariant(), + HttpVerb = httpContext.Request.Method.ToLowerInvariant(), + ClientId = clientId, + }; + } + + public bool IsWhitelisted(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + if (option.ClientWhitelist != null && option.ClientWhitelist.Contains(requestIdentity.ClientId)) + { + return true; + } + + return false; + } + + public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule) + { + _logger.LogDebug($"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { DownstreamRoute.ReRoute.UpstreamTemplate }, TraceIdentifier {httpContext.TraceIdentifier}."); + } + + public virtual Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitOptions option, string retryAfter) + { + var message = string.IsNullOrEmpty(option.QuotaExceededMessage) ? $"API calls quota exceeded! maximum admitted {option.RateLimitRule.Limit} per {option.RateLimitRule.Period}." : option.QuotaExceededMessage; + + if (!option.DisableRateLimitHeaders) + { + httpContext.Response.Headers["Retry-After"] = retryAfter; + } + + httpContext.Response.StatusCode = option.HttpStatusCode; + return httpContext.Response.WriteAsync(message); + } + + private Task SetRateLimitHeaders(object rateLimitHeaders) + { + var headers = (RateLimitHeaders)rateLimitHeaders; + + headers.Context.Response.Headers["X-Rate-Limit-Limit"] = headers.Limit; + headers.Context.Response.Headers["X-Rate-Limit-Remaining"] = headers.Remaining; + headers.Context.Response.Headers["X-Rate-Limit-Reset"] = headers.Reset; + + return Task.CompletedTask; + } + + } +} + + diff --git a/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs b/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs new file mode 100644 index 00000000..b27d6d9d --- /dev/null +++ b/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Builder; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit.Middleware +{ + public static class RateLimitMiddlewareExtensions + { + public static IApplicationBuilder UseRateLimiting(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/src/Ocelot/RateLimit/RateLimitCore.cs b/src/Ocelot/RateLimit/RateLimitCore.cs new file mode 100644 index 00000000..39904b06 --- /dev/null +++ b/src/Ocelot/RateLimit/RateLimitCore.cs @@ -0,0 +1,123 @@ +using Ocelot.Configuration; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public class RateLimitCore + { + private readonly IRateLimitCounterHandler _counterHandler; + private static readonly object _processLocker = new object(); + + public RateLimitCore(IRateLimitCounterHandler counterStore) + { + _counterHandler = counterStore; + } + + public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + var counter = new RateLimitCounter + { + Timestamp = DateTime.UtcNow, + TotalRequests = 1 + }; + var rule = option.RateLimitRule; + + var counterId = ComputeCounterKey(requestIdentity, option); + + // serial reads and writes + lock (_processLocker) + { + var entry = _counterHandler.Get(counterId); + if (entry.HasValue) + { + // entry has not expired + if (entry.Value.Timestamp + rule.PeriodTimespan.Value >= DateTime.UtcNow) + { + // increment request count + var totalRequests = entry.Value.TotalRequests + 1; + + // deep copy + counter = new RateLimitCounter + { + Timestamp = entry.Value.Timestamp, + TotalRequests = totalRequests + }; + } + } + + // stores: id (string) - timestamp (datetime) - total_requests (long) + _counterHandler.Set(counterId, counter, rule.PeriodTimespan.Value); + } + + return counter; + } + + public RateLimitHeaders GetRateLimitHeaders(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + var rule = option.RateLimitRule; + var headers = new RateLimitHeaders(); + var counterId = ComputeCounterKey(requestIdentity, option); + var entry = _counterHandler.Get(counterId); + if (entry.HasValue) + { + headers.Reset = (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo); + headers.Limit = rule.Period; + headers.Remaining = (rule.Limit - entry.Value.TotalRequests).ToString(); + } + else + { + headers.Reset = (DateTime.UtcNow + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo); + headers.Limit = rule.Period; + headers.Remaining = rule.Limit.ToString(); + } + + return headers; + throw new NotImplementedException(); + } + + public string ComputeCounterKey(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + var key = $"{option.RateLimitCounterPrefix}_{requestIdentity.ClientId}_{option.RateLimitRule.Period}_{requestIdentity.HttpVerb}_{requestIdentity.Path}"; + + var idBytes = System.Text.Encoding.UTF8.GetBytes(key); + + byte[] hashBytes; + + using (var algorithm = System.Security.Cryptography.SHA1.Create()) + { + hashBytes = algorithm.ComputeHash(idBytes); + } + + return BitConverter.ToString(hashBytes).Replace("-", string.Empty); + } + + public string RetryAfterFrom(DateTime timestamp, RateLimitRule rule) + { + var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds); + var retryAfter = Convert.ToInt32(rule.PeriodTimespan.Value.TotalSeconds); + retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1; + return retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture); + } + + public TimeSpan ConvertToTimeSpan(string timeSpan) + { + var l = timeSpan.Length - 1; + var value = timeSpan.Substring(0, l); + var type = timeSpan.Substring(l, 1); + + switch (type) + { + case "d": return TimeSpan.FromDays(double.Parse(value)); + case "h": return TimeSpan.FromHours(double.Parse(value)); + case "m": return TimeSpan.FromMinutes(double.Parse(value)); + case "s": return TimeSpan.FromSeconds(double.Parse(value)); + default: throw new FormatException($"{timeSpan} can't be converted to TimeSpan, unknown type {type}"); + } + } + + } +} diff --git a/src/Ocelot/RateLimit/RateLimitCounter.cs b/src/Ocelot/RateLimit/RateLimitCounter.cs new file mode 100644 index 00000000..f0133b25 --- /dev/null +++ b/src/Ocelot/RateLimit/RateLimitCounter.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + /// + /// Stores the initial access time and the numbers of calls made from that point + /// + public struct RateLimitCounter + { + public DateTime Timestamp { get; set; } + + public long TotalRequests { get; set; } + } +} diff --git a/src/Ocelot/RateLimit/RateLimitHeaders.cs b/src/Ocelot/RateLimit/RateLimitHeaders.cs new file mode 100644 index 00000000..a7bd4ae6 --- /dev/null +++ b/src/Ocelot/RateLimit/RateLimitHeaders.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public class RateLimitHeaders + { + public HttpContext Context { get; set; } + + public string Limit { get; set; } + + public string Remaining { get; set; } + + public string Reset { get; set; } + } +} diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 9b5faa04..92ab6daf 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -11,6 +11,7 @@ using CacheManager.Core; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Ocelot.Configuration.File; @@ -100,7 +101,7 @@ namespace Ocelot.AcceptanceTests }) .WithDictionaryHandle(); }; - + s.AddMemoryCache(); s.AddOcelotOutputCaching(settings); s.AddOcelotFileConfiguration(configuration); s.AddOcelot(); diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index b155bee6..a7a48f2a 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"RateLimitRule":{"Period":null,"PeriodTimespan":0,"Limit":0},"ClientWhitelist":[],"ClientIdHeader":"ClientId","ClientPolicyPrefix":"crlp","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","EnableRateLimiting":false,"DisableRateLimitHeaders":false}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index f1aa378b..5457280f 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -33,7 +33,8 @@ "Microsoft.NETCore.App": "1.1.0", "Shouldly": "2.8.2", "TestStack.BDDfy": "4.3.2", - "Consul": "0.7.2.1" + "Consul": "0.7.2.1", + "Microsoft.Extensions.Caching.Memory": "1.1.0" }, "runtimes": { "win10-x64": {}, diff --git a/test/Ocelot.ManualTest/Startup.cs b/test/Ocelot.ManualTest/Startup.cs index 70448fcf..2d8b7653 100644 --- a/test/Ocelot.ManualTest/Startup.cs +++ b/test/Ocelot.ManualTest/Startup.cs @@ -37,7 +37,7 @@ namespace Ocelot.ManualTest }) .WithDictionaryHandle(); }; - + services.AddMemoryCache(); services.AddOcelotOutputCaching(settings); services.AddOcelotFileConfiguration(Configuration); services.AddOcelot(); diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs new file mode 100644 index 00000000..9d826305 --- /dev/null +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -0,0 +1,146 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.TestHost; +using Microsoft.AspNetCore.Http; +using Moq; +using Ocelot.Infrastructure.RequestData; +using Ocelot.RateLimit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Logging; +using System.IO; +using Ocelot.RateLimit.Middleware; +using Ocelot.DownstreamRouteFinder; +using Ocelot.Responses; +using Xunit; +using TestStack.BDDfy; +using Ocelot.Configuration.Builder; +using Shouldly; + +namespace Ocelot.UnitTests.RateLimit +{ + public class ClientRateLimitMiddlewareTests + { + private readonly Mock _scopedRepository; + private readonly Mock _counterHanlder; + private readonly string _url; + private readonly TestServer _server; + private readonly HttpClient _client; + private HttpResponseMessage _result; + private OkResponse _downstreamRoute; + private int responseStatusCode; + + public ClientRateLimitMiddlewareTests() + { + _url = "http://localhost:51879/api/ClientRateLimit"; + _scopedRepository = new Mock(); + var builder = new WebHostBuilder() + .ConfigureServices(x => + { + x.AddSingleton(); + x.AddLogging(); + x.AddMemoryCache(); + x.AddSingleton(); + x.AddSingleton(_scopedRepository.Object); + }) + .UseUrls(_url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(_url) + .Configure(app => + { + app.UseRateLimiting(); + app.Run(async context => + { + context.Response.StatusCode = 200; + await context.Response.WriteAsync("This is ratelimit test"); + }); + }); + + _server = new TestServer(builder); + _client = _server.CreateClient(); + } + + + [Fact] + public void should_call_middleware_and_ratelimiting() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List(), false, "", "", new Ocelot.Configuration.RateLimitRule() { Limit = 3, Period = "1s", PeriodTimespan = TimeSpan.FromSeconds(100) })) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenresponseStatusCodeIs429()) + .BDDfy(); + } + + [Fact] + public void should_call_middleware_withWhitelistClient() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new Ocelot.Configuration.RateLimitRule() { Limit = 3, Period = "1s", PeriodTimespan = TimeSpan.FromSeconds(100) })) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .When(x => x.WhenICallTheMiddlewareWithWhiteClient()) + .Then(x => x.ThenresponseStatusCodeIs200()) + .BDDfy(); + } + + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _downstreamRoute = new OkResponse(downstreamRoute); + _scopedRepository + .Setup(x => x.Get(It.IsAny())) + .Returns(_downstreamRoute); + } + + private void WhenICallTheMiddleware() + { + var clientId = "ocelotclient1"; + // Act + for (int i = 0; i <10; i++) + { + var request = new HttpRequestMessage(new HttpMethod("GET"), _url); + request.Headers.Add("ClientId", clientId); + + var response = _client.SendAsync(request); + responseStatusCode = (int)response.Result.StatusCode; + } + + } + + private void WhenICallTheMiddlewareWithWhiteClient() + { + var clientId = "ocelotclient2"; + // Act + for (int i = 0; i < 10; i++) + { + var request = new HttpRequestMessage(new HttpMethod("GET"), _url); + request.Headers.Add("ClientId", clientId); + + var response = _client.SendAsync(request); + responseStatusCode = (int)response.Result.StatusCode; + } + } + + private void ThenresponseStatusCodeIs429() + { + responseStatusCode.ShouldBe(429); + } + + private void ThenresponseStatusCodeIs200() + { + responseStatusCode.ShouldBe(200); + } + } +} From ee73d3897c2c4c9a2b06313458f285c606d878e2 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 11 Feb 2017 12:18:27 +0000 Subject: [PATCH 070/113] removed second publish of symols as we publish with the nuget package --- build.cake | 8 -------- 1 file changed, 8 deletions(-) diff --git a/build.cake b/build.cake index 1a1f005d..0dca21ec 100644 --- a/build.cake +++ b/build.cake @@ -318,14 +318,6 @@ private void PublishPackages(string feedApiKey, string codeFeedUrl, string symbo ApiKey = feedApiKey, Source = codeFeedUrl }); - - NuGetPush( - symbolsPackage, - new NuGetPushSettings { - ApiKey = feedApiKey, - Source = symbolFeedUrl - }); - } /// gets the resource from the specified url From 2757fe25eb3d23f678827c0fdc2adeb01347d7df Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 11 Feb 2017 12:29:07 +0000 Subject: [PATCH 071/113] made changes to only push nuget package for some reason still trying to push symbols again --- build.cake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.cake b/build.cake index 0dca21ec..93cf8428 100644 --- a/build.cake +++ b/build.cake @@ -310,7 +310,8 @@ private void PublishPackages(string feedApiKey, string codeFeedUrl, string symbo .ToDictionary(v => v[0], v => v[1]); var codePackage = packagesDir + File(artifacts["nuget"]); - var symbolsPackage = packagesDir + File(artifacts["nugetSymbols"]); + + Information("Pushing package"); NuGetPush( codePackage, From 9b06afc781bc023ea0eda3ee124255f9b0b78cbd Mon Sep 17 00:00:00 2001 From: geffzhang Date: Sat, 11 Feb 2017 20:52:20 +0800 Subject: [PATCH 072/113] refactor ratelimit config --- global.json | 2 +- .../Creator/FileOcelotConfigurationCreator.cs | 12 +++---- .../File/FileGlobalConfiguration.cs | 4 +++ .../File/FileRateLimitOptions.cs | 33 +----------------- .../Configuration/File/FileRateLimitRule.cs | 34 +++++++++++++++++++ src/Ocelot/Configuration/File/FileReRoute.cs | 4 +-- src/Ocelot/Configuration/RateLimitOptions.cs | 1 - .../Ocelot.AcceptanceTests/configuration.json | 2 +- .../ClientRateLimitMiddlewareTests.cs | 1 - 9 files changed, 49 insertions(+), 44 deletions(-) create mode 100644 src/Ocelot/Configuration/File/FileRateLimitRule.cs diff --git a/global.json b/global.json index 616b2c4e..ff8d898e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@  { "projects": [ "src", "test" ], "sdk": { - "version": "1.0.0-preview2-003131" + "version": "1.0.0-preview2-003133" } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index a7244bb1..ae8cc047 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -114,14 +114,14 @@ namespace Ocelot.Configuration.Creator RateLimitOptions rateLimitOption = null; if (enableRateLimiting) { - rateLimitOption = new RateLimitOptions(enableRateLimiting, fileReRoute.RateLimitOptions.ClientIdHeader, - fileReRoute.RateLimitOptions.ClientWhitelist,fileReRoute.RateLimitOptions.DisableRateLimitHeaders, - fileReRoute.RateLimitOptions.QuotaExceededMessage,fileReRoute.RateLimitOptions.RateLimitCounterPrefix, + rateLimitOption = new RateLimitOptions(enableRateLimiting, globalConfiguration.RateLimitOptions.ClientIdHeader, + fileReRoute.RateLimitOptions.ClientWhitelist, globalConfiguration.RateLimitOptions.DisableRateLimitHeaders, + globalConfiguration.RateLimitOptions.QuotaExceededMessage, globalConfiguration.RateLimitOptions.RateLimitCounterPrefix, new RateLimitRule() { - Limit = fileReRoute.RateLimitOptions.RateLimitRule.Limit, - Period = fileReRoute.RateLimitOptions.RateLimitRule.Period, - PeriodTimespan = TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.RateLimitRule.PeriodTimespan) + Limit = fileReRoute.RateLimitOptions.Limit, + Period = fileReRoute.RateLimitOptions.Period, + PeriodTimespan = TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan) }); } var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs index f414bc83..2264bee9 100644 --- a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs @@ -5,8 +5,12 @@ public FileGlobalConfiguration() { ServiceDiscoveryProvider = new FileServiceDiscoveryProvider(); + RateLimitOptions = new FileRateLimitOptions(); } public string RequestIdKey { get; set; } + public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;} + + public FileRateLimitOptions RateLimitOptions { get; set; } } } diff --git a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs index bc555f33..e685ee56 100644 --- a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs +++ b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs @@ -7,26 +7,12 @@ namespace Ocelot.Configuration.File { public class FileRateLimitOptions { - public FileRateLimitOptions() - { - RateLimitRule = new FileRateLimitRule(); - ClientWhitelist = new List(); - } - - public FileRateLimitRule RateLimitRule { get; set; } - - public List ClientWhitelist { get; set; } /// /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId /// public string ClientIdHeader { get; set; } = "ClientId"; - /// - /// Gets or sets the policy prefix, used to compose the client policy cache key - /// - public string ClientPolicyPrefix { get; set; } = "crlp"; - /// /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. /// If none specified the default will be: @@ -39,28 +25,11 @@ namespace Ocelot.Configuration.File /// public string RateLimitCounterPrefix { get; set; } = "ocelot"; - /// - /// Enables endpoint rate limiting based URL path and HTTP verb - /// - public bool EnableRateLimiting { get; set; } - /// /// Disables X-Rate-Limit and Rety-After headers /// public bool DisableRateLimitHeaders { get; set; } } - public class FileRateLimitRule - { - /// - /// Rate limit period as in 1s, 1m, 1h - /// - public string Period { get; set; } - - public int PeriodTimespan { get; set; } - /// - /// Maximum number of requests that a client can make in a defined period - /// - public long Limit { get; set; } - } + } diff --git a/src/Ocelot/Configuration/File/FileRateLimitRule.cs b/src/Ocelot/Configuration/File/FileRateLimitRule.cs new file mode 100644 index 00000000..58d171de --- /dev/null +++ b/src/Ocelot/Configuration/File/FileRateLimitRule.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Configuration.File +{ + + public class FileRateLimitRule + { + public FileRateLimitRule() + { + ClientWhitelist = new List(); + } + + public List ClientWhitelist { get; set; } + + /// + /// Enables endpoint rate limiting based URL path and HTTP verb + /// + public bool EnableRateLimiting { get; set; } + + /// + /// Rate limit period as in 1s, 1m, 1h + /// + public string Period { get; set; } + + public int PeriodTimespan { get; set; } + /// + /// Maximum number of requests that a client can make in a defined period + /// + public long Limit { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 69b13e6a..f3658cbb 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -13,7 +13,7 @@ namespace Ocelot.Configuration.File AuthenticationOptions = new FileAuthenticationOptions(); FileCacheOptions = new FileCacheOptions(); QoSOptions = new FileQoSOptions(); - RateLimitOptions = new FileRateLimitOptions(); + RateLimitOptions = new FileRateLimitRule(); } public string DownstreamPathTemplate { get; set; } @@ -33,6 +33,6 @@ namespace Ocelot.Configuration.File public int DownstreamPort { get; set; } public FileQoSOptions QoSOptions { get; set; } public string LoadBalancer {get;set;} - public FileRateLimitOptions RateLimitOptions { get; set; } + public FileRateLimitRule RateLimitOptions { get; set; } } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/RateLimitOptions.cs b/src/Ocelot/Configuration/RateLimitOptions.cs index 51765874..7d4d0f4a 100644 --- a/src/Ocelot/Configuration/RateLimitOptions.cs +++ b/src/Ocelot/Configuration/RateLimitOptions.cs @@ -22,7 +22,6 @@ namespace Ocelot.Configuration RateLimitRule = rateLimitRule; } - public RateLimitRule RateLimitRule { get; private set; } public List ClientWhitelist { get; private set; } diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index a7a48f2a..8626f7c1 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"RateLimitRule":{"Period":null,"PeriodTimespan":0,"Limit":0},"ClientWhitelist":[],"ClientIdHeader":"ClientId","ClientPolicyPrefix":"crlp","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","EnableRateLimiting":false,"DisableRateLimitHeaders":false}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false}}} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index 9d826305..dd1dc0e0 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -26,7 +26,6 @@ namespace Ocelot.UnitTests.RateLimit public class ClientRateLimitMiddlewareTests { private readonly Mock _scopedRepository; - private readonly Mock _counterHanlder; private readonly string _url; private readonly TestServer _server; private readonly HttpClient _client; From ce8da4c92d55f53ad4ed9204279003ef2b968291 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 11 Feb 2017 13:14:25 +0000 Subject: [PATCH 073/113] removed qos by accident, added back in --- src/Ocelot/Configuration/Builder/ReRouteBuilder.cs | 7 ------- .../Creator/FileOcelotConfigurationCreator.cs | 4 +++- .../Configuration/FileConfigurationCreatorTests.cs | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 42e4e5bf..382cf374 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -22,7 +22,6 @@ namespace Ocelot.Configuration.Builder private string _requestIdHeaderKey; private bool _isCached; private CacheOptions _fileCacheOptions; - private string _serviceName; private string _downstreamScheme; private string _downstreamHost; private int _downstreamPort; @@ -49,12 +48,6 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithServiceName(string serviceName) - { - _serviceName = serviceName; - return this; - } - public ReRouteBuilder WithDownstreamPathTemplate(string input) { _downstreamPathTemplate = input; diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 740a2b95..25888681 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -95,6 +95,7 @@ namespace Ocelot.Configuration.Creator var loadBalancerKey = BuildLoadBalancerKey(fileReRoute); var upstreamTemplatePattern = BuildUpstreamTemplate(fileReRoute); + var isQos = fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions.TimeoutValue >0; var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration); @@ -103,7 +104,6 @@ namespace Ocelot.Configuration.Creator var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest); - var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest); var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest); @@ -129,6 +129,8 @@ namespace Ocelot.Configuration.Creator .WithDownstreamPort(fileReRoute.DownstreamPort) .WithLoadBalancerKey(loadBalancerKey) .WithServiceProviderConfiguraion(serviceProviderConfiguration) + .WithIsQos(isQos) + .WithQosOptions(new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue)) .Build(); await SetupLoadBalancer(reRoute); diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 2d2a48ef..5080c9e0 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -164,11 +164,11 @@ namespace Ocelot.UnitTests.Configuration .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") - .WithServiceName("ProductService") .WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder() .WithUseServiceDiscovery(true) .WithServiceDiscoveryProvider("consul") .WithServiceDiscoveryProviderHost("127.0.0.1") + .WithServiceName("ProductService") .Build()) .Build() })) From 820673dda8b25ecb4ae3098e3bef5600d51fec93 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 11 Feb 2017 15:11:10 +0000 Subject: [PATCH 074/113] added docs but qos acceptance test not working seems circuit never opens but not sure if it is meant to with timeouts..investigating --- README.md | 40 ++++-- src/Ocelot/Configuration/QoSOptions.cs | 6 +- src/Ocelot/Errors/OcelotErrorCode.cs | 3 +- .../CircuitBreakingDelegatingHandler.cs | 27 ++-- .../Requester/HttpClientHttpRequester.cs | 12 +- src/Ocelot/Requester/RequestTimedOutError.cs | 13 ++ .../Responder/ErrorsToHttpStatusCodeMapper.cs | 5 + src/Ocelot/Responses/ErrorResponseGeneric.cs | 8 +- test/Ocelot.AcceptanceTests/QoSTests.cs | 136 ++++++++++++++++++ .../ErrorsToHttpStatusCodeMapperTests.cs | 16 ++- 10 files changed, 235 insertions(+), 31 deletions(-) create mode 100644 src/Ocelot/Requester/RequestTimedOutError.cs create mode 100644 test/Ocelot.AcceptanceTests/QoSTests.cs diff --git a/README.md b/README.md index 3ef6d6b6..dfbdfd1e 100644 --- a/README.md +++ b/README.md @@ -305,19 +305,25 @@ Below is an example configuration that will transforms claims to query string pa 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. -## Logging +## Quality of Service -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. +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). -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. +Add the following section to a ReRoute configuration. -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. + "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 @@ -404,6 +410,20 @@ 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... diff --git a/src/Ocelot/Configuration/QoSOptions.cs b/src/Ocelot/Configuration/QoSOptions.cs index 8584c57e..9deaaeda 100644 --- a/src/Ocelot/Configuration/QoSOptions.cs +++ b/src/Ocelot/Configuration/QoSOptions.cs @@ -8,7 +8,11 @@ namespace Ocelot.Configuration { public class QoSOptions { - public QoSOptions(int exceptionsAllowedBeforeBreaking, int durationofBreak, int timeoutValue, TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) + public QoSOptions( + int exceptionsAllowedBeforeBreaking, + int durationofBreak, + int timeoutValue, + TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) { ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak); diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index d24988b9..e2fe4ada 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -25,6 +25,7 @@ ServicesAreNullError, ServicesAreEmptyError, UnableToFindServiceDiscoveryProviderError, - UnableToFindLoadBalancerError + UnableToFindLoadBalancerError, + RequestTimedOutError } } diff --git a/src/Ocelot/Requester/CircuitBreakingDelegatingHandler.cs b/src/Ocelot/Requester/CircuitBreakingDelegatingHandler.cs index 4adc0eeb..b4091296 100644 --- a/src/Ocelot/Requester/CircuitBreakingDelegatingHandler.cs +++ b/src/Ocelot/Requester/CircuitBreakingDelegatingHandler.cs @@ -18,11 +18,17 @@ namespace Ocelot.Requester private readonly Policy _circuitBreakerPolicy; private readonly TimeoutPolicy _timeoutPolicy; - public CircuitBreakingDelegatingHandler(int exceptionsAllowedBeforeBreaking, TimeSpan durationOfBreak,TimeSpan timeoutValue - ,TimeoutStrategy timeoutStrategy, IOcelotLogger logger, HttpMessageHandler innerHandler) + public CircuitBreakingDelegatingHandler( + int exceptionsAllowedBeforeBreaking, + TimeSpan durationOfBreak, + TimeSpan timeoutValue, + TimeoutStrategy timeoutStrategy, + IOcelotLogger logger, + HttpMessageHandler innerHandler) : base(innerHandler) { this._exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; + this._durationOfBreak = durationOfBreak; _circuitBreakerPolicy = Policy @@ -39,30 +45,27 @@ namespace Ocelot.Requester onReset: () => _logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again."), onHalfOpen: () => _logger.LogDebug(".Breaker logging: Half-open; next call is a trial.") ); + _timeoutPolicy = Policy.TimeoutAsync(timeoutValue, timeoutStrategy); + _logger = logger; } - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { - Task responseTask = null; - try { - responseTask = Policy.WrapAsync(_circuitBreakerPolicy, _timeoutPolicy).ExecuteAsync(() => - { - return base.SendAsync(request,cancellationToken); - }); - return responseTask; + return await Policy.WrapAsync(_circuitBreakerPolicy, _timeoutPolicy).ExecuteAsync(() => base.SendAsync(request,cancellationToken)); } catch (BrokenCircuitException ex) { _logger.LogError($"Reached to allowed number of exceptions. Circuit is open. AllowedExceptionCount: {_exceptionsAllowedBeforeBreaking}, DurationOfBreak: {_durationOfBreak}",ex); throw; } - catch (HttpRequestException) + catch (HttpRequestException ex) { - return responseTask; + _logger.LogError($"Error in CircuitBreakingDelegatingHandler.SendAync", ex); + throw; } } diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index 77ab90cc..cc5332f3 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -27,6 +27,7 @@ namespace Ocelot.Requester { builder.WithCircuitBreaker(request.Qos, _logger, handler); } + using (var httpClient = builder.Build(handler)) { try @@ -34,13 +35,14 @@ namespace Ocelot.Requester var response = await httpClient.SendAsync(request.HttpRequestMessage); return new OkResponse(response); } - catch (Exception exception) + catch (Polly.Timeout.TimeoutRejectedException exception) { return - new ErrorResponse(new List - { - new UnableToCompleteRequestError(exception) - }); + new ErrorResponse(new RequestTimedOutError(exception)); + } + catch (Exception exception) + { + return new ErrorResponse(new UnableToCompleteRequestError(exception)); } } } diff --git a/src/Ocelot/Requester/RequestTimedOutError.cs b/src/Ocelot/Requester/RequestTimedOutError.cs new file mode 100644 index 00000000..38afce1a --- /dev/null +++ b/src/Ocelot/Requester/RequestTimedOutError.cs @@ -0,0 +1,13 @@ +using System; +using Ocelot.Errors; + +namespace Ocelot.Requester +{ + public class RequestTimedOutError : Error + { + public RequestTimedOutError(Exception exception) + : base($"Timeout making http request, exception: {exception.Message}", OcelotErrorCode.RequestTimedOutError) + { + } + } +} diff --git a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs index 6ecc20eb..b9fcb299 100644 --- a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs +++ b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs @@ -22,6 +22,11 @@ namespace Ocelot.Responder return new OkResponse(403); } + if (errors.Any(e => e.Code == OcelotErrorCode.RequestTimedOutError)) + { + return new OkResponse(408); + } + return new OkResponse(404); } } diff --git a/src/Ocelot/Responses/ErrorResponseGeneric.cs b/src/Ocelot/Responses/ErrorResponseGeneric.cs index 77230503..044d6f61 100644 --- a/src/Ocelot/Responses/ErrorResponseGeneric.cs +++ b/src/Ocelot/Responses/ErrorResponseGeneric.cs @@ -5,7 +5,13 @@ namespace Ocelot.Responses { public class ErrorResponse : Response { - public ErrorResponse(List errors) : base(errors) + public ErrorResponse(Error error) + : base(new List {error}) + { + + } + public ErrorResponse(List errors) + : base(errors) { } } diff --git a/test/Ocelot.AcceptanceTests/QoSTests.cs b/test/Ocelot.AcceptanceTests/QoSTests.cs new file mode 100644 index 00000000..50c1fb66 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/QoSTests.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.File; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class QoSTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + private int _requestCount; + + public QoSTests() + { + _steps = new Steps(); + } + + + [Fact] + public void should_open_circuit_breaker_then_close() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = "Get", + QoSOptions = new FileQoSOptions + { + ExceptionsAllowedBeforeBreaking = 1, + TimeoutValue = 500, + DurationOfBreak = 1000 + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "Hello from Laura")) + .Given(x => _steps.GivenThereIsAConfiguration(configuration)) + .Given(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.RequestTimeout)) + .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.RequestTimeout)) + .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.RequestTimeout)) + .Given(x => x.GivenIWaitMilliSeconds(2000)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + private void GivenIWaitMilliSeconds(int ms) + { + Thread.Sleep(ms); + } + + private void GivenThereIsAServiceRunningOn(string url, string responseBody) + { + _builder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + //circuit starts closed + if (_requestCount == 0) + { + _requestCount++; + context.Response.StatusCode = 200; + await context.Response.WriteAsync(responseBody); + return; + } + + //request one times out and polly throws exception + if (_requestCount == 1) + { + _requestCount++; + await Task.Delay(1000); + context.Response.StatusCode = 200; + return; + } + + //request two times out and polly throws exception circuit opens + if (_requestCount == 2) + { + _requestCount++; + await Task.Delay(1000); + context.Response.StatusCode = 200; + return; + } + + //after break closes we return 200 OK + if (_requestCount == 3) + { + context.Response.StatusCode = 200; + await context.Response.WriteAsync(responseBody); + return; + } + }); + }) + .Build(); + + _builder.Start(); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs index cb8198dc..d2b7e386 100644 --- a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs +++ b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Ocelot.Errors; using Ocelot.Middleware; +using Ocelot.Requester; using Ocelot.Responder; using Ocelot.Responses; using Shouldly; @@ -20,6 +22,18 @@ namespace Ocelot.UnitTests.Responder _codeMapper = new ErrorsToHttpStatusCodeMapper(); } + [Fact] + public void should_return_timeout() + { + this.Given(x => x.GivenThereAreErrors(new List + { + new RequestTimedOutError(new Exception()) + })) + .When(x => x.WhenIGetErrorStatusCode()) + .Then(x => x.ThenTheResponseIsStatusCodeIs(408)) + .BDDfy(); + } + [Fact] public void should_create_unauthenticated_response_code() { From 286c7f848813d58d4cc7ca3ee386c8e1af5a29d9 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 11 Feb 2017 18:56:36 +0000 Subject: [PATCH 075/113] Fixed issue where qos was being created for each request so circuit breaker was never stopping traffic going to downstream service. --- .../Builder/QoSOptionsBuilder.cs | 34 ++++ .../Creator/FileOcelotConfigurationCreator.cs | 58 +++++-- src/Ocelot/Configuration/QoSOptions.cs | 7 +- src/Ocelot/Configuration/ReRoute.cs | 6 +- .../ServiceCollectionExtensions.cs | 3 + .../Finder/DownstreamRouteFinder.cs | 7 + src/Ocelot/Errors/OcelotErrorCode.cs | 3 +- .../Middleware/LoadBalancingMiddleware.cs | 3 +- .../Request/Builder/HttpRequestCreator.cs | 5 +- src/Ocelot/Request/Builder/IRequestCreator.cs | 4 +- src/Ocelot/Request/Builder/RequestBuilder.cs | 10 +- .../HttpRequestBuilderMiddleware.cs | 33 +++- src/Ocelot/Request/Request.cs | 11 +- .../CircuitBreakingDelegatingHandler.cs | 77 ---------- src/Ocelot/Requester/HttpClientBuilder.cs | 26 ++-- .../Requester/HttpClientHttpRequester.cs | 21 ++- .../PollyCircuitBreakingDelegatingHandler.cs | 47 ++++++ .../Requester/QoS/IQoSProviderFactory.cs | 145 ++++++++++++++++++ .../QoS/UnableToFindQoSProviderError.cs | 16 ++ .../Responder/ErrorsToHttpStatusCodeMapper.cs | 2 +- test/Ocelot.AcceptanceTests/QoSTests.cs | 116 +++++++++++--- .../FileConfigurationCreatorTests.cs | 62 +++++++- .../DownstreamRouteFinderTests.cs | 41 ++++- .../UrlMatcher/RegExUrlMatcherTests.cs | 20 +++ .../HttpRequestBuilderMiddlewareTests.cs | 17 +- .../Request/RequestBuilderTests.cs | 27 ++-- .../Requester/HttpRequesterMiddlewareTests.cs | 3 +- .../Requester/QoSProviderFactoryTests.cs | 80 ++++++++++ .../Requester/QosProviderHouseTests.cs | 117 ++++++++++++++ .../ErrorsToHttpStatusCodeMapperTests.cs | 2 +- 30 files changed, 819 insertions(+), 184 deletions(-) create mode 100644 src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs delete mode 100644 src/Ocelot/Requester/CircuitBreakingDelegatingHandler.cs create mode 100644 src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs create mode 100644 src/Ocelot/Requester/QoS/IQoSProviderFactory.cs create mode 100644 src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs create mode 100644 test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs create mode 100644 test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs diff --git a/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs new file mode 100644 index 00000000..8d953d5c --- /dev/null +++ b/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs @@ -0,0 +1,34 @@ +namespace Ocelot.Configuration.Builder +{ + public class QoSOptionsBuilder + { + private int _exceptionsAllowedBeforeBreaking; + + private int _durationOfBreak; + + private int _timeoutValue; + + public QoSOptionsBuilder WithExceptionsAllowedBeforeBreaking(int exceptionsAllowedBeforeBreaking) + { + _exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; + return this; + } + + public QoSOptionsBuilder WithDurationOfBreak(int durationOfBreak) + { + _durationOfBreak = durationOfBreak; + return this; + } + + public QoSOptionsBuilder WithTimeoutValue(int timeoutValue) + { + _timeoutValue = timeoutValue; + return this; + } + + public QoSOptions Build() + { + return new QoSOptions(_exceptionsAllowedBeforeBreaking, _durationOfBreak, _timeoutValue); + } + } +} diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 25888681..380e193f 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Net.Http; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -10,9 +9,9 @@ using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Requester.QoS; using Ocelot.Responses; using Ocelot.Utilities; -using Ocelot.Values; namespace Ocelot.Configuration.Creator { @@ -26,11 +25,14 @@ namespace Ocelot.Configuration.Creator 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; public FileOcelotConfigurationCreator( IOptions options, @@ -38,10 +40,14 @@ namespace Ocelot.Configuration.Creator IClaimToThingConfigurationParser claimToThingConfigurationParser, ILogger logger, ILoadBalancerFactory loadBalancerFactory, - ILoadBalancerHouse loadBalancerHouse) + ILoadBalancerHouse loadBalancerHouse, + IQoSProviderFactory qoSProviderFactory, + IQosProviderHouse qosProviderHouse) { _loadBalanceFactory = loadBalancerFactory; _loadBalancerHouse = loadBalancerHouse; + _qoSProviderFactory = qoSProviderFactory; + _qosProviderHouse = qosProviderHouse; _options = options; _configurationValidator = configurationValidator; _claimToThingConfigurationParser = claimToThingConfigurationParser; @@ -86,17 +92,17 @@ namespace Ocelot.Configuration.Creator { var isAuthenticated = IsAuthenticated(fileReRoute); - var isAuthorised = IsAuthenticated(fileReRoute); + var isAuthorised = IsAuthorised(fileReRoute); var isCached = IsCached(fileReRoute); var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration); - var loadBalancerKey = BuildLoadBalancerKey(fileReRoute); + var reRouteKey = BuildReRouteKey(fileReRoute); - var upstreamTemplatePattern = BuildUpstreamTemplate(fileReRoute); + var upstreamTemplatePattern = BuildUpstreamTemplatePattern(fileReRoute); - var isQos = fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions.TimeoutValue >0; + var isQos = IsQoS(fileReRoute); var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration); @@ -108,6 +114,8 @@ namespace Ocelot.Configuration.Creator var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest); + var qosOptions = BuildQoSOptions(fileReRoute); + var reRoute = new ReRouteBuilder() .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) @@ -127,16 +135,31 @@ namespace Ocelot.Configuration.Creator .WithLoadBalancer(fileReRoute.LoadBalancer) .WithDownstreamHost(fileReRoute.DownstreamHost) .WithDownstreamPort(fileReRoute.DownstreamPort) - .WithLoadBalancerKey(loadBalancerKey) + .WithLoadBalancerKey(reRouteKey) .WithServiceProviderConfiguraion(serviceProviderConfiguration) .WithIsQos(isQos) - .WithQosOptions(new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue)) + .WithQosOptions(qosOptions) .Build(); await SetupLoadBalancer(reRoute); + SetupQosProvider(reRoute); return reRoute; } + 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; + } + private bool IsAuthenticated(FileReRoute fileReRoute) { return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); @@ -163,7 +186,7 @@ namespace Ocelot.Configuration.Creator return requestIdKey; } - private string BuildLoadBalancerKey(FileReRoute fileReRoute) + 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 var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}"; @@ -185,7 +208,13 @@ namespace Ocelot.Configuration.Creator private async Task SetupLoadBalancer(ReRoute reRoute) { var loadBalancer = await _loadBalanceFactory.Get(reRoute); - _loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer); + _loadBalancerHouse.Add(reRoute.ReRouteKey, loadBalancer); + } + + private void SetupQosProvider(ReRoute reRoute) + { + var loadBalancer = _qoSProviderFactory.Get(reRoute); + _qosProviderHouse.Add(reRoute.ReRouteKey, loadBalancer); } private ServiceProviderConfiguraion BuildServiceProviderConfiguration(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) @@ -206,7 +235,7 @@ namespace Ocelot.Configuration.Creator .Build(); } - private string BuildUpstreamTemplate(FileReRoute reRoute) + private string BuildUpstreamTemplatePattern(FileReRoute reRoute) { var upstreamTemplate = reRoute.UpstreamPathTemplate; @@ -230,6 +259,11 @@ namespace Ocelot.Configuration.Creator upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything); } + if (upstreamTemplate == "/") + { + return RegExForwardSlashOnly; + } + var route = reRoute.ReRouteIsCaseSensitive ? $"{upstreamTemplate}{RegExMatchEndString}" : $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; diff --git a/src/Ocelot/Configuration/QoSOptions.cs b/src/Ocelot/Configuration/QoSOptions.cs index 9deaaeda..29145888 100644 --- a/src/Ocelot/Configuration/QoSOptions.cs +++ b/src/Ocelot/Configuration/QoSOptions.cs @@ -1,8 +1,5 @@ -using Polly.Timeout; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System; +using Polly.Timeout; namespace Ocelot.Configuration { diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 45b31d38..ab9405d0 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -25,12 +25,12 @@ namespace Ocelot.Configuration string loadBalancer, string downstreamHost, int downstreamPort, - string loadBalancerKey, + string reRouteKey, ServiceProviderConfiguraion serviceProviderConfiguraion, bool isQos, QoSOptions qos) { - LoadBalancerKey = loadBalancerKey; + ReRouteKey = reRouteKey; ServiceProviderConfiguraion = serviceProviderConfiguraion; LoadBalancer = loadBalancer; DownstreamHost = downstreamHost; @@ -57,7 +57,7 @@ namespace Ocelot.Configuration QosOptions = qos; } - public string LoadBalancerKey {get;private set;} + public string ReRouteKey {get;private set;} public PathTemplate DownstreamPathTemplate { get; private set; } public PathTemplate UpstreamPathTemplate { get; private set; } public string UpstreamTemplatePattern { get; private set; } diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 0a6bd42c..e9be9841 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -28,6 +28,7 @@ using Ocelot.Logging; using Ocelot.QueryStrings; using Ocelot.Request.Builder; using Ocelot.Requester; +using Ocelot.Requester.QoS; using Ocelot.Responder; using Ocelot.ServiceDiscovery; @@ -61,6 +62,8 @@ namespace Ocelot.DependencyInjection { services.AddMvcCore().AddJsonFormatters(); services.AddLogging(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index e504a430..8b6447c7 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -30,6 +30,13 @@ namespace Ocelot.DownstreamRouteFinder.Finder foreach (var reRoute in applicableReRoutes) { + if (upstreamUrlPath == reRoute.UpstreamTemplatePattern) + { + var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamPathTemplate.Value); + + return new OkResponse(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute)); + } + var urlMatch = _urlMatcher.Match(upstreamUrlPath, reRoute.UpstreamTemplatePattern); if (urlMatch.Data.Match) diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index e2fe4ada..373ac441 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -26,6 +26,7 @@ ServicesAreEmptyError, UnableToFindServiceDiscoveryProviderError, UnableToFindLoadBalancerError, - RequestTimedOutError + RequestTimedOutError, + UnableToFindQoSProviderError } } diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index ce37f828..8e26cbf2 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -6,7 +6,6 @@ using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Logging; using Ocelot.Middleware; using Ocelot.QueryStrings.Middleware; -using Ocelot.ServiceDiscovery; namespace Ocelot.LoadBalancer.Middleware { @@ -31,7 +30,7 @@ namespace Ocelot.LoadBalancer.Middleware { _logger.LogDebug("started calling load balancing middleware"); - var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.LoadBalancerKey); + var loadBalancer = _loadBalancerHouse.Get(DownstreamRoute.ReRoute.ReRouteKey); if(loadBalancer.IsError) { SetPipelineError(loadBalancer.Errors); diff --git a/src/Ocelot/Request/Builder/HttpRequestCreator.cs b/src/Ocelot/Request/Builder/HttpRequestCreator.cs index f326933c..3264db59 100644 --- a/src/Ocelot/Request/Builder/HttpRequestCreator.cs +++ b/src/Ocelot/Request/Builder/HttpRequestCreator.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Ocelot.Responses; using Ocelot.Configuration; +using Ocelot.Requester.QoS; namespace Ocelot.Request.Builder { @@ -18,7 +19,7 @@ namespace Ocelot.Request.Builder string contentType, RequestId.RequestId requestId, bool isQos, - QoSOptions qos) + IQoSProvider qosProvider) { var request = await new RequestBuilder() .WithHttpMethod(httpMethod) @@ -30,7 +31,7 @@ namespace Ocelot.Request.Builder .WithRequestId(requestId) .WithCookies(cookies) .WithIsQos(isQos) - .WithQos(qos) + .WithQos(qosProvider) .Build(); return new OkResponse(request); diff --git a/src/Ocelot/Request/Builder/IRequestCreator.cs b/src/Ocelot/Request/Builder/IRequestCreator.cs index 3417d933..7db999b1 100644 --- a/src/Ocelot/Request/Builder/IRequestCreator.cs +++ b/src/Ocelot/Request/Builder/IRequestCreator.cs @@ -1,8 +1,8 @@ using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Ocelot.Requester.QoS; using Ocelot.Responses; -using Ocelot.Configuration; namespace Ocelot.Request.Builder { @@ -17,6 +17,6 @@ namespace Ocelot.Request.Builder string contentType, RequestId.RequestId requestId, bool isQos, - QoSOptions qos); + IQoSProvider qosProvider); } } diff --git a/src/Ocelot/Request/Builder/RequestBuilder.cs b/src/Ocelot/Request/Builder/RequestBuilder.cs index e0201bc8..ea6511ce 100644 --- a/src/Ocelot/Request/Builder/RequestBuilder.cs +++ b/src/Ocelot/Request/Builder/RequestBuilder.cs @@ -8,7 +8,7 @@ using System.Net.Http.Headers; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; -using Ocelot.Configuration; +using Ocelot.Requester.QoS; namespace Ocelot.Request.Builder { @@ -24,7 +24,7 @@ namespace Ocelot.Request.Builder private IRequestCookieCollection _cookies; private readonly string[] _unsupportedHeaders = {"host"}; private bool _isQos; - private QoSOptions _qos; + private IQoSProvider _qoSProvider; public RequestBuilder WithHttpMethod(string httpMethod) { @@ -80,9 +80,9 @@ namespace Ocelot.Request.Builder return this; } - public RequestBuilder WithQos(QoSOptions qos) + public RequestBuilder WithQos(IQoSProvider qoSProvider) { - _qos = qos; + _qoSProvider = qoSProvider; return this; } @@ -105,7 +105,7 @@ namespace Ocelot.Request.Builder var cookieContainer = CreateCookieContainer(uri); - return new Request(httpRequestMessage, cookieContainer,_isQos,_qos); + return new Request(httpRequestMessage, cookieContainer,_isQos, _qoSProvider); } private Uri CreateUri() diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs index aafa2f3d..991e84da 100644 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs +++ b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddleware.cs @@ -1,10 +1,10 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; using Ocelot.Request.Builder; +using Ocelot.Requester.QoS; namespace Ocelot.Request.Middleware { @@ -13,15 +13,18 @@ namespace Ocelot.Request.Middleware private readonly RequestDelegate _next; private readonly IRequestCreator _requestCreator; private readonly IOcelotLogger _logger; + private readonly IQosProviderHouse _qosProviderHouse; public HttpRequestBuilderMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, IRequestScopedDataRepository requestScopedDataRepository, - IRequestCreator requestCreator) + IRequestCreator requestCreator, + IQosProviderHouse qosProviderHouse) :base(requestScopedDataRepository) { _next = next; _requestCreator = requestCreator; + _qosProviderHouse = qosProviderHouse; _logger = loggerFactory.CreateLogger(); } @@ -29,17 +32,35 @@ namespace Ocelot.Request.Middleware { _logger.LogDebug("started calling request builder middleware"); + var qosProvider = _qosProviderHouse.Get(DownstreamRoute.ReRoute.ReRouteKey); + + if (qosProvider.IsError) + { + _logger.LogDebug("IQosProviderHouse returned an error, setting pipeline error"); + + SetPipelineError(qosProvider.Errors); + + return; + } + var buildResult = await _requestCreator - .Build(context.Request.Method, DownstreamUrl, context.Request.Body, - context.Request.Headers, context.Request.Cookies, context.Request.QueryString, - context.Request.ContentType, new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier), - DownstreamRoute.ReRoute.IsQos,DownstreamRoute.ReRoute.QosOptions); + .Build(context.Request.Method, + DownstreamUrl, + context.Request.Body, + context.Request.Headers, + context.Request.Cookies, + context.Request.QueryString, + context.Request.ContentType, + new RequestId.RequestId(DownstreamRoute?.ReRoute?.RequestIdKey, context.TraceIdentifier), + DownstreamRoute.ReRoute.IsQos, + qosProvider.Data); if (buildResult.IsError) { _logger.LogDebug("IRequestCreator returned an error, setting pipeline error"); SetPipelineError(buildResult.Errors); + return; } _logger.LogDebug("setting upstream request"); diff --git a/src/Ocelot/Request/Request.cs b/src/Ocelot/Request/Request.cs index a1e62834..680ab757 100644 --- a/src/Ocelot/Request/Request.cs +++ b/src/Ocelot/Request/Request.cs @@ -2,22 +2,27 @@ using Ocelot.Values; using System.Net; using System.Net.Http; +using Ocelot.Requester.QoS; namespace Ocelot.Request { public class Request { - public Request(HttpRequestMessage httpRequestMessage, CookieContainer cookieContainer,bool isQos, QoSOptions qos) + public Request( + HttpRequestMessage httpRequestMessage, + CookieContainer cookieContainer, + bool isQos, + IQoSProvider qosProvider) { HttpRequestMessage = httpRequestMessage; CookieContainer = cookieContainer; IsQos = isQos; - Qos = qos; + QosProvider = qosProvider; } public HttpRequestMessage HttpRequestMessage { get; private set; } public CookieContainer CookieContainer { get; private set; } public bool IsQos { get; private set; } - public QoSOptions Qos { get; private set; } + public IQoSProvider QosProvider { get; private set; } } } diff --git a/src/Ocelot/Requester/CircuitBreakingDelegatingHandler.cs b/src/Ocelot/Requester/CircuitBreakingDelegatingHandler.cs deleted file mode 100644 index b4091296..00000000 --- a/src/Ocelot/Requester/CircuitBreakingDelegatingHandler.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Ocelot.Logging; -using Polly; -using Polly.CircuitBreaker; -using Polly.Timeout; -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace Ocelot.Requester -{ - public class CircuitBreakingDelegatingHandler : DelegatingHandler - { - private readonly IOcelotLogger _logger; - private readonly int _exceptionsAllowedBeforeBreaking; - private readonly TimeSpan _durationOfBreak; - private readonly Policy _circuitBreakerPolicy; - private readonly TimeoutPolicy _timeoutPolicy; - - public CircuitBreakingDelegatingHandler( - int exceptionsAllowedBeforeBreaking, - TimeSpan durationOfBreak, - TimeSpan timeoutValue, - TimeoutStrategy timeoutStrategy, - IOcelotLogger logger, - HttpMessageHandler innerHandler) - : base(innerHandler) - { - this._exceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; - - this._durationOfBreak = durationOfBreak; - - _circuitBreakerPolicy = Policy - .Handle() - .Or() - .Or() - .CircuitBreakerAsync( - exceptionsAllowedBeforeBreaking: exceptionsAllowedBeforeBreaking, - durationOfBreak: durationOfBreak, - onBreak: (ex, breakDelay) => - { - _logger.LogError(".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!", ex); - }, - onReset: () => _logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again."), - onHalfOpen: () => _logger.LogDebug(".Breaker logging: Half-open; next call is a trial.") - ); - - _timeoutPolicy = Policy.TimeoutAsync(timeoutValue, timeoutStrategy); - - _logger = logger; - } - - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - try - { - return await Policy.WrapAsync(_circuitBreakerPolicy, _timeoutPolicy).ExecuteAsync(() => base.SendAsync(request,cancellationToken)); - } - catch (BrokenCircuitException ex) - { - _logger.LogError($"Reached to allowed number of exceptions. Circuit is open. AllowedExceptionCount: {_exceptionsAllowedBeforeBreaking}, DurationOfBreak: {_durationOfBreak}",ex); - throw; - } - catch (HttpRequestException ex) - { - _logger.LogError($"Error in CircuitBreakingDelegatingHandler.SendAync", ex); - throw; - } - } - - private static bool IsTransientFailure(HttpResponseMessage result) - { - return result.StatusCode >= HttpStatusCode.InternalServerError; - } - } -} diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index c3c98ac0..da37ee02 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -1,35 +1,39 @@ -using Ocelot.Configuration; -using Ocelot.Logging; -using Ocelot.Values; -using Polly.Timeout; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Threading.Tasks; +using Ocelot.Logging; +using Ocelot.Requester.QoS; namespace Ocelot.Requester { internal class HttpClientBuilder { - private readonly Dictionary> handlers = new Dictionary>(); + private readonly Dictionary> _handlers = new Dictionary>(); - public HttpClientBuilder WithCircuitBreaker(QoSOptions qos, IOcelotLogger logger, HttpMessageHandler innerHandler) + public HttpClientBuilder WithQoS(IQoSProvider qoSProvider, IOcelotLogger logger, HttpMessageHandler innerHandler) { - handlers.Add(5000, () => new CircuitBreakingDelegatingHandler(qos.ExceptionsAllowedBeforeBreaking, qos.DurationOfBreak, qos.TimeoutValue, qos.TimeoutStrategy, logger, innerHandler)); + _handlers.Add(5000, () => new PollyCircuitBreakingDelegatingHandler(qoSProvider, logger, innerHandler)); return this; } internal HttpClient Build(HttpMessageHandler innerHandler) { - return handlers.Any() ? new HttpClient(CreateHttpMessageHandler()) : new HttpClient(innerHandler); + return _handlers.Any() ? + new HttpClient(CreateHttpMessageHandler()) : + new HttpClient(innerHandler); } private HttpMessageHandler CreateHttpMessageHandler() { HttpMessageHandler httpMessageHandler = new HttpClientHandler(); - handlers.OrderByDescending(handler => handler.Key).Select(handler => handler.Value).Reverse().ToList().ForEach(handler => + _handlers + .OrderByDescending(handler => handler.Key) + .Select(handler => handler.Value) + .Reverse() + .ToList() + .ForEach(handler => { var delegatingHandler = handler(); delegatingHandler.InnerHandler = httpMessageHandler; diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index cc5332f3..5a2c86c7 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -1,17 +1,17 @@ using System; -using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; -using Ocelot.Errors; -using Ocelot.Responses; using Ocelot.Logging; +using Ocelot.Responses; +using Polly.CircuitBreaker; +using Polly.Timeout; namespace Ocelot.Requester { public class HttpClientHttpRequester : IHttpRequester { private readonly IOcelotLogger _logger; - + public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger(); @@ -19,13 +19,13 @@ namespace Ocelot.Requester public async Task> GetResponse(Request.Request request) { - HttpClientBuilder builder = new HttpClientBuilder(); + var builder = new HttpClientBuilder(); using (var handler = new HttpClientHandler { CookieContainer = request.CookieContainer }) { if (request.IsQos) { - builder.WithCircuitBreaker(request.Qos, _logger, handler); + builder.WithQoS(request.QosProvider, _logger, handler); } using (var httpClient = builder.Build(handler)) @@ -35,10 +35,15 @@ namespace Ocelot.Requester var response = await httpClient.SendAsync(request.HttpRequestMessage); return new OkResponse(response); } - catch (Polly.Timeout.TimeoutRejectedException exception) + catch (TimeoutRejectedException exception) { return - new ErrorResponse(new RequestTimedOutError(exception)); + new ErrorResponse(new RequestTimedOutError(exception)); + } + catch (BrokenCircuitException exception) + { + return + new ErrorResponse(new RequestTimedOutError(exception)); } catch (Exception exception) { diff --git a/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs b/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs new file mode 100644 index 00000000..a50ec91b --- /dev/null +++ b/src/Ocelot/Requester/PollyCircuitBreakingDelegatingHandler.cs @@ -0,0 +1,47 @@ +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Ocelot.Logging; +using Ocelot.Requester.QoS; +using Polly; +using Polly.CircuitBreaker; +using Polly.Timeout; + +namespace Ocelot.Requester +{ + public class PollyCircuitBreakingDelegatingHandler : DelegatingHandler + { + private readonly IQoSProvider _qoSProvider; + private readonly IOcelotLogger _logger; + + public PollyCircuitBreakingDelegatingHandler( + IQoSProvider qoSProvider, + IOcelotLogger logger, + HttpMessageHandler innerHandler) + : base(innerHandler) + { + _qoSProvider = qoSProvider; + _logger = logger; + } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + try + { + return await Policy + .WrapAsync(_qoSProvider.CircuitBreaker.CircuitBreakerPolicy, _qoSProvider.CircuitBreaker.TimeoutPolicy) + .ExecuteAsync(() => base.SendAsync(request,cancellationToken)); + } + catch (BrokenCircuitException ex) + { + _logger.LogError($"Reached to allowed number of exceptions. Circuit is open",ex); + throw; + } + catch (HttpRequestException ex) + { + _logger.LogError($"Error in CircuitBreakingDelegatingHandler.SendAync", ex); + throw; + } + } + } +} diff --git a/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs b/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs new file mode 100644 index 00000000..d1f63ea1 --- /dev/null +++ b/src/Ocelot/Requester/QoS/IQoSProviderFactory.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using Ocelot.Configuration; +using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Logging; +using Ocelot.Responses; +using Polly; +using Polly.CircuitBreaker; +using Polly.Timeout; + +namespace Ocelot.Requester.QoS +{ + public interface IQoSProviderFactory + { + IQoSProvider Get(ReRoute reRoute); + } + + public class QoSProviderFactory : IQoSProviderFactory + { + private readonly IOcelotLoggerFactory _loggerFactory; + + public QoSProviderFactory(IOcelotLoggerFactory loggerFactory) + { + _loggerFactory = loggerFactory; + } + + public IQoSProvider Get(ReRoute reRoute) + { + if (reRoute.IsQos) + { + return new PollyQoSProvider(reRoute, _loggerFactory); + } + + return new NoQoSProvider(); + } + } + + public interface IQoSProvider + { + CircuitBreaker CircuitBreaker { get; } + } + + public class NoQoSProvider : IQoSProvider + { + public CircuitBreaker CircuitBreaker { get; } + } + + public class PollyQoSProvider : IQoSProvider + { + private readonly CircuitBreakerPolicy _circuitBreakerPolicy; + private readonly TimeoutPolicy _timeoutPolicy; + private readonly IOcelotLogger _logger; + private readonly CircuitBreaker _circuitBreaker; + + public PollyQoSProvider(ReRoute reRoute, IOcelotLoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + + _timeoutPolicy = Policy.TimeoutAsync(reRoute.QosOptions.TimeoutValue, reRoute.QosOptions.TimeoutStrategy); + + _circuitBreakerPolicy = Policy + .Handle() + .Or() + .Or() + .CircuitBreakerAsync( + exceptionsAllowedBeforeBreaking: reRoute.QosOptions.ExceptionsAllowedBeforeBreaking, + durationOfBreak: reRoute.QosOptions.DurationOfBreak, + onBreak: (ex, breakDelay) => + { + _logger.LogError( + ".Breaker logging: Breaking the circuit for " + breakDelay.TotalMilliseconds + "ms!", ex); + }, + onReset: () => + { + _logger.LogDebug(".Breaker logging: Call ok! Closed the circuit again."); + }, + onHalfOpen: () => + { + _logger.LogDebug(".Breaker logging: Half-open; next call is a trial."); + } + ); + + _circuitBreaker = new CircuitBreaker(_circuitBreakerPolicy, _timeoutPolicy); + } + + public CircuitBreaker CircuitBreaker => _circuitBreaker; + } + + public class CircuitBreaker + { + public CircuitBreaker(CircuitBreakerPolicy circuitBreakerPolicy, TimeoutPolicy timeoutPolicy) + { + CircuitBreakerPolicy = circuitBreakerPolicy; + TimeoutPolicy = timeoutPolicy; + } + + public CircuitBreakerPolicy CircuitBreakerPolicy { get; private set; } + public TimeoutPolicy TimeoutPolicy { get; private set; } + } + + + public interface IQosProviderHouse + { + Response Get(string key); + Response Add(string key, IQoSProvider loadBalancer); + } + + public class QosProviderHouse : IQosProviderHouse + { + private readonly Dictionary _qoSProviders; + + public QosProviderHouse() + { + _qoSProviders = new Dictionary(); + } + + public Response Get(string key) + { + IQoSProvider qoSProvider; + + if (_qoSProviders.TryGetValue(key, out qoSProvider)) + { + return new OkResponse(_qoSProviders[key]); + } + + return new ErrorResponse(new List() + { + new UnableToFindQoSProviderError($"unabe to find qos provider for {key}") + }); + } + + public Response Add(string key, IQoSProvider loadBalancer) + { + if (!_qoSProviders.ContainsKey(key)) + { + _qoSProviders.Add(key, loadBalancer); + } + + _qoSProviders.Remove(key); + _qoSProviders.Add(key, loadBalancer); + return new OkResponse(); + } + } +} diff --git a/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs b/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs new file mode 100644 index 00000000..46ec65f3 --- /dev/null +++ b/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Errors; + +namespace Ocelot.Requester.QoS +{ + public class UnableToFindQoSProviderError : Error + { + public UnableToFindQoSProviderError(string message) + : base(message, OcelotErrorCode.UnableToFindQoSProviderError) + { + } + } +} diff --git a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs index b9fcb299..45aeafd1 100644 --- a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs +++ b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs @@ -24,7 +24,7 @@ namespace Ocelot.Responder if (errors.Any(e => e.Code == OcelotErrorCode.RequestTimedOutError)) { - return new OkResponse(408); + return new OkResponse(503); } return new OkResponse(404); diff --git a/test/Ocelot.AcceptanceTests/QoSTests.cs b/test/Ocelot.AcceptanceTests/QoSTests.cs index 50c1fb66..0eb3d8a4 100644 --- a/test/Ocelot.AcceptanceTests/QoSTests.cs +++ b/test/Ocelot.AcceptanceTests/QoSTests.cs @@ -15,16 +15,16 @@ namespace Ocelot.AcceptanceTests { public class QoSTests : IDisposable { - private IWebHost _builder; + private IWebHost _brokenService; private readonly Steps _steps; private int _requestCount; + private IWebHost _workingService; public QoSTests() { _steps = new Steps(); } - [Fact] public void should_open_circuit_breaker_then_close() { @@ -50,33 +50,90 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "Hello from Laura")) + this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51879", "Hello from Laura")) .Given(x => _steps.GivenThereIsAConfiguration(configuration)) .Given(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.RequestTimeout)) + .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.RequestTimeout)) + .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) .Given(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.RequestTimeout)) - .Given(x => x.GivenIWaitMilliSeconds(2000)) + .Given(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .Given(x => x.GivenIWaitMilliseconds(3000)) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) .BDDfy(); } - private void GivenIWaitMilliSeconds(int ms) + [Fact] + public void open_circuit_should_not_effect_different_reRoute() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = "Get", + QoSOptions = new FileQoSOptions + { + ExceptionsAllowedBeforeBreaking = 1, + TimeoutValue = 500, + DurationOfBreak = 1000 + } + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51880, + UpstreamPathTemplate = "working", + UpstreamHttpMethod = "Get", + } + } + }; + + this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51879", "Hello from Laura")) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", 200, "Hello from Tom")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/working")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .And(x => x.GivenIWaitMilliseconds(3000)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + private void GivenIWaitMilliseconds(int ms) { Thread.Sleep(ms); } - private void GivenThereIsAServiceRunningOn(string url, string responseBody) + private void GivenThereIsAPossiblyBrokenServiceRunningOn(string url, string responseBody) { - _builder = new WebHostBuilder() + _brokenService = new WebHostBuilder() .UseUrls(url) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) @@ -95,7 +152,7 @@ namespace Ocelot.AcceptanceTests return; } - //request one times out and polly throws exception + //request one times out and polly throws exception, circuit opens if (_requestCount == 1) { _requestCount++; @@ -104,17 +161,8 @@ namespace Ocelot.AcceptanceTests return; } - //request two times out and polly throws exception circuit opens - if (_requestCount == 2) - { - _requestCount++; - await Task.Delay(1000); - context.Response.StatusCode = 200; - return; - } - //after break closes we return 200 OK - if (_requestCount == 3) + if (_requestCount == 2) { context.Response.StatusCode = 200; await context.Response.WriteAsync(responseBody); @@ -124,12 +172,34 @@ namespace Ocelot.AcceptanceTests }) .Build(); - _builder.Start(); + _brokenService.Start(); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) + { + _workingService = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _workingService.Start(); } public void Dispose() { - _builder?.Dispose(); + _workingService?.Dispose(); + _brokenService?.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 5080c9e0..fa1bbda3 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -9,6 +9,7 @@ using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Requester.QoS; using Ocelot.Responses; using Shouldly; using TestStack.BDDfy; @@ -28,9 +29,15 @@ namespace Ocelot.UnitTests.Configuration private readonly Mock _loadBalancerFactory; private readonly Mock _loadBalancerHouse; private readonly Mock _loadBalancer; + private readonly Mock _qosProviderFactory; + private readonly Mock _qosProviderHouse; + private readonly Mock _qosProvider; public FileConfigurationCreatorTests() { + _qosProviderFactory = new Mock(); + _qosProviderHouse = new Mock(); + _qosProvider = new Mock(); _logger = new Mock>(); _configParser = new Mock(); _validator = new Mock(); @@ -40,7 +47,8 @@ namespace Ocelot.UnitTests.Configuration _loadBalancer = new Mock(); _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object, - _loadBalancerFactory.Object, _loadBalancerHouse.Object); + _loadBalancerFactory.Object, _loadBalancerHouse.Object, + _qosProviderFactory.Object, _qosProviderHouse.Object); } [Fact] @@ -64,10 +72,39 @@ namespace Ocelot.UnitTests.Configuration .When(x => x.WhenICreateTheConfig()) .Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly()) .And(x => x.ThenTheLoadBalancerHouseIsCalledCorrectly()) - .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() { @@ -568,7 +605,7 @@ namespace Ocelot.UnitTests.Configuration .WithDownstreamPathTemplate("/api/products/") .WithUpstreamPathTemplate("/") .WithUpstreamHttpMethod("Get") - .WithUpstreamTemplatePattern("/$") + .WithUpstreamTemplatePattern("^/$") .Build() })) .BDDfy(); @@ -643,5 +680,24 @@ namespace Ocelot.UnitTests.Configuration _loadBalancerHouse .Verify(x => x.Add(It.IsAny(), _loadBalancer.Object), Times.Once); } + + private void GivenTheQosProviderFactoryReturns() + { + _qosProviderFactory + .Setup(x => x.Get(It.IsAny())) + .Returns(_qosProvider.Object); + } + + private void TheQosProviderFactoryIsCalledCorrectly() + { + _qosProviderFactory + .Verify(x => x.Get(It.IsAny()), Times.Once); + } + + private void ThenTheQosProviderHouseIsCalledCorrectly() + { + _qosProviderHouse + .Verify(x => x.Add(It.IsAny(), _qosProvider.Object), Times.Once); + } } } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 81fc7022..2fb8f28f 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -35,6 +35,37 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder [Fact] public void should_return_route() + { + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher")) + .And(x =>x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>( + new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod("Get") + .WithUpstreamTemplatePattern("someUpstreamPath") + .Build() + })) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) + .When(x => x.WhenICallTheFinder()) + .Then( + x => x.ThenTheFollowingIsReturned(new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamHttpMethod("Get") + .Build() + ))) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_return_route_if_upstream_path_and_upstream_template_are_the_same() { this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) .And( @@ -61,7 +92,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithUpstreamHttpMethod("Get") .Build() ))) - .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) + .And(x => x.ThenTheUrlMatcherIsNotCalled()) .BDDfy(); } @@ -105,7 +136,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder [Fact] public void should_not_return_route() { - this.Given(x => x.GivenThereIsAnUpstreamUrlPath("somePath")) + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("dontMatchPath")) .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() @@ -148,6 +179,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Once); } + private void ThenTheUrlMatcherIsNotCalled() + { + _mockMatcher + .Verify(x => x.Match(_upstreamUrlPath, _reRoutesConfig[0].UpstreamPathTemplate.Value), Times.Never); + } + private void GivenTheUrlMatcherReturns(Response match) { _match = match; diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs index ea069593..70dd747d 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs @@ -18,6 +18,26 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher _urlMatcher = new RegExUrlMatcher(); } + [Fact] + public void should_not_match_forward_slash_only_regex() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/working/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + + [Fact] + public void should_match_forward_slash_only_regex() + { + this.Given(x => x.GivenIHaveAUpstreamPath("/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern("^/$")) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsTrue()) + .BDDfy(); + } + [Fact] public void should_find_match_when_template_smaller_than_valid_path() { diff --git a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs index da5d2031..b674b015 100644 --- a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs @@ -20,6 +20,7 @@ using Ocelot.Responses; using TestStack.BDDfy; using Xunit; using Ocelot.Configuration; +using Ocelot.Requester.QoS; namespace Ocelot.UnitTests.Request { @@ -27,6 +28,7 @@ namespace Ocelot.UnitTests.Request { private readonly Mock _requestBuilder; private readonly Mock _scopedRepository; + private readonly Mock _qosProviderHouse; private readonly string _url; private readonly TestServer _server; private readonly HttpClient _client; @@ -38,6 +40,7 @@ namespace Ocelot.UnitTests.Request public HttpRequestBuilderMiddlewareTests() { _url = "http://localhost:51879"; + _qosProviderHouse = new Mock(); _requestBuilder = new Mock(); _scopedRepository = new Mock(); var builder = new WebHostBuilder() @@ -45,6 +48,7 @@ namespace Ocelot.UnitTests.Request { x.AddSingleton(); x.AddLogging(); + x.AddSingleton(_qosProviderHouse.Object); x.AddSingleton(_requestBuilder.Object); x.AddSingleton(_scopedRepository.Object); }) @@ -72,15 +76,22 @@ namespace Ocelot.UnitTests.Request .WithUpstreamHttpMethod("Get") .Build()); - 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 QoSOptions(3, 8 ,5000, Polly.Timeout.TimeoutStrategy.Pessimistic)))) + .And(x => x.GivenTheRequestBuilderReturns(new Ocelot.Request.Request(new HttpRequestMessage(), new CookieContainer(), true, new NoQoSProvider()))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheScopedDataRepositoryIsCalledCorrectly()) .BDDfy(); } + private void GivenTheQosProviderHouseReturns(Response qosProvider) + { + _qosProviderHouse + .Setup(x => x.Get(It.IsAny())) + .Returns(qosProvider); + } + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { _downstreamRoute = new OkResponse(downstreamRoute); @@ -94,7 +105,7 @@ 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())) + 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 c317c8d7..9d1c2ed5 100644 --- a/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs +++ b/test/Ocelot.UnitTests/Request/RequestBuilderTests.cs @@ -11,6 +11,7 @@ using Shouldly; using TestStack.BDDfy; using Xunit; using Ocelot.Configuration; +using Ocelot.Requester.QoS; namespace Ocelot.UnitTests.Request { @@ -27,7 +28,7 @@ namespace Ocelot.UnitTests.Request private Response _result; private Ocelot.RequestId.RequestId _requestId; private bool _isQos; - private QoSOptions _qos; + private IQoSProvider _qoSProvider; public RequestBuilderTests() { @@ -40,7 +41,7 @@ namespace Ocelot.UnitTests.Request { this.Given(x => x.GivenIHaveHttpMethod("GET")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) - .And(x=> x.GivenTheQos(true,new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x=> x.GivenTheQos(true, new NoQoSProvider())) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectDownstreamUrlIsUsed("http://www.bbc.co.uk/")) .BDDfy(); @@ -51,7 +52,7 @@ namespace Ocelot.UnitTests.Request { this.Given(x => x.GivenIHaveHttpMethod("POST")) .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) - .And(x => x.GivenTheQos(true,new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x => x.GivenTheQos(true, new NoQoSProvider())) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectHttpMethodIsUsed(HttpMethod.Post)) @@ -65,7 +66,7 @@ namespace Ocelot.UnitTests.Request .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) .And(x => x.GivenTheContentTypeIs("application/json")) - .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x => x.GivenTheQos(true, new NoQoSProvider())) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectContentIsUsed(new StringContent("Hi from Tom"))) @@ -79,7 +80,7 @@ namespace Ocelot.UnitTests.Request .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) .And(x => x.GivenTheContentTypeIs("application/json")) - .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x => x.GivenTheQos(true, new NoQoSProvider())) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary @@ -98,7 +99,7 @@ namespace Ocelot.UnitTests.Request .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenIHaveTheHttpContent(new StringContent("Hi from Tom"))) .And(x => x.GivenTheContentTypeIs("application/json; charset=utf-8")) - .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x => x.GivenTheQos(true, new NoQoSProvider())) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectContentHeadersAreUsed(new HeaderDictionary @@ -119,7 +120,7 @@ namespace Ocelot.UnitTests.Request { {"ChopSticks", "Bubbles" } })) - .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x => x.GivenTheQos(true, new NoQoSProvider())) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary @@ -138,7 +139,7 @@ namespace Ocelot.UnitTests.Request .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary())) .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", requestId))) - .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x => x.GivenTheQos(true, new NoQoSProvider())) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary { @@ -157,7 +158,7 @@ namespace Ocelot.UnitTests.Request {"RequestId", "534534gv54gv45g" } })) .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId("RequestId", Guid.NewGuid().ToString()))) - .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x => x.GivenTheQos(true, new NoQoSProvider())) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheCorrectHeadersAreUsed(new HeaderDictionary { @@ -177,7 +178,7 @@ namespace Ocelot.UnitTests.Request .And(x => x.GivenIHaveDownstreamUrl("http://www.bbc.co.uk")) .And(x => x.GivenTheHttpHeadersAre(new HeaderDictionary())) .And(x => x.GivenTheRequestIdIs(new Ocelot.RequestId.RequestId(requestIdKey, requestIdValue))) - .And(x => x.GivenTheQos(true, new QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic))) + .And(x => x.GivenTheQos(true, new NoQoSProvider())) .When(x => x.WhenICreateARequest()) .And(x => x.ThenTheRequestIdIsNotInTheHeaders()) .BDDfy(); @@ -188,10 +189,10 @@ namespace Ocelot.UnitTests.Request _requestId = requestId; } - private void GivenTheQos(bool isQos, QoSOptions qos) + private void GivenTheQos(bool isQos, IQoSProvider qoSProvider) { _isQos = isQos; - _qos = qos; + _qoSProvider = qoSProvider; } [Fact] @@ -304,7 +305,7 @@ namespace Ocelot.UnitTests.Request private void WhenICreateARequest() { _result = _requestCreator.Build(_httpMethod, _downstreamUrl, _content?.ReadAsStreamAsync().Result, _headers, - _cookies, _query, _contentType, _requestId,_isQos,_qos).Result; + _cookies, _query, _contentType, _requestId,_isQos,_qoSProvider).Result; } diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index d2d62923..37815a64 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -13,6 +13,7 @@ using Ocelot.Logging; using Ocelot.QueryStrings.Middleware; using Ocelot.Requester; using Ocelot.Requester.Middleware; +using Ocelot.Requester.QoS; using Ocelot.Responder; using Ocelot.Responses; using TestStack.BDDfy; @@ -61,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 Ocelot.Configuration.QoSOptions(3, 8, 5000, Polly.Timeout.TimeoutStrategy.Pessimistic)))) + this.Given(x => x.GivenTheRequestIs(new Ocelot.Request.Request(new HttpRequestMessage(),new CookieContainer(),true, new NoQoSProvider()))) .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) .And(x => x.GivenTheScopedRepoReturns()) .When(x => x.WhenICallTheMiddleware()) diff --git a/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs b/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs new file mode 100644 index 00000000..f4beb0f1 --- /dev/null +++ b/test/Ocelot.UnitTests/Requester/QoSProviderFactoryTests.cs @@ -0,0 +1,80 @@ +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Logging; +using Ocelot.Requester.QoS; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Requester +{ + public class QoSProviderFactoryTests + { + private readonly IQoSProviderFactory _factory; + private ReRoute _reRoute; + private IQoSProvider _result; + private Mock _loggerFactory; + private Mock _logger; + + public QoSProviderFactoryTests() + { + _logger = new Mock(); + _loggerFactory = new Mock(); + _loggerFactory + .Setup(x => x.CreateLogger()) + .Returns(_logger.Object); + _factory = new QoSProviderFactory(_loggerFactory.Object); + } + + [Fact] + public void should_return_no_qos_provider() + { + var reRoute = new ReRouteBuilder() + .WithUpstreamHttpMethod("get") + .WithIsQos(false) + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheQoSProvider()) + .Then(x => x.ThenTheQoSProviderIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_polly_qos_provider() + { + var qosOptions = new QoSOptionsBuilder() + .WithTimeoutValue(100) + .WithDurationOfBreak(100) + .WithExceptionsAllowedBeforeBreaking(100) + .Build(); + + var reRoute = new ReRouteBuilder() + .WithUpstreamHttpMethod("get") + .WithIsQos(true) + .WithQosOptions(qosOptions) + .Build(); + + this.Given(x => x.GivenAReRoute(reRoute)) + .When(x => x.WhenIGetTheQoSProvider()) + .Then(x => x.ThenTheQoSProviderIsReturned()) + .BDDfy(); + } + + private void GivenAReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGetTheQoSProvider() + { + _result = _factory.Get(_reRoute); + } + + private void ThenTheQoSProviderIsReturned() + { + _result.ShouldBeOfType(); + } + } +} diff --git a/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs b/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs new file mode 100644 index 00000000..1e76a027 --- /dev/null +++ b/test/Ocelot.UnitTests/Requester/QosProviderHouseTests.cs @@ -0,0 +1,117 @@ +using Ocelot.Requester.QoS; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Requester +{ + public class QosProviderHouseTests + { + private IQoSProvider _qoSProvider; + private readonly QosProviderHouse _qosProviderHouse; + private Response _addResult; + private Response _getResult; + private string _key; + + public QosProviderHouseTests() + { + _qosProviderHouse = new QosProviderHouse(); + } + + [Fact] + public void should_store_qos_provider() + { + var key = "test"; + + this.Given(x => x.GivenThereIsAQoSProvider(key, new FakeQoSProvider())) + .When(x => x.WhenIAddTheQoSProvider()) + .Then(x => x.ThenItIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_get_qos_provider() + { + var key = "test"; + + this.Given(x => x.GivenThereIsAQoSProvider(key, new FakeQoSProvider())) + .When(x => x.WhenWeGetTheQoSProvider(key)) + .Then(x => x.ThenItIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_store_qos_providers_by_key() + { + var key = "test"; + var keyTwo = "testTwo"; + + this.Given(x => x.GivenThereIsAQoSProvider(key, new FakeQoSProvider())) + .And(x => x.GivenThereIsAQoSProvider(keyTwo, new FakePollyQoSProvider())) + .When(x => x.WhenWeGetTheQoSProvider(key)) + .Then(x => x.ThenTheQoSProviderIs()) + .When(x => x.WhenWeGetTheQoSProvider(keyTwo)) + .Then(x => x.ThenTheQoSProviderIs()) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_no_qos_provider_with_key() + { + this.When(x => x.WhenWeGetTheQoSProvider("test")) + .Then(x => x.ThenAnErrorIsReturned()) + .BDDfy(); + } + + private void ThenAnErrorIsReturned() + { + _getResult.IsError.ShouldBeTrue(); + _getResult.Errors[0].ShouldBeOfType(); + } + + private void ThenTheQoSProviderIs() + { + _getResult.Data.ShouldBeOfType(); + } + + private void ThenItIsAdded() + { + _addResult.IsError.ShouldBe(false); + _addResult.ShouldBeOfType(); + } + + private void WhenIAddTheQoSProvider() + { + _addResult = _qosProviderHouse.Add(_key, _qoSProvider); + } + + + private void GivenThereIsAQoSProvider(string key, IQoSProvider qoSProvider) + { + _key = key; + _qoSProvider = qoSProvider; + WhenIAddTheQoSProvider(); + } + + private void WhenWeGetTheQoSProvider(string key) + { + _getResult = _qosProviderHouse.Get(key); + } + + private void ThenItIsReturned() + { + _getResult.Data.ShouldBe(_qoSProvider); + } + + class FakeQoSProvider : IQoSProvider + { + public CircuitBreaker CircuitBreaker { get; } + } + + class FakePollyQoSProvider : IQoSProvider + { + public CircuitBreaker CircuitBreaker { get; } + } + } +} diff --git a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs index d2b7e386..d2ac91e0 100644 --- a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs +++ b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs @@ -30,7 +30,7 @@ namespace Ocelot.UnitTests.Responder new RequestTimedOutError(new Exception()) })) .When(x => x.WhenIGetErrorStatusCode()) - .Then(x => x.ThenTheResponseIsStatusCodeIs(408)) + .Then(x => x.ThenTheResponseIsStatusCodeIs(503)) .BDDfy(); } From e1f16c2be10550adf63c9047fd94fb5e368d6361 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Sun, 12 Feb 2017 15:49:21 +0800 Subject: [PATCH 076/113] add ratelimit acceptance test --- global.json | 2 +- .../Creator/FileOcelotConfigurationCreator.cs | 2 +- .../File/FileRateLimitOptions.cs | 6 +- src/Ocelot/Configuration/RateLimitOptions.cs | 9 +- ...DistributedCacheRateLimitCounterHanlder.cs | 45 ++++ .../ClientRateLimitTests.cs | 208 ++++++++++++++++++ test/Ocelot.AcceptanceTests/Steps.cs | 18 ++ .../Ocelot.AcceptanceTests/configuration.json | 2 +- .../ClientRateLimitMiddlewareTests.cs | 5 +- 9 files changed, 286 insertions(+), 11 deletions(-) create mode 100644 src/Ocelot/RateLimit/DistributedCacheRateLimitCounterHanlder.cs create mode 100644 test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs diff --git a/global.json b/global.json index ff8d898e..616b2c4e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@  { "projects": [ "src", "test" ], "sdk": { - "version": "1.0.0-preview2-003133" + "version": "1.0.0-preview2-003131" } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index ae8cc047..c5eababe 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -122,7 +122,7 @@ namespace Ocelot.Configuration.Creator Limit = fileReRoute.RateLimitOptions.Limit, Period = fileReRoute.RateLimitOptions.Period, PeriodTimespan = TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan) - }); + }, globalConfiguration.RateLimitOptions.HttpStatusCode); } var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; diff --git a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs index e685ee56..655b2442 100644 --- a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs +++ b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs @@ -7,7 +7,6 @@ namespace Ocelot.Configuration.File { public class FileRateLimitOptions { - /// /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId /// @@ -29,6 +28,11 @@ namespace Ocelot.Configuration.File /// Disables X-Rate-Limit and Rety-After headers /// public bool DisableRateLimitHeaders { get; set; } + + /// + /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) + /// + public int HttpStatusCode { get; private set; } = 429; } diff --git a/src/Ocelot/Configuration/RateLimitOptions.cs b/src/Ocelot/Configuration/RateLimitOptions.cs index 7d4d0f4a..d5c68d23 100644 --- a/src/Ocelot/Configuration/RateLimitOptions.cs +++ b/src/Ocelot/Configuration/RateLimitOptions.cs @@ -11,7 +11,7 @@ namespace Ocelot.Configuration public class RateLimitOptions { public RateLimitOptions(bool enbleRateLimiting, string clientIdHeader, List clientWhitelist,bool disableRateLimitHeaders, - string quotaExceededMessage, string rateLimitCounterPrefix, RateLimitRule rateLimitRule) + string quotaExceededMessage, string rateLimitCounterPrefix, RateLimitRule rateLimitRule, int httpStatusCode) { EnableRateLimiting = enbleRateLimiting; ClientIdHeader = clientIdHeader; @@ -20,6 +20,7 @@ namespace Ocelot.Configuration QuotaExceededMessage = quotaExceededMessage; RateLimitCounterPrefix = rateLimitCounterPrefix; RateLimitRule = rateLimitRule; + HttpStatusCode = httpStatusCode; } public RateLimitRule RateLimitRule { get; private set; } @@ -29,12 +30,12 @@ namespace Ocelot.Configuration /// /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId /// - public string ClientIdHeader { get; private set; } = "ClientId"; + public string ClientIdHeader { get; private set; } /// /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) /// - public int HttpStatusCode { get; private set; } = 429; + public int HttpStatusCode { get; private set; } /// /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. @@ -46,7 +47,7 @@ namespace Ocelot.Configuration /// /// Gets or sets the counter prefix, used to compose the rate limit counter cache key /// - public string RateLimitCounterPrefix { get; private set; } = "ocelot"; + public string RateLimitCounterPrefix { get; private set; } /// /// Enables endpoint rate limiting based URL path and HTTP verb diff --git a/src/Ocelot/RateLimit/DistributedCacheRateLimitCounterHanlder.cs b/src/Ocelot/RateLimit/DistributedCacheRateLimitCounterHanlder.cs new file mode 100644 index 00000000..1db8f334 --- /dev/null +++ b/src/Ocelot/RateLimit/DistributedCacheRateLimitCounterHanlder.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.Caching.Distributed; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.RateLimit +{ + public class DistributedCacheRateLimitCounterHanlder : IRateLimitCounterHandler + { + private readonly IDistributedCache _memoryCache; + + public DistributedCacheRateLimitCounterHanlder(IDistributedCache memoryCache) + { + _memoryCache = memoryCache; + } + + public void Set(string id, RateLimitCounter counter, TimeSpan expirationTime) + { + _memoryCache.SetString(id, JsonConvert.SerializeObject(counter), new DistributedCacheEntryOptions().SetAbsoluteExpiration(expirationTime)); + } + + public bool Exists(string id) + { + var stored = _memoryCache.GetString(id); + return !string.IsNullOrEmpty(stored); + } + + public RateLimitCounter? Get(string id) + { + var stored = _memoryCache.GetString(id); + if (!string.IsNullOrEmpty(stored)) + { + return JsonConvert.DeserializeObject(stored); + } + return null; + } + + public void Remove(string id) + { + _memoryCache.Remove(id); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs new file mode 100644 index 00000000..2e8c3c90 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs @@ -0,0 +1,208 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.File; +using Shouldly; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class ClientRateLimitTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + private int _counterOne; + + + public ClientRateLimitTests() + { + _steps = new Steps(); + } + + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + + [Fact] + public void should_call_withratelimiting() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/ClientRateLimit", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", + UpstreamTemplate = "/api/ClientRateLimit", + UpstreamHttpMethod = "Get", + RequestIdKey = _steps.RequestIdKey, + + RateLimitOptions = new FileRateLimitRule() + { + EnableRateLimiting = true, + ClientWhitelist = new List(), + Limit = 3, + Period = "1s", + PeriodTimespan = 100 + } + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + RateLimitOptions = new FileRateLimitOptions() + { + ClientIdHeader = "ClientId", + DisableRateLimitHeaders = false, + QuotaExceededMessage = "", + RateLimitCounterPrefix = "" + }, + RequestIdKey ="oceclientrequest" + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/ClientRateLimit")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 5)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(429)) + .BDDfy(); + } + + + [Fact] + public void should_call_middleware_withWhitelistClient() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/ClientRateLimit", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", + UpstreamTemplate = "/api/ClientRateLimit", + UpstreamHttpMethod = "Get", + RequestIdKey = _steps.RequestIdKey, + + RateLimitOptions = new FileRateLimitRule() + { + EnableRateLimiting = true, + ClientWhitelist = new List() { "ocelotclient1"}, + Limit = 3, + Period = "1s", + PeriodTimespan = 100 + } + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + RateLimitOptions = new FileRateLimitOptions() + { + ClientIdHeader = "ClientId", + DisableRateLimitHeaders = false, + QuotaExceededMessage = "", + RateLimitCounterPrefix = "" + }, + RequestIdKey = "oceclientrequest" + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/ClientRateLimit")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 4)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) + .BDDfy(); + } + + + private void GivenThereIsAServiceRunningOn(string url) + { + _builder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(context => + { + _counterOne++; + context.Response.StatusCode = 200; + context.Response.WriteAsync(_counterOne.ToString()); + return Task.CompletedTask; + }); + }) + .Build(); + + _builder.Start(); + } + + //private void GetApiRateLimait(string url) + //{ + // var clientId = "ocelotclient1"; + // var request = new HttpRequestMessage(new HttpMethod("GET"), url); + // request.Headers.Add("ClientId", clientId); + + // var response = _client.SendAsync(request); + // responseStatusCode = (int)response.Result.StatusCode; + // } + + //} + + //public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times) + //{ + // var clientId = "ocelotclient1"; + // var tasks = new Task[times]; + + // for (int i = 0; i < times; i++) + // { + // var urlCopy = url; + // tasks[i] = GetForServiceDiscoveryTest(urlCopy); + // Thread.Sleep(_random.Next(40, 60)); + // } + + // Task.WaitAll(tasks); + //} + + //private void WhenICallTheMiddlewareWithWhiteClient() + //{ + // var clientId = "ocelotclient2"; + // // Act + // for (int i = 0; i < 2; i++) + // { + // var request = new HttpRequestMessage(new HttpMethod("GET"), apiRateLimitPath); + // request.Headers.Add("ClientId", clientId); + + // var response = _client.SendAsync(request); + // responseStatusCode = (int)response.Result.StatusCode; + // } + //} + + //private void ThenresponseStatusCodeIs429() + //{ + // responseStatusCode.ShouldBe(429); + //} + + //private void ThenresponseStatusCodeIs200() + //{ + // responseStatusCode.ShouldBe(200); + //} + } +} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 92ab6daf..5981c343 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -184,6 +184,17 @@ namespace Ocelot.AcceptanceTests count.ShouldBeGreaterThan(0); } + public void WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit(string url, int times) + { + for (int i = 0; i < times; i++) + { + var clientId = "ocelotclient1"; + var request = new HttpRequestMessage(new HttpMethod("GET"), url); + request.Headers.Add("ClientId", clientId); + _response = _ocelotClient.SendAsync(request).Result; + } + } + public void WhenIGetUrlOnTheApiGateway(string url, string requestId) { _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(RequestIdKey, requestId); @@ -211,6 +222,13 @@ namespace Ocelot.AcceptanceTests _response.StatusCode.ShouldBe(expectedHttpStatusCode); } + + public void ThenTheStatusCodeShouldBe(int expectedHttpStatusCode) + { + var responseStatusCode = (int)_response.StatusCode; + responseStatusCode.ShouldBe(expectedHttpStatusCode); + } + public void Dispose() { _ocelotClient?.Dispose(); diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index 8626f7c1..af289aab 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index dd1dc0e0..11bca113 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -29,7 +29,6 @@ namespace Ocelot.UnitTests.RateLimit private readonly string _url; private readonly TestServer _server; private readonly HttpClient _client; - private HttpResponseMessage _result; private OkResponse _downstreamRoute; private int responseStatusCode; @@ -71,7 +70,7 @@ namespace Ocelot.UnitTests.RateLimit { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( - new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List(), false, "", "", new Ocelot.Configuration.RateLimitRule() { Limit = 3, Period = "1s", PeriodTimespan = TimeSpan.FromSeconds(100) })) + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List(), false, "", "", new Ocelot.Configuration.RateLimitRule() { Limit = 3, Period = "1s", PeriodTimespan = TimeSpan.FromSeconds(100) },429)) .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) @@ -85,7 +84,7 @@ namespace Ocelot.UnitTests.RateLimit { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( - new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new Ocelot.Configuration.RateLimitRule() { Limit = 3, Period = "1s", PeriodTimespan = TimeSpan.FromSeconds(100) })) + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new Ocelot.Configuration.RateLimitRule() { Limit = 3, Period = "1s", PeriodTimespan = TimeSpan.FromSeconds(100) },429)) .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) From 659d12478031b8469c5342c1a580621555aba547 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Sun, 12 Feb 2017 15:50:21 +0800 Subject: [PATCH 077/113] reset sdk version --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 616b2c4e..ff8d898e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@  { "projects": [ "src", "test" ], "sdk": { - "version": "1.0.0-preview2-003131" + "version": "1.0.0-preview2-003133" } } From 2fa6e66dd46422232323944df27d5227ff535044 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Mon, 13 Feb 2017 08:29:29 +0800 Subject: [PATCH 078/113] refactor code --- .../Creator/FileOcelotConfigurationCreator.cs | 8 +-- .../File/FileRateLimitOptions.cs | 2 +- .../Configuration/File/FileRateLimitRule.cs | 2 +- src/Ocelot/Configuration/RateLimitOptions.cs | 21 ++++-- .../ServiceCollectionExtensions.cs | 2 +- .../RateLimit/ClientRateLimitProcessor.cs | 7 +- src/Ocelot/RateLimit/ClientRequestIdentity.cs | 13 +++- .../Middleware/ClientRateLimitMiddleware.cs | 20 +++--- src/Ocelot/RateLimit/RateLimitCore.cs | 68 ++++++++++--------- src/Ocelot/RateLimit/RateLimitCounter.cs | 10 ++- src/Ocelot/RateLimit/RateLimitHeaders.cs | 16 +++-- .../ClientRateLimitTests.cs | 63 +++-------------- test/Ocelot.AcceptanceTests/Steps.cs | 1 - .../Ocelot.AcceptanceTests/configuration.json | 2 +- test/Ocelot.AcceptanceTests/project.json | 4 +- test/Ocelot.ManualTest/Startup.cs | 1 - .../ClientRateLimitMiddlewareTests.cs | 13 ++-- test/Ocelot.UnitTests/project.json | 4 +- 18 files changed, 119 insertions(+), 138 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index c5eababe..646af96c 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -117,12 +117,8 @@ namespace Ocelot.Configuration.Creator rateLimitOption = new RateLimitOptions(enableRateLimiting, globalConfiguration.RateLimitOptions.ClientIdHeader, fileReRoute.RateLimitOptions.ClientWhitelist, globalConfiguration.RateLimitOptions.DisableRateLimitHeaders, globalConfiguration.RateLimitOptions.QuotaExceededMessage, globalConfiguration.RateLimitOptions.RateLimitCounterPrefix, - new RateLimitRule() - { - Limit = fileReRoute.RateLimitOptions.Limit, - Period = fileReRoute.RateLimitOptions.Period, - PeriodTimespan = TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan) - }, globalConfiguration.RateLimitOptions.HttpStatusCode); + new RateLimitRule(fileReRoute.RateLimitOptions.Period, TimeSpan.FromSeconds(fileReRoute.RateLimitOptions.PeriodTimespan), fileReRoute.RateLimitOptions.Limit) + , globalConfiguration.RateLimitOptions.HttpStatusCode); } var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; diff --git a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs index 655b2442..afb3fab5 100644 --- a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs +++ b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs @@ -32,7 +32,7 @@ namespace Ocelot.Configuration.File /// /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) /// - public int HttpStatusCode { get; private set; } = 429; + public int HttpStatusCode { get; set; } = 429; } diff --git a/src/Ocelot/Configuration/File/FileRateLimitRule.cs b/src/Ocelot/Configuration/File/FileRateLimitRule.cs index 58d171de..727a9e82 100644 --- a/src/Ocelot/Configuration/File/FileRateLimitRule.cs +++ b/src/Ocelot/Configuration/File/FileRateLimitRule.cs @@ -25,7 +25,7 @@ namespace Ocelot.Configuration.File /// public string Period { get; set; } - public int PeriodTimespan { get; set; } + public double PeriodTimespan { get; set; } /// /// Maximum number of requests that a client can make in a defined period /// diff --git a/src/Ocelot/Configuration/RateLimitOptions.cs b/src/Ocelot/Configuration/RateLimitOptions.cs index d5c68d23..85a3bf78 100644 --- a/src/Ocelot/Configuration/RateLimitOptions.cs +++ b/src/Ocelot/Configuration/RateLimitOptions.cs @@ -15,7 +15,7 @@ namespace Ocelot.Configuration { EnableRateLimiting = enbleRateLimiting; ClientIdHeader = clientIdHeader; - ClientWhitelist = clientWhitelist; + ClientWhitelist = clientWhitelist?? new List(); DisableRateLimitHeaders = disableRateLimitHeaders; QuotaExceededMessage = quotaExceededMessage; RateLimitCounterPrefix = rateLimitCounterPrefix; @@ -62,15 +62,22 @@ namespace Ocelot.Configuration public class RateLimitRule { - /// - /// Rate limit period as in 1s, 1m, 1h - /// - public string Period { get; set; } + public RateLimitRule(string period, TimeSpan periodTimespan, long limit) + { + Period = period; + PeriodTimespan = periodTimespan; + Limit = limit; + } - public TimeSpan? PeriodTimespan { get; set; } + /// + /// Rate limit period as in 1s, 1m, 1h,1d + /// + public string Period { get; private set; } + + public TimeSpan PeriodTimespan { get; private set; } /// /// Maximum number of requests that a client can make in a defined period /// - public long Limit { get; set; } + public long Limit { get; private set; } } } diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index e83ad30c..446720ca 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -91,7 +91,7 @@ namespace Ocelot.DependencyInjection // could maybe use a scoped data repository services.AddSingleton(); services.AddScoped(); - + services.AddMemoryCache(); return services; } } diff --git a/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs b/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs index 38141b49..a2ee1202 100644 --- a/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs +++ b/src/Ocelot/RateLimit/ClientRateLimitProcessor.cs @@ -1,4 +1,5 @@ -using Ocelot.Configuration; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; using System; using System.Collections.Generic; using System.Linq; @@ -27,9 +28,9 @@ namespace Ocelot.RateLimit return _core.RetryAfterFrom(timestamp, rule); } - public RateLimitHeaders GetRateLimitHeaders(ClientRequestIdentity requestIdentity, RateLimitOptions option) + public RateLimitHeaders GetRateLimitHeaders(HttpContext context, ClientRequestIdentity requestIdentity, RateLimitOptions option) { - return _core.GetRateLimitHeaders(requestIdentity, option); + return _core.GetRateLimitHeaders(context, requestIdentity, option); } } diff --git a/src/Ocelot/RateLimit/ClientRequestIdentity.cs b/src/Ocelot/RateLimit/ClientRequestIdentity.cs index 9cceeb9e..a27bc994 100644 --- a/src/Ocelot/RateLimit/ClientRequestIdentity.cs +++ b/src/Ocelot/RateLimit/ClientRequestIdentity.cs @@ -2,10 +2,17 @@ { public class ClientRequestIdentity { - public string ClientId { get; set; } + public ClientRequestIdentity(string clientId, string path, string httpverb) + { + ClientId = clientId; + Path = path; + HttpVerb = httpverb; + } - public string Path { get; set; } + public string ClientId { get; private set; } - public string HttpVerb { get; set; } + public string Path { get; private set; } + + public string HttpVerb { get; private set; } } } \ No newline at end of file diff --git a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs index 64ac40c0..4dbaeed1 100644 --- a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs +++ b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs @@ -72,9 +72,7 @@ namespace Ocelot.RateLimit.Middleware //set X-Rate-Limit headers for the longest period if (!options.DisableRateLimitHeaders) { - var headers = _processor.GetRateLimitHeaders(identity, options); - headers.Context = context; - + var headers = _processor.GetRateLimitHeaders( context,identity, options); context.Response.OnStarting(SetRateLimitHeaders, state: headers); } @@ -89,21 +87,19 @@ namespace Ocelot.RateLimit.Middleware clientId = httpContext.Request.Headers[option.ClientIdHeader].First(); } - return new ClientRequestIdentity - { - Path = httpContext.Request.Path.ToString().ToLowerInvariant(), - HttpVerb = httpContext.Request.Method.ToLowerInvariant(), - ClientId = clientId, - }; - } + return new ClientRequestIdentity( + clientId, + httpContext.Request.Path.ToString().ToLowerInvariant(), + httpContext.Request.Method.ToLowerInvariant() + ); + } public bool IsWhitelisted(ClientRequestIdentity requestIdentity, RateLimitOptions option) { - if (option.ClientWhitelist != null && option.ClientWhitelist.Contains(requestIdentity.ClientId)) + if (option.ClientWhitelist.Contains(requestIdentity.ClientId)) { return true; } - return false; } diff --git a/src/Ocelot/RateLimit/RateLimitCore.cs b/src/Ocelot/RateLimit/RateLimitCore.cs index 39904b06..07627f8d 100644 --- a/src/Ocelot/RateLimit/RateLimitCore.cs +++ b/src/Ocelot/RateLimit/RateLimitCore.cs @@ -1,8 +1,11 @@ -using Ocelot.Configuration; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; namespace Ocelot.RateLimit @@ -19,11 +22,7 @@ namespace Ocelot.RateLimit public RateLimitCounter ProcessRequest(ClientRequestIdentity requestIdentity, RateLimitOptions option) { - var counter = new RateLimitCounter - { - Timestamp = DateTime.UtcNow, - TotalRequests = 1 - }; + RateLimitCounter counter = new RateLimitCounter(DateTime.UtcNow, 1); var rule = option.RateLimitRule; var counterId = ComputeCounterKey(requestIdentity, option); @@ -35,59 +34,57 @@ namespace Ocelot.RateLimit if (entry.HasValue) { // entry has not expired - if (entry.Value.Timestamp + rule.PeriodTimespan.Value >= DateTime.UtcNow) + if (entry.Value.Timestamp + rule.PeriodTimespan >= DateTime.UtcNow) { // increment request count var totalRequests = entry.Value.TotalRequests + 1; // deep copy - counter = new RateLimitCounter - { - Timestamp = entry.Value.Timestamp, - TotalRequests = totalRequests - }; + counter = new RateLimitCounter(entry.Value.Timestamp, totalRequests); + } } - // stores: id (string) - timestamp (datetime) - total_requests (long) - _counterHandler.Set(counterId, counter, rule.PeriodTimespan.Value); + _counterHandler.Set(counterId, counter, rule.PeriodTimespan); } return counter; } - public RateLimitHeaders GetRateLimitHeaders(ClientRequestIdentity requestIdentity, RateLimitOptions option) + public RateLimitHeaders GetRateLimitHeaders(HttpContext context, ClientRequestIdentity requestIdentity, RateLimitOptions option) { var rule = option.RateLimitRule; - var headers = new RateLimitHeaders(); + RateLimitHeaders headers = null; var counterId = ComputeCounterKey(requestIdentity, option); var entry = _counterHandler.Get(counterId); if (entry.HasValue) { - headers.Reset = (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo); - headers.Limit = rule.Period; - headers.Remaining = (rule.Limit - entry.Value.TotalRequests).ToString(); - } + headers = new RateLimitHeaders(context, rule.Period, + (rule.Limit - entry.Value.TotalRequests).ToString(), + (entry.Value.Timestamp + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo) + ); + } else { - headers.Reset = (DateTime.UtcNow + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo); - headers.Limit = rule.Period; - headers.Remaining = rule.Limit.ToString(); + headers = new RateLimitHeaders(context, + rule.Period, + rule.Limit.ToString(), + (DateTime.UtcNow + ConvertToTimeSpan(rule.Period)).ToUniversalTime().ToString("o", DateTimeFormatInfo.InvariantInfo)); + } return headers; - throw new NotImplementedException(); - } + } public string ComputeCounterKey(ClientRequestIdentity requestIdentity, RateLimitOptions option) { var key = $"{option.RateLimitCounterPrefix}_{requestIdentity.ClientId}_{option.RateLimitRule.Period}_{requestIdentity.HttpVerb}_{requestIdentity.Path}"; - var idBytes = System.Text.Encoding.UTF8.GetBytes(key); + var idBytes = Encoding.UTF8.GetBytes(key); byte[] hashBytes; - using (var algorithm = System.Security.Cryptography.SHA1.Create()) + using (var algorithm = SHA1.Create()) { hashBytes = algorithm.ComputeHash(idBytes); } @@ -98,7 +95,7 @@ namespace Ocelot.RateLimit public string RetryAfterFrom(DateTime timestamp, RateLimitRule rule) { var secondsPast = Convert.ToInt32((DateTime.UtcNow - timestamp).TotalSeconds); - var retryAfter = Convert.ToInt32(rule.PeriodTimespan.Value.TotalSeconds); + var retryAfter = Convert.ToInt32(rule.PeriodTimespan.TotalSeconds); retryAfter = retryAfter > 1 ? retryAfter - secondsPast : 1; return retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture); } @@ -111,11 +108,16 @@ namespace Ocelot.RateLimit switch (type) { - case "d": return TimeSpan.FromDays(double.Parse(value)); - case "h": return TimeSpan.FromHours(double.Parse(value)); - case "m": return TimeSpan.FromMinutes(double.Parse(value)); - case "s": return TimeSpan.FromSeconds(double.Parse(value)); - default: throw new FormatException($"{timeSpan} can't be converted to TimeSpan, unknown type {type}"); + case "d": + return TimeSpan.FromDays(double.Parse(value)); + case "h": + return TimeSpan.FromHours(double.Parse(value)); + case "m": + return TimeSpan.FromMinutes(double.Parse(value)); + case "s": + return TimeSpan.FromSeconds(double.Parse(value)); + default: + throw new FormatException($"{timeSpan} can't be converted to TimeSpan, unknown type {type}"); } } diff --git a/src/Ocelot/RateLimit/RateLimitCounter.cs b/src/Ocelot/RateLimit/RateLimitCounter.cs index f0133b25..42dd03b7 100644 --- a/src/Ocelot/RateLimit/RateLimitCounter.cs +++ b/src/Ocelot/RateLimit/RateLimitCounter.cs @@ -10,8 +10,14 @@ namespace Ocelot.RateLimit /// public struct RateLimitCounter { - public DateTime Timestamp { get; set; } + public RateLimitCounter(DateTime timestamp, long totalRequest) + { + Timestamp = timestamp; + TotalRequests = totalRequest; + } - public long TotalRequests { get; set; } + public DateTime Timestamp { get; private set; } + + public long TotalRequests { get; private set; } } } diff --git a/src/Ocelot/RateLimit/RateLimitHeaders.cs b/src/Ocelot/RateLimit/RateLimitHeaders.cs index a7bd4ae6..909f656d 100644 --- a/src/Ocelot/RateLimit/RateLimitHeaders.cs +++ b/src/Ocelot/RateLimit/RateLimitHeaders.cs @@ -8,12 +8,20 @@ namespace Ocelot.RateLimit { public class RateLimitHeaders { - public HttpContext Context { get; set; } + public RateLimitHeaders(HttpContext context, string limit, string remaining, string reset) + { + Context = context; + Limit = limit; + Remaining = remaining; + Reset = reset; + } - public string Limit { get; set; } + public HttpContext Context { get; private set; } - public string Remaining { get; set; } + public string Limit { get; private set; } - public string Reset { get; set; } + public string Remaining { get; private set; } + + public string Reset { get; private set; } } } diff --git a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs index 2e8c3c90..dc2b2b31 100644 --- a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs +++ b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs @@ -67,7 +67,9 @@ namespace Ocelot.AcceptanceTests ClientIdHeader = "ClientId", DisableRateLimitHeaders = false, QuotaExceededMessage = "", - RateLimitCounterPrefix = "" + RateLimitCounterPrefix = "", + HttpStatusCode = 428 + }, RequestIdKey ="oceclientrequest" } @@ -76,8 +78,12 @@ namespace Ocelot.AcceptanceTests this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/api/ClientRateLimit")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 5)) - .Then(x => _steps.ThenTheStatusCodeShouldBe(429)) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 2)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(200)) + .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit",1)) + .Then(x => _steps.ThenTheStatusCodeShouldBe(428)) .BDDfy(); } @@ -154,55 +160,6 @@ namespace Ocelot.AcceptanceTests _builder.Start(); } - //private void GetApiRateLimait(string url) - //{ - // var clientId = "ocelotclient1"; - // var request = new HttpRequestMessage(new HttpMethod("GET"), url); - // request.Headers.Add("ClientId", clientId); - - // var response = _client.SendAsync(request); - // responseStatusCode = (int)response.Result.StatusCode; - // } - - //} - - //public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times) - //{ - // var clientId = "ocelotclient1"; - // var tasks = new Task[times]; - - // for (int i = 0; i < times; i++) - // { - // var urlCopy = url; - // tasks[i] = GetForServiceDiscoveryTest(urlCopy); - // Thread.Sleep(_random.Next(40, 60)); - // } - - // Task.WaitAll(tasks); - //} - - //private void WhenICallTheMiddlewareWithWhiteClient() - //{ - // var clientId = "ocelotclient2"; - // // Act - // for (int i = 0; i < 2; i++) - // { - // var request = new HttpRequestMessage(new HttpMethod("GET"), apiRateLimitPath); - // request.Headers.Add("ClientId", clientId); - - // var response = _client.SendAsync(request); - // responseStatusCode = (int)response.Result.StatusCode; - // } - //} - - //private void ThenresponseStatusCodeIs429() - //{ - // responseStatusCode.ShouldBe(429); - //} - - //private void ThenresponseStatusCodeIs200() - //{ - // responseStatusCode.ShouldBe(200); - //} + } } \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 5981c343..ebced901 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -101,7 +101,6 @@ namespace Ocelot.AcceptanceTests }) .WithDictionaryHandle(); }; - s.AddMemoryCache(); s.AddOcelotOutputCaching(settings); s.AddOcelotFileConfiguration(configuration); s.AddOcelot(); diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index af289aab..0a65abc2 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null,"RateLimitOptions":{"ClientWhitelist":[],"EnableRateLimiting":false,"Period":null,"PeriodTimespan":0,"Limit":0}}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"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},"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index 5457280f..2dd3094e 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -23,7 +23,6 @@ "Microsoft.AspNetCore.Http": "1.1.0", "Microsoft.DotNet.InternalAbstractions": "1.0.0", "Ocelot": "0.0.0-dev", - "xunit": "2.2.0-beta2-build3300", "dotnet-test-xunit": "2.2.0-preview2-build1029", "Ocelot.ManualTest": "0.0.0-dev", "Microsoft.AspNetCore.TestHost": "1.1.0", @@ -34,7 +33,8 @@ "Shouldly": "2.8.2", "TestStack.BDDfy": "4.3.2", "Consul": "0.7.2.1", - "Microsoft.Extensions.Caching.Memory": "1.1.0" + "Microsoft.Extensions.Caching.Memory": "1.1.0", + "xunit": "2.2.0-rc1-build3507" }, "runtimes": { "win10-x64": {}, diff --git a/test/Ocelot.ManualTest/Startup.cs b/test/Ocelot.ManualTest/Startup.cs index 2d8b7653..767aed43 100644 --- a/test/Ocelot.ManualTest/Startup.cs +++ b/test/Ocelot.ManualTest/Startup.cs @@ -37,7 +37,6 @@ namespace Ocelot.ManualTest }) .WithDictionaryHandle(); }; - services.AddMemoryCache(); services.AddOcelotOutputCaching(settings); services.AddOcelotFileConfiguration(Configuration); services.AddOcelot(); diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index 11bca113..72f66faa 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -20,6 +20,7 @@ using Xunit; using TestStack.BDDfy; using Ocelot.Configuration.Builder; using Shouldly; +using Ocelot.Configuration; namespace Ocelot.UnitTests.RateLimit { @@ -70,11 +71,13 @@ namespace Ocelot.UnitTests.RateLimit { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( - new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List(), false, "", "", new Ocelot.Configuration.RateLimitRule() { Limit = 3, Period = "1s", PeriodTimespan = TimeSpan.FromSeconds(100) },429)) + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List(), false, "", "", new Ocelot.Configuration.RateLimitRule("1s", TimeSpan.FromSeconds(100), 3), 429)) .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .When(x => x.WhenICallTheMiddleware()) + .When(x => x.WhenICallTheMiddlewareMultipleTime(2)) + .Then(x => x.ThenresponseStatusCodeIs200()) + .When(x => x.WhenICallTheMiddlewareMultipleTime(2)) .Then(x => x.ThenresponseStatusCodeIs429()) .BDDfy(); } @@ -84,7 +87,7 @@ namespace Ocelot.UnitTests.RateLimit { var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( - new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new Ocelot.Configuration.RateLimitRule() { Limit = 3, Period = "1s", PeriodTimespan = TimeSpan.FromSeconds(100) },429)) + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new RateLimitRule( "1s", TimeSpan.FromSeconds(100),3),429)) .Build()); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) @@ -102,11 +105,11 @@ namespace Ocelot.UnitTests.RateLimit .Returns(_downstreamRoute); } - private void WhenICallTheMiddleware() + private void WhenICallTheMiddlewareMultipleTime(int times) { var clientId = "ocelotclient1"; // Act - for (int i = 0; i <10; i++) + for (int i = 0; i < times; i++) { var request = new HttpRequestMessage(new HttpMethod("GET"), _url); request.Headers.Add("ClientId", clientId); diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index 3151ac57..2f42c283 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -14,7 +14,6 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", "Microsoft.AspNetCore.Http": "1.1.0", "Ocelot": "0.0.0-dev", - "xunit": "2.2.0-beta2-build3300", "dotnet-test-xunit": "2.2.0-preview2-build1029", "Moq": "4.6.38-alpha", "Microsoft.AspNetCore.TestHost": "1.1.0", @@ -24,7 +23,8 @@ "Shouldly": "2.8.2", "TestStack.BDDfy": "4.3.2", "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", - "Microsoft.DotNet.InternalAbstractions": "1.0.0" + "Microsoft.DotNet.InternalAbstractions": "1.0.0", + "xunit": "2.2.0-rc1-build3507" }, "runtimes": { "win10-x64": {}, From 0b830d989160169d90c83482215b59d4e070dad7 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 13 Feb 2017 07:42:31 +0000 Subject: [PATCH 079/113] mapped admin path works for manual test --- src/Ocelot/Controllers/ReRoutesController.cs | 13 ++++++ .../Middleware/OcelotMiddlewareExtensions.cs | 9 +++++ test/Ocelot.ManualTest/configuration.json | 40 +++++++++---------- test/Ocelot.ManualTest/project.json | 4 +- 4 files changed, 45 insertions(+), 21 deletions(-) create mode 100644 src/Ocelot/Controllers/ReRoutesController.cs diff --git a/src/Ocelot/Controllers/ReRoutesController.cs b/src/Ocelot/Controllers/ReRoutesController.cs new file mode 100644 index 00000000..0b02c796 --- /dev/null +++ b/src/Ocelot/Controllers/ReRoutesController.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Ocelot.Controllers +{ + [RouteAttribute("reroutes")] + public class ReRoutesController + { + public IActionResult Get() + { + return new OkObjectResult("hi from re routes controller"); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 352aa501..afc080a0 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -31,7 +31,16 @@ namespace Ocelot.Middleware public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder) { CreateConfiguration(builder); + + builder.Map("/admin", x => + { + x.UseMvc(); + }); + builder.UseOcelot(new OcelotMiddlewareConfiguration()); + + + return builder; } diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index 9b5fcb7e..fc0b6567 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -5,7 +5,7 @@ "DownstreamScheme": "http", "DownstreamHost": "localhost", "DownstreamPort": 52876, - "UpstreamTemplate": "/identityserverexample", + "UpstreamPathTemplate": "/identityserverexample", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -50,7 +50,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts", + "UpstreamPathTemplate": "/posts", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -63,7 +63,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -76,7 +76,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}/comments", + "UpstreamPathTemplate": "/posts/{postId}/comments", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -89,7 +89,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/comments", + "UpstreamPathTemplate": "/comments", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -102,7 +102,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts", + "UpstreamPathTemplate": "/posts", "UpstreamHttpMethod": "Post", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -115,7 +115,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Put", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -128,7 +128,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Patch", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -141,7 +141,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Delete", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -154,7 +154,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/products", + "UpstreamPathTemplate": "/products", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -168,7 +168,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", + "UpstreamPathTemplate": "/products/{productId}", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, @@ -177,7 +177,7 @@ "DownstreamScheme": "http", "DownstreamHost": "products20161126090340.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/products", + "UpstreamPathTemplate": "/products", "UpstreamHttpMethod": "Post", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -190,7 +190,7 @@ "DownstreamScheme": "http", "DownstreamHost": "products20161126090340.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", + "UpstreamPathTemplate": "/products/{productId}", "UpstreamHttpMethod": "Put", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -204,7 +204,7 @@ "DownstreamScheme": "http", "DownstreamHost": "products20161126090340.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", + "UpstreamPathTemplate": "/products/{productId}", "UpstreamHttpMethod": "Delete", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -218,7 +218,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers", + "UpstreamPathTemplate": "/customers", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -232,7 +232,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", + "UpstreamPathTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -246,7 +246,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers", + "UpstreamPathTemplate": "/customers", "UpstreamHttpMethod": "Post", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -260,7 +260,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", + "UpstreamPathTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Put", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -274,7 +274,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", + "UpstreamPathTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Delete", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -288,7 +288,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/", + "UpstreamPathTemplate": "/posts/", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index cf67f9bd..04dba415 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -12,7 +12,9 @@ "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", "Ocelot": "0.0.0-dev", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", - "Microsoft.NETCore.App": "1.1.0" + "Microsoft.NETCore.App": "1.1.0", + "Consul": "0.7.2.1", + "Polly": "5.0.3" }, "tools": { From 95fc687e9338f27d8c18c44d153d9d4649fb479b Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 13 Feb 2017 12:13:53 +0000 Subject: [PATCH 080/113] started messing around with admin area --- .../Creator/FileOcelotConfigurationCreator.cs | 2 +- .../File/FileGlobalConfiguration.cs | 1 + src/Ocelot/Configuration/IOcelotConfiguration.cs | 1 + src/Ocelot/Configuration/OcelotConfiguration.cs | 4 +++- .../Middleware/OcelotMiddlewareExtensions.cs | 16 ++++++++++------ test/Ocelot.ManualTest/configuration.json | 3 ++- .../FileConfigurationProviderTests.cs | 8 ++++---- .../InMemoryConfigurationRepositoryTests.cs | 8 ++++++++ .../DownstreamRouteFinderTests.cs | 13 +++++++------ 9 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 380e193f..8025f59d 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -85,7 +85,7 @@ namespace Ocelot.Configuration.Creator reRoutes.Add(ocelotReRoute); } - return new OcelotConfiguration(reRoutes); + return new OcelotConfiguration(reRoutes, _options.Value.GlobalConfiguration.AdministrationPath); } private async Task SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs index f414bc83..fd0f41a9 100644 --- a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs @@ -8,5 +8,6 @@ } public string RequestIdKey { get; set; } public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;} + public string AdministrationPath {get;set;} } } diff --git a/src/Ocelot/Configuration/IOcelotConfiguration.cs b/src/Ocelot/Configuration/IOcelotConfiguration.cs index 8359a2e1..566e2f91 100644 --- a/src/Ocelot/Configuration/IOcelotConfiguration.cs +++ b/src/Ocelot/Configuration/IOcelotConfiguration.cs @@ -5,5 +5,6 @@ namespace Ocelot.Configuration public interface IOcelotConfiguration { List ReRoutes { get; } + string AdministrationPath {get;} } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/OcelotConfiguration.cs b/src/Ocelot/Configuration/OcelotConfiguration.cs index 3b0858eb..5655b3c2 100644 --- a/src/Ocelot/Configuration/OcelotConfiguration.cs +++ b/src/Ocelot/Configuration/OcelotConfiguration.cs @@ -4,11 +4,13 @@ namespace Ocelot.Configuration { public class OcelotConfiguration : IOcelotConfiguration { - public OcelotConfiguration(List reRoutes) + public OcelotConfiguration(List reRoutes, string administrationPath) { ReRoutes = reRoutes; + AdministrationPath = administrationPath; } public List ReRoutes { get; } + public string AdministrationPath {get;} } } \ No newline at end of file diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index afc080a0..5ba19ca5 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -18,6 +18,7 @@ namespace Ocelot.Middleware using System.Threading.Tasks; using Authorisation.Middleware; using Microsoft.AspNetCore.Http; + using Ocelot.Configuration; using Ocelot.Configuration.Provider; using Ocelot.LoadBalancer.Middleware; @@ -30,9 +31,9 @@ namespace Ocelot.Middleware /// public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder) { - CreateConfiguration(builder); + var configuration = CreateConfiguration(builder).Result; - builder.Map("/admin", x => + builder.Map(configuration.AdministrationPath, x => { x.UseMvc(); }); @@ -131,16 +132,19 @@ namespace Ocelot.Middleware return builder; } - private static void CreateConfiguration(IApplicationBuilder builder) + private static async Task CreateConfiguration(IApplicationBuilder builder) { var configProvider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider)); - var config = configProvider.Get(); + var config = await configProvider.Get(); - if(config == null) + //todo move this to config validators + if(config == null || config.Data == null || config.IsError) { - throw new Exception("Unable to start Ocelot: configuration was null"); + throw new Exception("Unable to start Ocelot: configuration was invalid"); } + + return config.Data; } private static void UseIfNotNull(this IApplicationBuilder builder, Func, Task> middleware) diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index fc0b6567..f6f3c787 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -300,6 +300,7 @@ ], "GlobalConfiguration": { - "RequestIdKey": "OcRequestId" + "RequestIdKey": "OcRequestId", + "AdministrationPath": "/admin" } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs index 98e01293..2573e32f 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs @@ -29,9 +29,9 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void should_get_config() { - this.Given(x => x.GivenTheRepoReturns(new OkResponse(new OcelotConfiguration(new List())))) + this.Given(x => x.GivenTheRepoReturns(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) .When(x => x.WhenIGetTheConfig()) - .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List())))) + .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) .BDDfy(); } @@ -39,9 +39,9 @@ namespace Ocelot.UnitTests.Configuration public void should_create_config_if_it_doesnt_exist() { this.Given(x => x.GivenTheRepoReturns(new OkResponse(null))) - .And(x => x.GivenTheCreatorReturns(new OkResponse(new OcelotConfiguration(new List())))) + .And(x => x.GivenTheCreatorReturns(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) .When(x => x.WhenIGetTheConfig()) - .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List())))) + .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) .BDDfy(); } diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index 9f437204..8e0e66d3 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -82,6 +82,14 @@ namespace Ocelot.UnitTests.Configuration _downstreamTemplatePath = downstreamTemplatePath; } + public string AdministrationPath + { + get + { + throw new NotImplementedException(); + } + } + public List ReRoutes => new List { new ReRouteBuilder() diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 2fb8f28f..0d272c63 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -48,7 +48,8 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("someUpstreamPath") .Build() - })) + }, string.Empty + )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) @@ -80,7 +81,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("someUpstreamPath") .Build() - } + }, string.Empty )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) @@ -118,7 +119,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithUpstreamHttpMethod("Post") .WithUpstreamTemplatePattern("") .Build() - } + }, string.Empty )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) @@ -145,7 +146,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithUpstreamHttpMethod("Get") .WithUpstreamTemplatePattern("somePath") .Build(), - } + }, string.Empty )) .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(false)))) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) @@ -193,12 +194,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .Returns(_match); } - private void GivenTheConfigurationIs(List reRoutesConfig) + private void GivenTheConfigurationIs(List reRoutesConfig, string adminPath) { _reRoutesConfig = reRoutesConfig; _mockConfig .Setup(x => x.Get()) - .ReturnsAsync(new OkResponse(new OcelotConfiguration(_reRoutesConfig))); + .ReturnsAsync(new OkResponse(new OcelotConfiguration(_reRoutesConfig, adminPath))); } private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) From 159969483b64c5491b1d86b1aa4dac7b4b2a3ba1 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 13 Feb 2017 18:51:47 +0000 Subject: [PATCH 081/113] hacking away --- .../Middleware/OcelotMiddlewareExtensions.cs | 30 +++++++++++-------- .../ReturnsErrorTests.cs | 9 ++++-- .../TestConfiguration.cs | 2 +- test/Ocelot.ManualTest/Startup.cs | 5 ++-- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 5ba19ca5..26b1638f 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -29,31 +29,37 @@ namespace Ocelot.Middleware /// /// /// - public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder) + public static async Task UseOcelot(this IApplicationBuilder builder) { - var configuration = CreateConfiguration(builder).Result; - - builder.Map(configuration.AdministrationPath, x => - { - x.UseMvc(); - }); - - builder.UseOcelot(new OcelotMiddlewareConfiguration()); - + await CreateAdministrationArea(builder); + await builder.UseOcelot(new OcelotMiddlewareConfiguration()); return builder; } + private static async Task CreateAdministrationArea(IApplicationBuilder builder) + { + var configuration = await CreateConfiguration(builder); + + if(!string.IsNullOrEmpty(configuration.AdministrationPath)) + { + builder.Map(configuration.AdministrationPath, x => + { + x.UseMvc(); + }); + } + } + /// /// Registers Ocelot with a combination of default middlewares and optional middlewares in the configuration /// /// /// /// - public static IApplicationBuilder UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) + public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) { - CreateConfiguration(builder); + await CreateAdministrationArea(builder); // This is registered to catch any global exceptions that are not handled builder.UseExceptionHandlerMiddleware(); diff --git a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs index f9222b86..0d81b6df 100644 --- a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs +++ b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs @@ -21,7 +21,7 @@ namespace Ocelot.AcceptanceTests } [Fact] - public void should_return_response_200_and_foward_claim_as_header() + public void should_return_server_error() { var configuration = new FileConfiguration { @@ -29,9 +29,12 @@ namespace Ocelot.AcceptanceTests { new FileReRoute { - DownstreamPathTemplate = "http://localhost:53876/", + DownstreamPathTemplate = "/", UpstreamPathTemplate = "/", - UpstreamHttpMethod = "Get" + UpstreamHttpMethod = "Get", + DownstreamPort = 53876, + DownstreamHost = "localhost", + DownstreamScheme = "http", } } }; diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index ce802efb..6784391c 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -28,7 +28,7 @@ { var runTime = $"{oSDescription}-{osArchitecture}".ToLower(); - var configPath = $"./bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; + var configPath = $"./test/Ocelot.AcceptanceTests/bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; return configPath; } diff --git a/test/Ocelot.ManualTest/Startup.cs b/test/Ocelot.ManualTest/Startup.cs index 70448fcf..b187f6b4 100644 --- a/test/Ocelot.ManualTest/Startup.cs +++ b/test/Ocelot.ManualTest/Startup.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using CacheManager.Core; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -43,11 +44,11 @@ namespace Ocelot.ManualTest services.AddOcelot(); } - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); - app.UseOcelot(); + await app.UseOcelot(); } } } From ef3deec8da78fd282f6b5f2bff8e6d6853496c31 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 13 Feb 2017 19:14:49 +0000 Subject: [PATCH 082/113] more hacking --- test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs | 4 ++-- test/Ocelot.AcceptanceTests/TestConfiguration.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs index 0d81b6df..868cb39f 100644 --- a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs +++ b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs @@ -21,7 +21,7 @@ namespace Ocelot.AcceptanceTests } [Fact] - public void should_return_server_error() + public void should_return_internal_server_error_if_downstream_service_returns_internal_server_error() { var configuration = new FileConfiguration { @@ -33,8 +33,8 @@ namespace Ocelot.AcceptanceTests UpstreamPathTemplate = "/", UpstreamHttpMethod = "Get", DownstreamPort = 53876, - DownstreamHost = "localhost", DownstreamScheme = "http", + DownstreamHost = "localhost" } } }; diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index 6784391c..ce802efb 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -28,7 +28,7 @@ { var runTime = $"{oSDescription}-{osArchitecture}".ToLower(); - var configPath = $"./test/Ocelot.AcceptanceTests/bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; + var configPath = $"./bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; return configPath; } From ab6ae8a062022f0a94b1d1a34e8797a2d4250e9b Mon Sep 17 00:00:00 2001 From: geffzhang Date: Wed, 15 Feb 2017 08:50:22 +0800 Subject: [PATCH 083/113] merge newest code for develop --- .../Configuration/Builder/ReRouteBuilder.cs | 129 +++------ .../Creator/FileOcelotConfigurationCreator.cs | 247 ++++++++++++------ src/Ocelot/Configuration/QoSOptions.cs | 10 +- src/Ocelot/Configuration/ReRoute.cs | 41 ++- 4 files changed, 241 insertions(+), 186 deletions(-) diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 028b89e3..2e442701 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -1,23 +1,19 @@ using System; using System.Collections.Generic; +using System.Net.Http; using Ocelot.Values; namespace Ocelot.Configuration.Builder { public class ReRouteBuilder { + private AuthenticationOptions _authenticationOptions; private string _loadBalancerKey; private string _downstreamPathTemplate; private string _upstreamTemplate; private string _upstreamTemplatePattern; private string _upstreamHttpMethod; private bool _isAuthenticated; - private string _authenticationProvider; - private string _authenticationProviderUrl; - private string _scopeName; - private List _additionalScopes; - private bool _requireHttps; - private string _scopeSecret; private List _configHeaderExtractorProperties; private List _claimToClaims; private Dictionary _routeClaimRequirement; @@ -26,29 +22,19 @@ namespace Ocelot.Configuration.Builder private string _requestIdHeaderKey; private bool _isCached; private CacheOptions _fileCacheOptions; - private bool _useServiceDiscovery; - private string _serviceName; - private string _serviceDiscoveryProvider; - private string _serviceDiscoveryAddress; private string _downstreamScheme; private string _downstreamHost; - private int _dsPort; + private int _downstreamPort; private string _loadBalancer; - private string _serviceProviderHost; - private int _serviceProviderPort; + private ServiceProviderConfiguraion _serviceProviderConfiguraion; private bool _useQos; private QoSOptions _qosOptions; public bool _enableRateLimiting; public RateLimitOptions _rateLimitOptions; - public ReRouteBuilder() - { - _additionalScopes = new List(); - } - public ReRouteBuilder WithLoadBalancer(string loadBalancer) { - _loadBalancer = loadBalancer; + _loadBalancer = loadBalancer; return this; } @@ -64,37 +50,13 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithServiceDiscoveryAddress(string serviceDiscoveryAddress) - { - _serviceDiscoveryAddress = serviceDiscoveryAddress; - return this; - } - - public ReRouteBuilder WithServiceDiscoveryProvider(string serviceDiscoveryProvider) - { - _serviceDiscoveryProvider = serviceDiscoveryProvider; - return this; - } - - public ReRouteBuilder WithServiceName(string serviceName) - { - _serviceName = serviceName; - return this; - } - - public ReRouteBuilder WithUseServiceDiscovery(bool useServiceDiscovery) - { - _useServiceDiscovery = useServiceDiscovery; - return this; - } - public ReRouteBuilder WithDownstreamPathTemplate(string input) { _downstreamPathTemplate = input; return this; } - public ReRouteBuilder WithUpstreamTemplate(string input) + public ReRouteBuilder WithUpstreamPathTemplate(string input) { _upstreamTemplate = input; return this; @@ -122,42 +84,6 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithAuthenticationProvider(string input) - { - _authenticationProvider = input; - return this; - } - - public ReRouteBuilder WithAuthenticationProviderUrl(string input) - { - _authenticationProviderUrl = input; - return this; - } - - public ReRouteBuilder WithAuthenticationProviderScopeName(string input) - { - _scopeName = input; - return this; - } - - public ReRouteBuilder WithAuthenticationProviderAdditionalScopes(List input) - { - _additionalScopes = input; - return this; - } - - public ReRouteBuilder WithRequireHttps(bool input) - { - _requireHttps = input; - return this; - } - - public ReRouteBuilder WithScopeSecret(string input) - { - _scopeSecret = input; - return this; - } - public ReRouteBuilder WithRequestIdKey(string input) { _requestIdHeaderKey = input; @@ -202,7 +128,7 @@ namespace Ocelot.Configuration.Builder public ReRouteBuilder WithDownstreamPort(int port) { - _dsPort = port; + _downstreamPort = port; return this; } @@ -225,15 +151,15 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithServiceProviderHost(string serviceProviderHost) + public ReRouteBuilder WithServiceProviderConfiguraion(ServiceProviderConfiguraion serviceProviderConfiguraion) { - _serviceProviderHost = serviceProviderHost; + _serviceProviderConfiguraion = serviceProviderConfiguraion; return this; } - public ReRouteBuilder WithServiceProviderPort(int serviceProviderPort) + public ReRouteBuilder WithAuthenticationOptions(AuthenticationOptions authenticationOptions) { - _serviceProviderPort = serviceProviderPort; + _authenticationOptions = authenticationOptions; return this; } @@ -252,14 +178,31 @@ namespace Ocelot.Configuration.Builder public ReRoute Build() { - return new ReRoute(new DownstreamPathTemplate(_downstreamPathTemplate), _upstreamTemplate, _upstreamHttpMethod, _upstreamTemplatePattern, - _isAuthenticated, new AuthenticationOptions(_authenticationProvider, _authenticationProviderUrl, _scopeName, - _requireHttps, _additionalScopes, _scopeSecret), _configHeaderExtractorProperties, _claimToClaims, _routeClaimRequirement, - _isAuthorised, _claimToQueries, _requestIdHeaderKey, _isCached, _fileCacheOptions, _downstreamScheme, _loadBalancer, - _downstreamHost, _dsPort, _loadBalancerKey, new ServiceProviderConfiguraion(_serviceName, _downstreamHost, _dsPort, _useServiceDiscovery, - _serviceDiscoveryProvider, _serviceProviderHost, _serviceProviderPort), - _useQos,_qosOptions,_enableRateLimiting,_rateLimitOptions); - + return new ReRoute( + new PathTemplate(_downstreamPathTemplate), + new PathTemplate(_upstreamTemplate), + new HttpMethod(_upstreamHttpMethod), + _upstreamTemplatePattern, + _isAuthenticated, + _authenticationOptions, + _configHeaderExtractorProperties, + _claimToClaims, + _routeClaimRequirement, + _isAuthorised, + _claimToQueries, + _requestIdHeaderKey, + _isCached, + _fileCacheOptions, + _downstreamScheme, + _loadBalancer, + _downstreamHost, + _downstreamPort, + _loadBalancerKey, + _serviceProviderConfiguraion, + _useQos, + _qosOptions, + _enableRateLimiting, + _rateLimitOptions); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 646af96c..55f31bfa 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -4,13 +4,14 @@ using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; using Ocelot.Configuration.Validator; using Ocelot.LoadBalancer.LoadBalancers; +using Ocelot.Requester.QoS; using Ocelot.Responses; using Ocelot.Utilities; -using Ocelot.Values; namespace Ocelot.Configuration.Creator { @@ -24,11 +25,14 @@ namespace Ocelot.Configuration.Creator 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; public FileOcelotConfigurationCreator( IOptions options, @@ -36,10 +40,14 @@ namespace Ocelot.Configuration.Creator IClaimToThingConfigurationParser claimToThingConfigurationParser, ILogger logger, ILoadBalancerFactory loadBalancerFactory, - ILoadBalancerHouse loadBalancerHouse) + ILoadBalancerHouse loadBalancerHouse, + IQoSProviderFactory qoSProviderFactory, + IQosProviderHouse qosProviderHouse) { _loadBalanceFactory = loadBalancerFactory; _loadBalancerHouse = loadBalancerHouse; + _qoSProviderFactory = qoSProviderFactory; + _qosProviderHouse = qosProviderHouse; _options = options; _configurationValidator = configurationValidator; _claimToThingConfigurationParser = claimToThingConfigurationParser; @@ -53,10 +61,6 @@ namespace Ocelot.Configuration.Creator return new OkResponse(config); } - /// - /// This method is meant to be tempoary to convert a config to an ocelot config...probably wont keep this but we will see - /// will need a refactor at some point as its crap - /// private async Task SetUpConfiguration() { var response = _configurationValidator.IsValid(_options.Value); @@ -86,93 +90,179 @@ namespace Ocelot.Configuration.Creator private async Task SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) { - var globalRequestIdConfiguration = !string.IsNullOrEmpty(globalConfiguration?.RequestIdKey); + var isAuthenticated = IsAuthenticated(fileReRoute); - var upstreamTemplate = BuildUpstreamTemplate(fileReRoute); + var isAuthorised = IsAuthorised(fileReRoute); - var isAuthenticated = !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); + var isCached = IsCached(fileReRoute); - var isAuthorised = fileReRoute.RouteClaimsRequirement?.Count > 0; + var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration); - var isCached = fileReRoute.FileCacheOptions.TtlSeconds > 0; + var reRouteKey = BuildReRouteKey(fileReRoute); - var isQos = fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions.TimeoutValue >0; + var upstreamTemplatePattern = BuildUpstreamTemplatePattern(fileReRoute); - var requestIdKey = globalRequestIdConfiguration - ? globalConfiguration.RequestIdKey - : fileReRoute.RequestIdKey; + var isQos = IsQoS(fileReRoute); - var useServiceDiscovery = !string.IsNullOrEmpty(fileReRoute.ServiceName) - && !string.IsNullOrEmpty(globalConfiguration?.ServiceDiscoveryProvider?.Provider); + var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration); - //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.UpstreamTemplate}{fileReRoute.UpstreamHttpMethod}"; + var authOptionsForRoute = BuildAuthenticationOptions(fileReRoute); - ReRoute reRoute; + var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest); - var enableRateLimiting = (fileReRoute.RateLimitOptions!= null && fileReRoute.RateLimitOptions.EnableRateLimiting)? true: false; - 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); - } - var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; + var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest); - var serviceProviderConfiguration = new ServiceProviderConfiguraion(fileReRoute.ServiceName, - fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, useServiceDiscovery, - globalConfiguration?.ServiceDiscoveryProvider?.Provider, globalConfiguration?.ServiceDiscoveryProvider?.Host, - serviceProviderPort); + var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest); - if (isAuthenticated) - { - var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider, - fileReRoute.AuthenticationOptions.ProviderRootUrl, fileReRoute.AuthenticationOptions.ScopeName, - fileReRoute.AuthenticationOptions.RequireHttps, fileReRoute.AuthenticationOptions.AdditionalScopes, - fileReRoute.AuthenticationOptions.ScopeSecret); + var qosOptions = BuildQoSOptions(fileReRoute); - var claimsToHeaders = GetAddThingsToRequest(fileReRoute.AddHeadersToRequest); - var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest); - var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest); + var enableRateLimiting = IsEnableRateLimiting(fileReRoute); - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), - fileReRoute.UpstreamTemplate, - fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, - authOptionsForRoute, claimsToHeaders, claimsToClaims, - fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, - requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds) - , fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, - serviceProviderConfiguration, isQos, - new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue), - enableRateLimiting, rateLimitOption); - } - else - { - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), - fileReRoute.UpstreamTemplate, - fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, - null, new List(), new List(), - fileReRoute.RouteClaimsRequirement, isAuthorised, new List(), - requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), - fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, - serviceProviderConfiguration, isQos, - new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue), - enableRateLimiting, rateLimitOption); - } - - var loadBalancer = await _loadBalanceFactory.Get(reRoute); - _loadBalancerHouse.Add(reRoute.LoadBalancerKey, loadBalancer); + var rateLimitOption = BuildRateLimitOptions(fileReRoute, globalConfiguration, enableRateLimiting); + + var reRoute = new ReRouteBuilder() + .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) + .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) + .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) + .WithUpstreamTemplatePattern(upstreamTemplatePattern) + .WithIsAuthenticated(isAuthenticated) + .WithAuthenticationOptions(authOptionsForRoute) + .WithClaimsToHeaders(claimsToHeaders) + .WithClaimsToClaims(claimsToClaims) + .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) + .WithIsAuthorised(isAuthorised) + .WithClaimsToQueries(claimsToQueries) + .WithRequestIdKey(requestIdKey) + .WithIsCached(isCached) + .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)) + .WithDownstreamScheme(fileReRoute.DownstreamScheme) + .WithLoadBalancer(fileReRoute.LoadBalancer) + .WithDownstreamHost(fileReRoute.DownstreamHost) + .WithDownstreamPort(fileReRoute.DownstreamPort) + .WithLoadBalancerKey(reRouteKey) + .WithServiceProviderConfiguraion(serviceProviderConfiguration) + .WithIsQos(isQos) + .WithQosOptions(qosOptions) + .WithEnableRateLimiting(enableRateLimiting) + .WithRateLimitOptions(rateLimitOption) + .Build(); + await SetupLoadBalancer(reRoute); + SetupQosProvider(reRoute); return reRoute; } - private string BuildUpstreamTemplate(FileReRoute reRoute) + private static RateLimitOptions BuildRateLimitOptions(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting) { - var upstreamTemplate = reRoute.UpstreamTemplate; + 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 static bool IsEnableRateLimiting(FileReRoute fileReRoute) + { + 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; + } + + 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 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 + var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}"; + 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); + _loadBalancerHouse.Add(reRoute.ReRouteKey, loadBalancer); + } + + private void SetupQosProvider(ReRoute reRoute) + { + var loadBalancer = _qoSProviderFactory.Get(reRoute); + _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 string BuildUpstreamTemplatePattern(FileReRoute reRoute) + { + var upstreamTemplate = reRoute.UpstreamPathTemplate; upstreamTemplate = upstreamTemplate.SetLastCharacterAs('/'); @@ -194,6 +284,11 @@ namespace Ocelot.Configuration.Creator upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything); } + if (upstreamTemplate == "/") + { + return RegExForwardSlashOnly; + } + var route = reRoute.ReRouteIsCaseSensitive ? $"{upstreamTemplate}{RegExMatchEndString}" : $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; @@ -201,7 +296,7 @@ namespace Ocelot.Configuration.Creator return route; } - private List GetAddThingsToRequest(Dictionary thingBeingAdded) + private List BuildAddThingsToRequest(Dictionary thingBeingAdded) { var claimsToTHings = new List(); diff --git a/src/Ocelot/Configuration/QoSOptions.cs b/src/Ocelot/Configuration/QoSOptions.cs index 27b862af..29145888 100644 --- a/src/Ocelot/Configuration/QoSOptions.cs +++ b/src/Ocelot/Configuration/QoSOptions.cs @@ -1,11 +1,15 @@ -using Polly.Timeout; -using System; +using System; +using Polly.Timeout; namespace Ocelot.Configuration { public class QoSOptions { - public QoSOptions(int exceptionsAllowedBeforeBreaking, int durationofBreak, int timeoutValue, TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) + public QoSOptions( + int exceptionsAllowedBeforeBreaking, + int durationofBreak, + int timeoutValue, + TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) { ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak); diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 38e28af9..bfce7c76 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -1,31 +1,44 @@ using System; using System.Collections.Generic; +using System.Net.Http; using Ocelot.Values; namespace Ocelot.Configuration { public class ReRoute { - public ReRoute(DownstreamPathTemplate downstreamPathTemplate, - string upstreamTemplate, string upstreamHttpMethod, + public ReRoute(PathTemplate downstreamPathTemplate, + PathTemplate upstreamTemplate, + HttpMethod upstreamHttpMethod, string upstreamTemplatePattern, - bool isAuthenticated, AuthenticationOptions authenticationOptions, + bool isAuthenticated, + AuthenticationOptions authenticationOptions, List configurationHeaderExtractorProperties, List claimsToClaims, - Dictionary routeClaimsRequirement, bool isAuthorised, + Dictionary routeClaimsRequirement, + bool isAuthorised, List claimsToQueries, - string requestIdKey, bool isCached, CacheOptions fileCacheOptions, - string downstreamScheme, string loadBalancer, string downstreamHost, - int downstreamPort, string loadBalancerKey, ServiceProviderConfiguraion serviceProviderConfiguraion, - bool isQos,QoSOptions qos, bool enableRateLimit, RateLimitOptions ratelimitOptions) + string requestIdKey, + bool isCached, + CacheOptions fileCacheOptions, + string downstreamScheme, + string loadBalancer, + string downstreamHost, + int downstreamPort, + string reRouteKey, + ServiceProviderConfiguraion serviceProviderConfiguraion, + bool isQos, + QoSOptions qos, + bool enableRateLimit, + RateLimitOptions ratelimitOptions) { - LoadBalancerKey = loadBalancerKey; + ReRouteKey = reRouteKey; ServiceProviderConfiguraion = serviceProviderConfiguraion; LoadBalancer = loadBalancer; DownstreamHost = downstreamHost; DownstreamPort = downstreamPort; DownstreamPathTemplate = downstreamPathTemplate; - UpstreamTemplate = upstreamTemplate; + UpstreamPathTemplate = upstreamTemplate; UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; IsAuthenticated = isAuthenticated; @@ -48,11 +61,11 @@ namespace Ocelot.Configuration RateLimitOptions = ratelimitOptions; } - public string LoadBalancerKey {get;private set;} - public DownstreamPathTemplate DownstreamPathTemplate { get; private set; } - public string UpstreamTemplate { get; private set; } + public string ReRouteKey {get;private set;} + public PathTemplate DownstreamPathTemplate { get; private set; } + public PathTemplate UpstreamPathTemplate { get; private set; } public string UpstreamTemplatePattern { get; private set; } - public string UpstreamHttpMethod { get; private set; } + public HttpMethod UpstreamHttpMethod { get; private set; } public bool IsAuthenticated { get; private set; } public bool IsAuthorised { get; private set; } public AuthenticationOptions AuthenticationOptions { get; private set; } From f302ee77bfd2ce6d162d017ff69f1493835cc662 Mon Sep 17 00:00:00 2001 From: geffzhang Date: Wed, 15 Feb 2017 08:54:20 +0800 Subject: [PATCH 084/113] remove bak file --- global.json | 2 +- .../FileOcelotConfigurationCreator.cs.bak | 356 ------------------ src/Ocelot/Configuration/QoSOptions.cs.bak | 35 -- src/Ocelot/Configuration/ReRoute.cs.bak | 94 ----- .../Ocelot.AcceptanceTests/configuration.json | 2 +- 5 files changed, 2 insertions(+), 487 deletions(-) delete mode 100644 src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs.bak delete mode 100644 src/Ocelot/Configuration/QoSOptions.cs.bak delete mode 100644 src/Ocelot/Configuration/ReRoute.cs.bak diff --git a/global.json b/global.json index 616b2c4e..ff8d898e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@  { "projects": [ "src", "test" ], "sdk": { - "version": "1.0.0-preview2-003131" + "version": "1.0.0-preview2-003133" } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs.bak b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs.bak deleted file mode 100644 index 86cd0ff2..00000000 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs.bak +++ /dev/null @@ -1,356 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Parser; -using Ocelot.Configuration.Validator; -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.Requester.QoS; -using Ocelot.Responses; -using Ocelot.Utilities; - -namespace Ocelot.Configuration.Creator -{ - /// - /// Register as singleton - /// - public class FileOcelotConfigurationCreator : IOcelotConfigurationCreator - { - 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; - - public FileOcelotConfigurationCreator( - IOptions options, - IConfigurationValidator configurationValidator, - IClaimToThingConfigurationParser claimToThingConfigurationParser, - ILogger logger, - ILoadBalancerFactory loadBalancerFactory, - ILoadBalancerHouse loadBalancerHouse, - IQoSProviderFactory qoSProviderFactory, - IQosProviderHouse qosProviderHouse) - { - _loadBalanceFactory = loadBalancerFactory; - _loadBalancerHouse = loadBalancerHouse; - _qoSProviderFactory = qoSProviderFactory; - _qosProviderHouse = qosProviderHouse; - _options = options; - _configurationValidator = configurationValidator; - _claimToThingConfigurationParser = claimToThingConfigurationParser; - _logger = logger; - } - - public async Task> Create() - { - var config = await SetUpConfiguration(); - - return new OkResponse(config); - } - - private async Task SetUpConfiguration() - { - var response = _configurationValidator.IsValid(_options.Value); - - if (response.Data.IsError) - { - var errorBuilder = new StringBuilder(); - - foreach (var error in response.Errors) - { - errorBuilder.AppendLine(error.Message); - } - - throw new Exception($"Unable to start Ocelot..configuration, errors were {errorBuilder}"); - } - - var reRoutes = new List(); - - foreach (var reRoute in _options.Value.ReRoutes) - { - var ocelotReRoute = await SetUpReRoute(reRoute, _options.Value.GlobalConfiguration); - reRoutes.Add(ocelotReRoute); - } - - return new OcelotConfiguration(reRoutes); - } - - private async Task SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) - { - var isAuthenticated = IsAuthenticated(fileReRoute); - - var isAuthorised = IsAuthorised(fileReRoute); - - var isCached = IsCached(fileReRoute); - - var requestIdKey = BuildRequestId(fileReRoute, globalConfiguration); - - var reRouteKey = BuildReRouteKey(fileReRoute); - - var upstreamTemplatePattern = BuildUpstreamTemplatePattern(fileReRoute); - - var isQos = IsQoS(fileReRoute); - - var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration); - - var authOptionsForRoute = BuildAuthenticationOptions(fileReRoute); - -<<<<<<< HEAD - ReRoute reRoute; - - var enableRateLimiting = (fileReRoute.RateLimitOptions!= null && fileReRoute.RateLimitOptions.EnableRateLimiting)? true: false; - 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); - } - var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; -======= - var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest); - - var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest); - - var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest); - - var qosOptions = BuildQoSOptions(fileReRoute); - - var reRoute = new ReRouteBuilder() - .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) - .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) - .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) - .WithUpstreamTemplatePattern(upstreamTemplatePattern) - .WithIsAuthenticated(isAuthenticated) - .WithAuthenticationOptions(authOptionsForRoute) - .WithClaimsToHeaders(claimsToHeaders) - .WithClaimsToClaims(claimsToClaims) - .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) - .WithIsAuthorised(isAuthorised) - .WithClaimsToQueries(claimsToQueries) - .WithRequestIdKey(requestIdKey) - .WithIsCached(isCached) - .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)) - .WithDownstreamScheme(fileReRoute.DownstreamScheme) - .WithLoadBalancer(fileReRoute.LoadBalancer) - .WithDownstreamHost(fileReRoute.DownstreamHost) - .WithDownstreamPort(fileReRoute.DownstreamPort) - .WithLoadBalancerKey(reRouteKey) - .WithServiceProviderConfiguraion(serviceProviderConfiguration) - .WithIsQos(isQos) - .WithQosOptions(qosOptions) - .Build(); - - await SetupLoadBalancer(reRoute); - SetupQosProvider(reRoute); - return reRoute; - } - - private QoSOptions BuildQoSOptions(FileReRoute fileReRoute) - { - return new QoSOptionsBuilder() - .WithExceptionsAllowedBeforeBreaking(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking) - .WithDurationOfBreak(fileReRoute.QoSOptions.DurationOfBreak) - .WithTimeoutValue(fileReRoute.QoSOptions.TimeoutValue) - .Build(); - } ->>>>>>> refs/remotes/origin/develop - - private bool IsQoS(FileReRoute fileReRoute) - { - return fileReRoute.QoSOptions?.ExceptionsAllowedBeforeBreaking > 0 && fileReRoute.QoSOptions?.TimeoutValue > 0; - } - -<<<<<<< HEAD - if (isAuthenticated) - { - var authOptionsForRoute = new AuthenticationOptions(fileReRoute.AuthenticationOptions.Provider, - fileReRoute.AuthenticationOptions.ProviderRootUrl, fileReRoute.AuthenticationOptions.ScopeName, - fileReRoute.AuthenticationOptions.RequireHttps, fileReRoute.AuthenticationOptions.AdditionalScopes, - fileReRoute.AuthenticationOptions.ScopeSecret); - - var claimsToHeaders = GetAddThingsToRequest(fileReRoute.AddHeadersToRequest); - var claimsToClaims = GetAddThingsToRequest(fileReRoute.AddClaimsToRequest); - var claimsToQueries = GetAddThingsToRequest(fileReRoute.AddQueriesToRequest); - - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), - fileReRoute.UpstreamTemplate, - fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, - authOptionsForRoute, claimsToHeaders, claimsToClaims, - fileReRoute.RouteClaimsRequirement, isAuthorised, claimsToQueries, - requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds) - , fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, - serviceProviderConfiguration, isQos, - new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue), - enableRateLimiting, rateLimitOption); - } - else - { - reRoute = new ReRoute(new DownstreamPathTemplate(fileReRoute.DownstreamPathTemplate), - fileReRoute.UpstreamTemplate, - fileReRoute.UpstreamHttpMethod, upstreamTemplate, isAuthenticated, - null, new List(), new List(), - fileReRoute.RouteClaimsRequirement, isAuthorised, new List(), - requestIdKey, isCached, new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds), - fileReRoute.DownstreamScheme, - fileReRoute.LoadBalancer, fileReRoute.DownstreamHost, fileReRoute.DownstreamPort, loadBalancerKey, - serviceProviderConfiguration, isQos, - new QoSOptions(fileReRoute.QoSOptions.ExceptionsAllowedBeforeBreaking, fileReRoute.QoSOptions.DurationOfBreak, fileReRoute.QoSOptions.TimeoutValue), - enableRateLimiting, rateLimitOption); - } -======= - private bool IsAuthenticated(FileReRoute fileReRoute) - { - return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.Provider); - } ->>>>>>> refs/remotes/origin/develop - - private bool IsAuthorised(FileReRoute fileReRoute) - { - return fileReRoute.RouteClaimsRequirement?.Count > 0; - } - - private bool IsCached(FileReRoute fileReRoute) - { - 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 - var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}{fileReRoute.UpstreamHttpMethod}"; - 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); - _loadBalancerHouse.Add(reRoute.ReRouteKey, loadBalancer); - } - - private void SetupQosProvider(ReRoute reRoute) - { - var loadBalancer = _qoSProviderFactory.Get(reRoute); - _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 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] == '{'; - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Configuration/QoSOptions.cs.bak b/src/Ocelot/Configuration/QoSOptions.cs.bak deleted file mode 100644 index 8e41e7d7..00000000 --- a/src/Ocelot/Configuration/QoSOptions.cs.bak +++ /dev/null @@ -1,35 +0,0 @@ -<<<<<<< HEAD -using Polly.Timeout; -using System; -======= -using System; -using Polly.Timeout; ->>>>>>> refs/remotes/origin/develop - -namespace Ocelot.Configuration -{ - public class QoSOptions - { - public QoSOptions( - int exceptionsAllowedBeforeBreaking, - int durationofBreak, - int timeoutValue, - TimeoutStrategy timeoutStrategy = TimeoutStrategy.Pessimistic) - { - ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking; - DurationOfBreak = TimeSpan.FromMilliseconds(durationofBreak); - TimeoutValue = TimeSpan.FromMilliseconds(timeoutValue); - TimeoutStrategy = timeoutStrategy; - } - - - public int ExceptionsAllowedBeforeBreaking { get; private set; } - - public TimeSpan DurationOfBreak { get; private set; } - - public TimeSpan TimeoutValue { get; private set; } - - public TimeoutStrategy TimeoutStrategy { get; private set; } - - } -} diff --git a/src/Ocelot/Configuration/ReRoute.cs.bak b/src/Ocelot/Configuration/ReRoute.cs.bak deleted file mode 100644 index 4ef5b08a..00000000 --- a/src/Ocelot/Configuration/ReRoute.cs.bak +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using Ocelot.Values; - -namespace Ocelot.Configuration -{ - public class ReRoute - { - public ReRoute(PathTemplate downstreamPathTemplate, - PathTemplate upstreamTemplate, - HttpMethod upstreamHttpMethod, - string upstreamTemplatePattern, - bool isAuthenticated, - AuthenticationOptions authenticationOptions, - List configurationHeaderExtractorProperties, - List claimsToClaims, - Dictionary routeClaimsRequirement, - bool isAuthorised, - List claimsToQueries, -<<<<<<< HEAD - string requestIdKey, bool isCached, CacheOptions fileCacheOptions, - string downstreamScheme, string loadBalancer, string downstreamHost, - int downstreamPort, string loadBalancerKey, ServiceProviderConfiguraion serviceProviderConfiguraion, - bool isQos,QoSOptions qos, bool enableRateLimit, RateLimitOptions ratelimitOptions) -======= - string requestIdKey, - bool isCached, - CacheOptions fileCacheOptions, - string downstreamScheme, - string loadBalancer, - string downstreamHost, - int downstreamPort, - string reRouteKey, - ServiceProviderConfiguraion serviceProviderConfiguraion, - bool isQos, - QoSOptions qos) ->>>>>>> refs/remotes/origin/develop - { - ReRouteKey = reRouteKey; - ServiceProviderConfiguraion = serviceProviderConfiguraion; - LoadBalancer = loadBalancer; - DownstreamHost = downstreamHost; - DownstreamPort = downstreamPort; - DownstreamPathTemplate = downstreamPathTemplate; - UpstreamPathTemplate = upstreamTemplate; - UpstreamHttpMethod = upstreamHttpMethod; - UpstreamTemplatePattern = upstreamTemplatePattern; - IsAuthenticated = isAuthenticated; - AuthenticationOptions = authenticationOptions; - RouteClaimsRequirement = routeClaimsRequirement; - IsAuthorised = isAuthorised; - RequestIdKey = requestIdKey; - IsCached = isCached; - FileCacheOptions = fileCacheOptions; - ClaimsToQueries = claimsToQueries - ?? new List(); - ClaimsToClaims = claimsToClaims - ?? new List(); - ClaimsToHeaders = configurationHeaderExtractorProperties - ?? new List(); - DownstreamScheme = downstreamScheme; - IsQos = isQos; - QosOptions = qos; - EnableEndpointRateLimiting = enableRateLimit; - RateLimitOptions = ratelimitOptions; - } - - public string ReRouteKey {get;private set;} - public PathTemplate DownstreamPathTemplate { get; private set; } - public PathTemplate UpstreamPathTemplate { get; private set; } - public string UpstreamTemplatePattern { get; private set; } - public HttpMethod UpstreamHttpMethod { get; private set; } - public bool IsAuthenticated { get; private set; } - public bool IsAuthorised { get; private set; } - public AuthenticationOptions AuthenticationOptions { get; private set; } - public List ClaimsToQueries { get; private set; } - public List ClaimsToHeaders { get; private set; } - public List ClaimsToClaims { get; private set; } - public Dictionary RouteClaimsRequirement { get; private set; } - public string RequestIdKey { get; private set; } - public bool IsCached { get; private set; } - public CacheOptions FileCacheOptions { get; private set; } - public string DownstreamScheme {get;private set;} - public bool IsQos { get; private set; } - public QoSOptions QosOptions { get; private set; } - 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 bool EnableEndpointRateLimiting { get; private set; } - public RateLimitOptions RateLimitOptions { get; private set; } - } -} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json index 0a65abc2..aba4cdf9 100755 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ b/test/Ocelot.AcceptanceTests/configuration.json @@ -1 +1 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"41879/","UpstreamTemplate":"/","UpstreamHttpMethod":"Get","AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ScopeName":null,"RequireHttps":false,"AdditionalScopes":[],"ScopeSecret":null},"AddHeadersToRequest":{},"AddClaimsToRequest":{},"RouteClaimsRequirement":{},"AddQueriesToRequest":{},"RequestIdKey":null,"FileCacheOptions":{"TtlSeconds":0},"ReRouteIsCaseSensitive":false,"ServiceName":null,"DownstreamScheme":"http","DownstreamHost":"localhost","DownstreamPort":41879,"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},"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file +{"ReRoutes":[{"DownstreamPathTemplate":"41879/","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":41879,"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},"RateLimitOptions":{"ClientIdHeader":"ClientId","QuotaExceededMessage":null,"RateLimitCounterPrefix":"ocelot","DisableRateLimitHeaders":false,"HttpStatusCode":429}}} \ No newline at end of file From 1e88062ce2dc523f635791234110e599736cc8e9 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 15 Feb 2017 07:43:50 +0000 Subject: [PATCH 085/113] moved create admin area call --- .../Middleware/OcelotMiddlewareExtensions.cs | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 26b1638f..269d646d 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -31,26 +31,11 @@ namespace Ocelot.Middleware /// public static async Task UseOcelot(this IApplicationBuilder builder) { - await CreateAdministrationArea(builder); - await builder.UseOcelot(new OcelotMiddlewareConfiguration()); return builder; } - private static async Task CreateAdministrationArea(IApplicationBuilder builder) - { - var configuration = await CreateConfiguration(builder); - - if(!string.IsNullOrEmpty(configuration.AdministrationPath)) - { - builder.Map(configuration.AdministrationPath, x => - { - x.UseMvc(); - }); - } - } - /// /// Registers Ocelot with a combination of default middlewares and optional middlewares in the configuration /// @@ -153,6 +138,19 @@ namespace Ocelot.Middleware return config.Data; } + private static async Task CreateAdministrationArea(IApplicationBuilder builder) + { + var configuration = await CreateConfiguration(builder); + + if(!string.IsNullOrEmpty(configuration.AdministrationPath)) + { + builder.Map(configuration.AdministrationPath, x => + { + x.UseMvc(); + }); + } + } + private static void UseIfNotNull(this IApplicationBuilder builder, Func, Task> middleware) { if (middleware != null) From 8ae8e2d834ac9d7733f6005ec67dc0e0315cb7f8 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 15 Feb 2017 07:48:49 +0000 Subject: [PATCH 086/113] added acceptance test for calling reroutes controller --- .../AdministrationTests.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 test/Ocelot.AcceptanceTests/AdministrationTests.cs diff --git a/test/Ocelot.AcceptanceTests/AdministrationTests.cs b/test/Ocelot.AcceptanceTests/AdministrationTests.cs new file mode 100644 index 00000000..5a76e458 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/AdministrationTests.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration.File; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class AdministrationTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + + public AdministrationTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_return_response_200_with_call_re_routes_controller() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + } + }; + + this.Given(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/administration/reroutes")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) + { + _builder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _builder.Start(); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} From 219b45e7d061b49e23afc358be9a4bb9171895a9 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 15 Feb 2017 07:51:19 +0000 Subject: [PATCH 087/113] updated acceptance test assertion --- src/Ocelot/Controllers/ReRoutesController.cs | 1 + test/Ocelot.AcceptanceTests/AdministrationTests.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Ocelot/Controllers/ReRoutesController.cs b/src/Ocelot/Controllers/ReRoutesController.cs index 0b02c796..4304d4b1 100644 --- a/src/Ocelot/Controllers/ReRoutesController.cs +++ b/src/Ocelot/Controllers/ReRoutesController.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Ocelot.Controllers diff --git a/test/Ocelot.AcceptanceTests/AdministrationTests.cs b/test/Ocelot.AcceptanceTests/AdministrationTests.cs index 5a76e458..44c4447e 100644 --- a/test/Ocelot.AcceptanceTests/AdministrationTests.cs +++ b/test/Ocelot.AcceptanceTests/AdministrationTests.cs @@ -36,6 +36,7 @@ namespace Ocelot.AcceptanceTests .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/administration/reroutes")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("hi from re routes controller")) .BDDfy(); } From bc5010837eafdc54f3f28964ea6417327f8b151f Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 15 Feb 2017 13:24:06 +0000 Subject: [PATCH 088/113] removed using --- src/Ocelot/Controllers/ReRoutesController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Ocelot/Controllers/ReRoutesController.cs b/src/Ocelot/Controllers/ReRoutesController.cs index 4304d4b1..0b02c796 100644 --- a/src/Ocelot/Controllers/ReRoutesController.cs +++ b/src/Ocelot/Controllers/ReRoutesController.cs @@ -1,4 +1,3 @@ -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Ocelot.Controllers From 0b4aee7b966ae32082e5904d395a9b462d94bac1 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Wed, 15 Feb 2017 20:12:47 +0000 Subject: [PATCH 089/113] Add syntax highlighting to readme.md --- README.md | 177 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 104 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index dfbdfd1e..0a918587 100644 --- a/README.md +++ b/README.md @@ -61,11 +61,12 @@ The ReRoutes are the objects that tell Ocelot how to treat an upstream request. 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. - { - "ReRoutes": [], - "GlobalConfiguration": {} - } - +```json + { + "ReRoutes": [], + "GlobalConfiguration": {} + } +``` More information on how to use these options is below.. ## Startup @@ -73,7 +74,8 @@ More information on how to use these options is below.. An example startup using a json file for configuration can be seen below. Currently this is the only way to get configuration into Ocelot. - public class Startup +```csharp + public class Startup { public Startup(IHostingEnvironment env) { @@ -112,7 +114,7 @@ Currently this is the only way to get configuration into Ocelot. app.UseOcelot(); } } - +``` This is pretty much all you need to get going.......more to come! @@ -127,22 +129,26 @@ 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. - { - "ReRoutes": [ - ] - } +```json + { + "ReRoutes": [ + ] + } +``` In order to set up a ReRoute you need to add one to the json array called ReRoutes like the following. - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamPort": 80, - "DownstreamHost" "localhost" - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Put" - } +```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 @@ -156,7 +162,9 @@ 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. - "ReRouteIsCaseSensitive": true +```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 @@ -176,25 +184,28 @@ At the moment the only supported service discovery provider is Consul. The follo GlobalConfiguration. The Provider is required and if you do not specify a host and port the Consul default will be used. - "ServiceDiscoveryProvider": - { - "Provider":"Consul", - "Host":"localhost", - "Port":8500 - } +```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. - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Put", - "ServiceName": "product" - "LoadBalancer": "LeastConnection" - } +```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. @@ -206,16 +217,18 @@ Ocelot currently supports the use of bearer tokens with Identity Server (more pr come if required). In order to identity a ReRoute as authenticated it needs the following configuration added. - "AuthenticationOptions": { - "Provider": "IdentityServer", - "ProviderRootUrl": "http://localhost:52888", - "ScopeName": "api", - "AdditionalScopes": [ - "openid", - "offline_access" - ], - "ScopeSecret": "secret" - } +```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 @@ -234,9 +247,11 @@ is 401 unauthorised. 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. - "RouteClaimsRequirement": { - "UserType": "registered" - }, +```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. @@ -275,10 +290,12 @@ and add whatever was at the index requested to the transform. Below is an example configuration that will transforms claims to claims - "AddClaimsToRequest": { - "UserType": "Claims[sub] > value[0] > |", - "UserId": "Claims[sub] > value[1] > |" - }, +```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". @@ -287,9 +304,11 @@ UserType and UserId claims. Assuming the sub looks like this "usertypevalue|user Below is an example configuration that will transforms claims to headers - "AddHeadersToRequest": { - "CustomerId": "Claims[sub] > value[1] > |" - }, +```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". @@ -298,9 +317,11 @@ CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue". Below is an example configuration that will transforms claims to query string parameters - "AddQueriesToRequest": { - "LocationId": "Claims[LocationId] > value", - }, +```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. @@ -313,11 +334,13 @@ want to use a circuit breaker when making requests to a downstream service. This Add the following section to a ReRoute configuration. - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking":3, - "DurationOfBreak":5, - "TimeoutValue":5000 - } +```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. @@ -338,14 +361,18 @@ have an OcelotRequestId. In order to use the requestid feature in your ReRoute configuration add this setting - "RequestIdKey": "OcRequestId" +```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. - "RequestIdKey": "OcRequestId", +```json + "RequestIdKey": "OcRequestId", +``` It behaves in exactly the same way as the ReRoute level RequestIdKey settings. @@ -364,7 +391,9 @@ 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. - "FileCacheOptions": { "TtlSeconds": 15 } +```json + "FileCacheOptions": { "TtlSeconds": 15 } +``` In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds. @@ -376,15 +405,17 @@ 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. - var configuration = new OcelotMiddlewareConfiguration - { - PreErrorResponderMiddleware = async (ctx, next) => - { - await next.Invoke(); - } - }; +```csharp + var configuration = new OcelotMiddlewareConfiguration + { + PreErrorResponderMiddleware = async (ctx, next) => + { + await next.Invoke(); + } + }; - app.UseOcelot(configuration); + 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. From 45341df237c06481405417064cb6fd585b7298c9 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Wed, 15 Feb 2017 20:18:15 +0000 Subject: [PATCH 090/113] Remove extra level of indentation. Fixed a typo in one of tje json examples --- README.md | 200 +++++++++++++++++++++++++++--------------------------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index 0a918587..6fcf10f4 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,10 @@ configuration is a bit hacky and allows overrides of ReRoute specific settings. if you don't want to manage lots of ReRoute specific settings. ```json - { - "ReRoutes": [], - "GlobalConfiguration": {} - } +{ + "ReRoutes": [], + "GlobalConfiguration": {} +} ``` More information on how to use these options is below.. @@ -75,45 +75,45 @@ 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 class Startup +{ + public Startup(IHostingEnvironment env) { - 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(); + 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.AddOcelotFileConfiguration(Configuration); - services.AddOcelot(); - } - - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) - { - loggerFactory.AddConsole(Configuration.GetSection("Logging")); - - app.UseOcelot(); - } + 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.AddOcelotFileConfiguration(Configuration); + services.AddOcelot(); + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(Configuration.GetSection("Logging")); + + app.UseOcelot(); + } +} ``` This is pretty much all you need to get going.......more to come! @@ -140,14 +140,14 @@ In order to set up a ReRoute you need to add one to the json array called ReRout the following. ```json - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamPort": 80, - "DownstreamHost" "localhost" - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": "Put" - } +{ + "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. @@ -163,7 +163,7 @@ At the moment without any configuration Ocelot will default to all ReRoutes bein In order to change this you can specify on a per ReRoute basis the following setting. ```json - "ReRouteIsCaseSensitive": true +"ReRouteIsCaseSensitive": true ``` This means that when Ocelot tries to match the incoming upstream url with an upstream template the @@ -185,11 +185,11 @@ GlobalConfiguration. The Provider is required and if you do not specify a host a will be used. ```json - "ServiceDiscoveryProvider": { - "Provider":"Consul", - "Host":"localhost", - "Port":8500 - } +"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 @@ -197,14 +197,14 @@ ServiceName and load balancer you wish to use when making requests downstream. A 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" - } +{ + "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 @@ -218,16 +218,16 @@ come if required). In order to identity a ReRoute as authenticated it needs the configuration added. ```json - "AuthenticationOptions": { - "Provider": "IdentityServer", - "ProviderRootUrl": "http://localhost:52888", - "ScopeName": "api", - "AdditionalScopes": [ - "openid", - "offline_access" - ], - "ScopeSecret": "secret" - } +"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 @@ -248,9 +248,9 @@ Ocelot supports claims based authorisation which is run post authentication. Thi you have a route you want to authorise you can add the following to you ReRoute configuration. ```json - "RouteClaimsRequirement": { - "UserType": "registered" - }, +"RouteClaimsRequirement": { + "UserType": "registered" +}, ``` In this example when the authorisation middleware is called Ocelot will check to see @@ -291,10 +291,10 @@ and add whatever was at the index requested to the transform. Below is an example configuration that will transforms claims to claims ```json - "AddClaimsToRequest": { - "UserType": "Claims[sub] > value[0] > |", - "UserId": "Claims[sub] > value[1] > |" - }, + "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 @@ -305,9 +305,9 @@ UserType and UserId claims. Assuming the sub looks like this "usertypevalue|user Below is an example configuration that will transforms claims to headers ```json - "AddHeadersToRequest": { - "CustomerId": "Claims[sub] > value[1] > |" - }, +"AddHeadersToRequest": { + "CustomerId": "Claims[sub] > value[1] > |" +}, ``` This shows a transform where Ocelot looks at the users sub claim and trasnforms it into a @@ -318,9 +318,9 @@ CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue". Below is an example configuration that will transforms claims to query string parameters ```json - "AddQueriesToRequest": { - "LocationId": "Claims[LocationId] > value", - }, +"AddQueriesToRequest": { + "LocationId": "Claims[LocationId] > value", +}, ``` This shows a transform where Ocelot looks at the users LocationId claim and add its as @@ -335,11 +335,11 @@ want to use a circuit breaker when making requests to a downstream service. This Add the following section to a ReRoute configuration. ```json - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking":3, - "DurationOfBreak":5, - "TimeoutValue":5000 - } +"QoSOptions": { + "ExceptionsAllowedBeforeBreaking":3, + "DurationOfBreak":5, + "TimeoutValue":5000 +} ``` You must set a number greater than 0 against ExceptionsAllowedBeforeBreaking for this rule to be @@ -362,7 +362,7 @@ have an OcelotRequestId. In order to use the requestid feature in your ReRoute configuration add this setting ```json - "RequestIdKey": "OcRequestId" +"RequestIdKey": "OcRequestId" ``` In this example OcRequestId is the request header that contains the clients request id. @@ -371,7 +371,7 @@ There is also a setting in the GlobalConfiguration section which will override w set at ReRoute level for the request id. The setting is as fllows. ```json - "RequestIdKey": "OcRequestId", +"RequestIdKey": "OcRequestId", ``` It behaves in exactly the same way as the ReRoute level RequestIdKey settings. @@ -392,7 +392,7 @@ 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 } +"FileCacheOptions": { "TtlSeconds": 15 } ``` In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds. @@ -406,15 +406,15 @@ When setting up Ocelot in your Startup.cs you can provide some additonal middlew and override middleware. This is done as follos. ```csharp - var configuration = new OcelotMiddlewareConfiguration +var configuration = new OcelotMiddlewareConfiguration +{ + PreErrorResponderMiddleware = async (ctx, next) => { - PreErrorResponderMiddleware = async (ctx, next) => - { - await next.Invoke(); - } - }; + await next.Invoke(); + } +}; - app.UseOcelot(configuration); +app.UseOcelot(configuration); ``` In the example above the provided function will run before the first piece of Ocelot middleware. From 4ad8accbc0f0b2e5b710d1f6897571b27aa23b6b Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Wed, 15 Feb 2017 20:30:35 +0000 Subject: [PATCH 091/113] grr! I always miss something... --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6fcf10f4..57faf26f 100644 --- a/README.md +++ b/README.md @@ -130,10 +130,10 @@ Ocelot's describes the routing of one request to another as a ReRoute. In order anything working in Ocelot you need to set up a ReRoute in the configuration. ```json - { - "ReRoutes": [ - ] - } +{ + "ReRoutes": [ + ] +} ``` In order to set up a ReRoute you need to add one to the json array called ReRoutes like From 4dac8cb4fb37d3db49dd2ce8cc7a407e25e8640d Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 17 Feb 2017 07:27:49 +0000 Subject: [PATCH 092/113] still hacking around --- .../FileConfigurationController.cs | 21 ++++ src/Ocelot/Controllers/ReRoutesController.cs | 13 --- src/Ocelot/Services/GetFileConfiguration.cs | 19 ++++ src/Ocelot/Services/IGetFileConfiguration.cs | 10 ++ .../AdministrationTests.cs | 31 +++++- test/Ocelot.AcceptanceTests/Steps.cs | 20 +++- .../TestConfiguration.cs | 2 +- .../FileConfigurationControllerTests.cs | 53 ++++++++++ .../Services/GetFileConfigurationTests.cs | 96 +++++++++++++++++++ test/Ocelot.UnitTests/configuration.json | 1 + 10 files changed, 250 insertions(+), 16 deletions(-) create mode 100644 src/Ocelot/Controllers/FileConfigurationController.cs delete mode 100644 src/Ocelot/Controllers/ReRoutesController.cs create mode 100644 src/Ocelot/Services/GetFileConfiguration.cs create mode 100644 src/Ocelot/Services/IGetFileConfiguration.cs create mode 100644 test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs create mode 100644 test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs create mode 100755 test/Ocelot.UnitTests/configuration.json diff --git a/src/Ocelot/Controllers/FileConfigurationController.cs b/src/Ocelot/Controllers/FileConfigurationController.cs new file mode 100644 index 00000000..cf0a792a --- /dev/null +++ b/src/Ocelot/Controllers/FileConfigurationController.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Mvc; +using Ocelot.Services; + +namespace Ocelot.Controllers +{ + [RouteAttribute("configuration")] + public class FileConfigurationController + { + private IGetFileConfiguration _getFileConfig; + + public FileConfigurationController(IGetFileConfiguration getFileConfig) + { + _getFileConfig = getFileConfig; + } + + public IActionResult Get() + { + return new OkObjectResult(_getFileConfig.Invoke().Data); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Controllers/ReRoutesController.cs b/src/Ocelot/Controllers/ReRoutesController.cs deleted file mode 100644 index 0b02c796..00000000 --- a/src/Ocelot/Controllers/ReRoutesController.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace Ocelot.Controllers -{ - [RouteAttribute("reroutes")] - public class ReRoutesController - { - public IActionResult Get() - { - return new OkObjectResult("hi from re routes controller"); - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Services/GetFileConfiguration.cs b/src/Ocelot/Services/GetFileConfiguration.cs new file mode 100644 index 00000000..b97cac92 --- /dev/null +++ b/src/Ocelot/Services/GetFileConfiguration.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Services +{ + public class GetFileConfiguration : IGetFileConfiguration + { + public Response Invoke() + { + var configFilePath = "configuration.json"; + var json = File.ReadAllText(configFilePath); + var fileConfiguration = JsonConvert.DeserializeObject(json); + return new OkResponse(fileConfiguration); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Services/IGetFileConfiguration.cs b/src/Ocelot/Services/IGetFileConfiguration.cs new file mode 100644 index 00000000..08b950a8 --- /dev/null +++ b/src/Ocelot/Services/IGetFileConfiguration.cs @@ -0,0 +1,10 @@ +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Services +{ + public interface IGetFileConfiguration + { + Response Invoke(); + } +} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/AdministrationTests.cs b/test/Ocelot.AcceptanceTests/AdministrationTests.cs index 44c4447e..29ad10a8 100644 --- a/test/Ocelot.AcceptanceTests/AdministrationTests.cs +++ b/test/Ocelot.AcceptanceTests/AdministrationTests.cs @@ -34,12 +34,41 @@ namespace Ocelot.AcceptanceTests this.Given(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/administration/reroutes")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/administration/configuration")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("hi from re routes controller")) .BDDfy(); } + [Fact] + public void should_return_file_configuration() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + }, + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/" + } + } + }; + + this.Given(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseShouldBe(configuration)) + .BDDfy(); + } + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) { _builder = new WebHostBuilder() diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 9b5faa04..73585017 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -75,6 +75,25 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } + internal 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); + } + } + /// /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. /// @@ -155,7 +174,6 @@ namespace Ocelot.AcceptanceTests } } - public void WhenIGetUrlOnTheApiGateway(string url) { _response = _ocelotClient.GetAsync(url).Result; diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index ce802efb..6784391c 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -28,7 +28,7 @@ { var runTime = $"{oSDescription}-{osArchitecture}".ToLower(); - var configPath = $"./bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; + var configPath = $"./test/Ocelot.AcceptanceTests/bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; return configPath; } diff --git a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs new file mode 100644 index 00000000..cbd8aeac --- /dev/null +++ b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.File; +using Ocelot.Controllers; +using Ocelot.Responses; +using Ocelot.Services; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Controllers +{ + public class FileConfigurationControllerTests + { + private FileConfigurationController _controller; + private Mock _getFileConfig; + private IActionResult _result; + + public FileConfigurationControllerTests() + { + _getFileConfig = new Mock(); + _controller = new FileConfigurationController(_getFileConfig.Object); + } + + [Fact] + public void should_return_file_configuration() + { + var expected = new OkResponse(new FileConfiguration()); + + this.Given(x => x.GivenTheGetConfigurationReturns(expected)) + .When(x => x.WhenIGetTheFileConfiguration()) + .Then(x => x.ThenTheFileConfigurationIsReturned(expected.Data)) + .BDDfy(); + } + + private void GivenTheGetConfigurationReturns(Response fileConfiguration) + { + _getFileConfig + .Setup(x => x.Invoke()) + .Returns(fileConfiguration); + } + + private void WhenIGetTheFileConfiguration() + { + _result = _controller.Get(); + } + + private void ThenTheFileConfigurationIsReturned(FileConfiguration expected) + { + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs b/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs new file mode 100644 index 00000000..89ce22b1 --- /dev/null +++ b/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.File; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using Ocelot.Services; +using Newtonsoft.Json; +using System.IO; + +namespace Ocelot.UnitTests.Services +{ + public class GetFileConfigurationTests + { + private IGetFileConfiguration _getReRoutes; + private FileConfiguration _result; + + public GetFileConfigurationTests() + { + _getReRoutes = new GetFileConfiguration(); + } + + [Fact] + public void should_return_file_configuration() + { + var reRoutes = new List + { + new FileReRoute + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/test/test/{test}" + } + }; + + var globalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "testy", + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Provider = "consul", + Port = 198, + Host = "blah" + } + }; + + var config = new FileConfiguration(); + config.GlobalConfiguration = globalConfiguration; + config.ReRoutes.AddRange(reRoutes); + + this.Given(x => x.GivenTheConfigurationIs(config)) + .When(x => x.WhenIGetTheReRoutes()) + .Then(x => x.ThenTheFollowingIsReturned(config)) + .BDDfy(); + } + + private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) + { + var configurationPath = "configuration.json"; + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + private void WhenIGetTheReRoutes() + { + _result = _getReRoutes.Invoke().Data; + } + + private void ThenTheFollowingIsReturned(FileConfiguration expected) + { + _result.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath); + _result.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider); + + for(var i = 0; i < _result.ReRoutes.Count; i++) + { + _result.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); + _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); + _result.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); + _result.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); + //todo -- add more! + } + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/configuration.json b/test/Ocelot.UnitTests/configuration.json new file mode 100755 index 00000000..61548dab --- /dev/null +++ b/test/Ocelot.UnitTests/configuration.json @@ -0,0 +1 @@ +{"ReRoutes":[{"DownstreamPathTemplate":"/test/test/{test}","UpstreamPathTemplate":null,"UpstreamHttpMethod":null,"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":"https","DownstreamHost":"localhost","DownstreamPort":80,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":"consul","Host":"blah","Port":198},"AdministrationPath":"testy"}} \ No newline at end of file From b98310c80d6a8d3cc78b8fe852279e323350989d Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 17 Feb 2017 18:24:32 +0000 Subject: [PATCH 093/113] working ssl example --- test/Ocelot.AcceptanceTests/project.json | 1 + test/Ocelot.ManualTest/configuration.json | 44 +++++++++++------------ 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index 2dd3094e..41594e1f 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -29,6 +29,7 @@ "IdentityServer4": "1.0.1", "Microsoft.AspNetCore.Mvc": "1.1.0", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", + "Microsoft.AspNetCore.Server.Kestrel.Https": "1.1.0", "Microsoft.NETCore.App": "1.1.0", "Shouldly": "2.8.2", "TestStack.BDDfy": "4.3.2", diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index 9b5fcb7e..e8bc8928 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -5,7 +5,7 @@ "DownstreamScheme": "http", "DownstreamHost": "localhost", "DownstreamPort": 52876, - "UpstreamTemplate": "/identityserverexample", + "UpstreamPathTemplate": "/identityserverexample", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -47,10 +47,10 @@ }, { "DownstreamPathTemplate": "/posts", - "DownstreamScheme": "http", + "DownstreamScheme": "https", "DownstreamHost": "jsonplaceholder.typicode.com", - "DownstreamPort": 80, - "UpstreamTemplate": "/posts", + "DownstreamPort": 443, + "UpstreamPathTemplate": "/posts", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -63,7 +63,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -76,7 +76,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}/comments", + "UpstreamPathTemplate": "/posts/{postId}/comments", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -89,7 +89,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/comments", + "UpstreamPathTemplate": "/comments", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -102,7 +102,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts", + "UpstreamPathTemplate": "/posts", "UpstreamHttpMethod": "Post", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -115,7 +115,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Put", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -128,7 +128,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Patch", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -141,7 +141,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/{postId}", + "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": "Delete", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -154,7 +154,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/products", + "UpstreamPathTemplate": "/products", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -168,7 +168,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", + "UpstreamPathTemplate": "/products/{productId}", "UpstreamHttpMethod": "Get", "FileCacheOptions": { "TtlSeconds": 15 } }, @@ -177,7 +177,7 @@ "DownstreamScheme": "http", "DownstreamHost": "products20161126090340.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/products", + "UpstreamPathTemplate": "/products", "UpstreamHttpMethod": "Post", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -190,7 +190,7 @@ "DownstreamScheme": "http", "DownstreamHost": "products20161126090340.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", + "UpstreamPathTemplate": "/products/{productId}", "UpstreamHttpMethod": "Put", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -204,7 +204,7 @@ "DownstreamScheme": "http", "DownstreamHost": "products20161126090340.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/products/{productId}", + "UpstreamPathTemplate": "/products/{productId}", "UpstreamHttpMethod": "Delete", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -218,7 +218,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers", + "UpstreamPathTemplate": "/customers", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -232,7 +232,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", + "UpstreamPathTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -246,7 +246,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers", + "UpstreamPathTemplate": "/customers", "UpstreamHttpMethod": "Post", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -260,7 +260,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", + "UpstreamPathTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Put", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -274,7 +274,7 @@ "DownstreamScheme": "http", "DownstreamHost": "customers20161126090811.azurewebsites.net", "DownstreamPort": 80, - "UpstreamTemplate": "/customers/{customerId}", + "UpstreamPathTemplate": "/customers/{customerId}", "UpstreamHttpMethod": "Delete", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, @@ -288,7 +288,7 @@ "DownstreamScheme": "http", "DownstreamHost": "jsonplaceholder.typicode.com", "DownstreamPort": 80, - "UpstreamTemplate": "/posts/", + "UpstreamPathTemplate": "/posts/", "UpstreamHttpMethod": "Get", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, From ed3a629827e561b1b064641088a272787c5490f8 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 17 Feb 2017 20:17:23 +0000 Subject: [PATCH 094/113] updated nuget info --- README.md | 3 --- src/Ocelot/project.json | 8 ++++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 57faf26f..3d390a3d 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,6 @@ and retrived 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. -This is not ready for production yet as uses a lot of rc and beta .net core packages. -Hopefully by the start of 2017 it will be in use. - ## Contributing Pull requests, issues and commentary welcome! No special process just create a request and get in diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 4e098803..0ab54df2 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -1,5 +1,13 @@ { "version": "0.0.0-dev", + "title": "Ocelot", + "summary": "API Gateway created using .NET core.", + "projectUrl": "https://github.com/TomPallister/Ocelot", + "description": "This project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. We have been unable to find this in my current workplace without having to write our own Javascript middlewares to handle the IdentityServer reference tokens. We would rather use the IdentityServer code that already exists to do this. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features.", + "tags": [ + "API Gateway", + ".NET core" + ], "dependencies": { "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", From 816221c7a6fb2106500db4c02688340e855cebe9 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sun, 19 Feb 2017 12:33:09 +0000 Subject: [PATCH 095/113] morehaking about --- .../InMemoryConfigurationRepositoryTests.cs | 17 ++++++----------- .../FileConfigurationControllerTests.cs | 8 ++++---- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index 8e0e66d3..8d28b112 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -27,7 +27,7 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void can_add_config() { - this.Given(x => x.GivenTheConfigurationIs(new FakeConfig("initial"))) + this.Given(x => x.GivenTheConfigurationIs(new FakeConfig("initial", "adminath"))) .When(x => x.WhenIAddOrReplaceTheConfig()) .Then(x => x.ThenNoErrorsAreReturned()) .BDDfy(); @@ -54,7 +54,7 @@ namespace Ocelot.UnitTests.Configuration private void GivenThereIsASavedConfiguration() { - GivenTheConfigurationIs(new FakeConfig("initial")); + GivenTheConfigurationIs(new FakeConfig("initial", "adminath")); WhenIAddOrReplaceTheConfig(); } @@ -77,17 +77,10 @@ namespace Ocelot.UnitTests.Configuration { private readonly string _downstreamTemplatePath; - public FakeConfig(string downstreamTemplatePath) + public FakeConfig(string downstreamTemplatePath, string administrationPath) { _downstreamTemplatePath = downstreamTemplatePath; - } - - public string AdministrationPath - { - get - { - throw new NotImplementedException(); - } + AdministrationPath = administrationPath; } public List ReRoutes => new List @@ -97,6 +90,8 @@ namespace Ocelot.UnitTests.Configuration .WithUpstreamHttpMethod("Get") .Build() }; + + public string AdministrationPath {get;} } } } diff --git a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs index cbd8aeac..cd2ab4ce 100644 --- a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs @@ -1,7 +1,5 @@ -using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Moq; -using Ocelot.Configuration; using Ocelot.Configuration.File; using Ocelot.Controllers; using Ocelot.Responses; @@ -30,7 +28,7 @@ namespace Ocelot.UnitTests.Controllers this.Given(x => x.GivenTheGetConfigurationReturns(expected)) .When(x => x.WhenIGetTheFileConfiguration()) - .Then(x => x.ThenTheFileConfigurationIsReturned(expected.Data)) + .Then(x => x.TheTheGetFileConfigurationIsCalledCorrectly()) .BDDfy(); } @@ -46,8 +44,10 @@ namespace Ocelot.UnitTests.Controllers _result = _controller.Get(); } - private void ThenTheFileConfigurationIsReturned(FileConfiguration expected) + private void TheTheGetFileConfigurationIsCalledCorrectly() { + _getFileConfig + .Verify(x => x.Invoke(), Times.Once); } } } \ No newline at end of file From fa4766325968cc6cec93173a01073c4d6d1f7670 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 19 Feb 2017 12:58:22 +0000 Subject: [PATCH 096/113] changed file config stuff to just use app base directory --- .../ServiceCollectionExtensions.cs | 2 ++ src/Ocelot/Services/GetFileConfiguration.cs | 2 +- .../AdministrationTests.cs | 35 +++++++------------ test/Ocelot.AcceptanceTests/Steps.cs | 4 ++- .../TestConfiguration.cs | 35 +++---------------- .../Services/GetFileConfigurationTests.cs | 7 ++-- 6 files changed, 26 insertions(+), 59 deletions(-) diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index e9be9841..74e6f97e 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -31,6 +31,7 @@ using Ocelot.Requester; using Ocelot.Requester.QoS; using Ocelot.Responder; using Ocelot.ServiceDiscovery; +using Ocelot.Services; namespace Ocelot.DependencyInjection { @@ -62,6 +63,7 @@ namespace Ocelot.DependencyInjection { services.AddMvcCore().AddJsonFormatters(); services.AddLogging(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/Services/GetFileConfiguration.cs b/src/Ocelot/Services/GetFileConfiguration.cs index b97cac92..536a0b46 100644 --- a/src/Ocelot/Services/GetFileConfiguration.cs +++ b/src/Ocelot/Services/GetFileConfiguration.cs @@ -10,7 +10,7 @@ namespace Ocelot.Services { public Response Invoke() { - var configFilePath = "configuration.json"; + var configFilePath = $"{AppContext.BaseDirectory}/configuration.json"; var json = File.ReadAllText(configFilePath); var fileConfiguration = JsonConvert.DeserializeObject(json); return new OkResponse(fileConfiguration); diff --git a/test/Ocelot.AcceptanceTests/AdministrationTests.cs b/test/Ocelot.AcceptanceTests/AdministrationTests.cs index 29ad10a8..525d9be5 100644 --- a/test/Ocelot.AcceptanceTests/AdministrationTests.cs +++ b/test/Ocelot.AcceptanceTests/AdministrationTests.cs @@ -36,7 +36,6 @@ namespace Ocelot.AcceptanceTests .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/administration/configuration")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("hi from re routes controller")) .BDDfy(); } @@ -56,7 +55,18 @@ namespace Ocelot.AcceptanceTests DownstreamHost = "localhost", DownstreamPort = 80, DownstreamScheme = "https", - DownstreamPathTemplate = "/" + DownstreamPathTemplate = "/", + UpstreamHttpMethod = "get", + UpstreamPathTemplate = "/" + }, + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = "get", + UpstreamPathTemplate = "/test" } } }; @@ -69,27 +79,6 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } - private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) - { - _builder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _builder.Start(); - } - public void Dispose() { _builder?.Dispose(); diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 73585017..4a946947 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -31,7 +31,7 @@ namespace Ocelot.AcceptanceTests private BearerToken _token; public HttpClient OcelotClient => _ocelotClient; public string RequestIdKey = "OcRequestId"; - private Random _random; + private readonly Random _random; public Steps() { @@ -91,6 +91,8 @@ namespace Ocelot.AcceptanceTests 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); } } diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index 6784391c..b4e6b778 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -1,36 +1,9 @@ -namespace Ocelot.AcceptanceTests -{ - using System.Runtime.InteropServices; +using System; +namespace Ocelot.AcceptanceTests +{ public static class TestConfiguration { - public static double Version => 1.1; - public static string ConfigurationPath => GetConfigurationPath(); - - public static string GetConfigurationPath() - { - var osArchitecture = RuntimeInformation.OSArchitecture.ToString(); - - if(RuntimeInformation.OSDescription.ToLower().Contains("darwin")) - { - return FormatConfigurationPath("osx.10.11", osArchitecture); - } - - if(RuntimeInformation.OSDescription.ToLower().Contains("microsoft windows 10")) - { - return FormatConfigurationPath("win10", osArchitecture); - } - - return FormatConfigurationPath("win7", osArchitecture); - } - - private static string FormatConfigurationPath(string oSDescription, string osArchitecture) - { - var runTime = $"{oSDescription}-{osArchitecture}".ToLower(); - - var configPath = $"./test/Ocelot.AcceptanceTests/bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; - - return configPath; - } + public static string ConfigurationPath => $"{AppContext.BaseDirectory}/configuration.json"; } } diff --git a/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs b/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs index 89ce22b1..fa86ced6 100644 --- a/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs +++ b/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Moq; using Ocelot.Configuration; @@ -14,7 +15,7 @@ namespace Ocelot.UnitTests.Services { public class GetFileConfigurationTests { - private IGetFileConfiguration _getReRoutes; + private readonly IGetFileConfiguration _getReRoutes; private FileConfiguration _result; public GetFileConfigurationTests() @@ -59,7 +60,8 @@ namespace Ocelot.UnitTests.Services private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) { - var configurationPath = "configuration.json"; + var configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); if (File.Exists(configurationPath)) @@ -89,7 +91,6 @@ namespace Ocelot.UnitTests.Services _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); _result.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); _result.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); - //todo -- add more! } } } From d236ed3018a278e8ad22d88abe03a302eb503911 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 19 Feb 2017 13:59:17 +0000 Subject: [PATCH 097/113] trying to get identity server authing --- .../FileConfigurationController.cs | 10 ++- .../ServiceCollectionExtensions.cs | 51 ++++++++++++++++ .../Middleware/OcelotMiddlewareExtensions.cs | 22 +++++-- src/Ocelot/project.json | 61 ++++++++++--------- .../AdministrationTests.cs | 6 -- 5 files changed, 107 insertions(+), 43 deletions(-) diff --git a/src/Ocelot/Controllers/FileConfigurationController.cs b/src/Ocelot/Controllers/FileConfigurationController.cs index cf0a792a..3589bdb5 100644 --- a/src/Ocelot/Controllers/FileConfigurationController.cs +++ b/src/Ocelot/Controllers/FileConfigurationController.cs @@ -1,20 +1,24 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Ocelot.Services; namespace Ocelot.Controllers { - [RouteAttribute("configuration")] - public class FileConfigurationController + [Authorize(Roles = "Admin")] + [Route("configuration")] + public class FileConfigurationController : Controller { - private IGetFileConfiguration _getFileConfig; + private readonly IGetFileConfiguration _getFileConfig; public FileConfigurationController(IGetFileConfiguration getFileConfig) { _getFileConfig = getFileConfig; } + [HttpGet] public IActionResult Get() { + var user = this.HttpContext.User; return new OkObjectResult(_getFileConfig.Invoke().Data); } } diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 74e6f97e..c06d965b 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -1,6 +1,10 @@ using System; +using System.Collections.Generic; using System.Net.Http; +using System.Security.Claims; using CacheManager.Core; +using IdentityServer4.Models; +using IdentityServer4.Test; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -61,6 +65,53 @@ namespace Ocelot.DependencyInjection public static IServiceCollection AddOcelot(this IServiceCollection services) { + services.AddIdentityServer() + .AddTemporarySigningCredential() + .AddInMemoryApiResources(new List + { + new ApiResource + { + Name = "admin", + Description = "Ocelot Administration", + Enabled = true, + DisplayName = "admin", + Scopes = new List() + { + new Scope("admin"), + new Scope("openid"), + new Scope("offline_access") + }, + ApiSecrets = new List + { + new Secret + { + Value = "secret".Sha256() + } + } + } + }) + .AddInMemoryClients(new List + { + new Client + { + ClientId = "admin", + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List {new Secret("secret".Sha256())}, + AllowedScopes = new List {"admin", "openid", "offline_access"}, + AccessTokenType = AccessTokenType.Jwt, + Enabled = true, + RequireClientSecret = false + } + }) + .AddTestUsers(new List + { + new TestUser + { + Username = "admin", + Password = "admin", + SubjectId = "admin", + } + }); services.AddMvcCore().AddJsonFormatters(); services.AddLogging(); services.AddSingleton(); diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 269d646d..ab51e8da 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Builder; +using System.Collections.Generic; +using IdentityServer4.AccessTokenValidation; +using Microsoft.AspNetCore.Builder; using Ocelot.Authentication.Middleware; using Ocelot.Cache.Middleware; using Ocelot.Claims.Middleware; @@ -45,7 +47,7 @@ namespace Ocelot.Middleware public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) { await CreateAdministrationArea(builder); - + // This is registered to catch any global exceptions that are not handled builder.UseExceptionHandlerMiddleware(); @@ -144,9 +146,21 @@ namespace Ocelot.Middleware if(!string.IsNullOrEmpty(configuration.AdministrationPath)) { - builder.Map(configuration.AdministrationPath, x => + builder.Map(configuration.AdministrationPath, app => { - x.UseMvc(); + app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions + { + Authority = "http://localhost:5000/admin", + ApiName = "admin", + RequireHttpsMetadata = false, + AllowedScopes = new List(), + SupportedTokens = SupportedTokens.Both, + ApiSecret = "secret" + }); + + app.UseIdentityServer(); + + app.UseMvc(); }); } } diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 4e098803..7b169503 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -1,35 +1,36 @@ { "version": "0.0.0-dev", - "dependencies": { - "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", - "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", - "Microsoft.Extensions.Configuration.Json": "1.1.0", - "Microsoft.Extensions.Logging": "1.1.0", - "Microsoft.Extensions.Logging.Console": "1.1.0", - "Microsoft.Extensions.Logging.Debug": "1.1.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", - "Microsoft.AspNetCore.Http": "1.1.0", - "System.Text.RegularExpressions": "4.3.0", - "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", - "Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0", - "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0", - "Microsoft.AspNetCore.Authentication.Cookies": "1.1.0", - "Microsoft.AspNetCore.Authentication.Google": "1.1.0", - "Microsoft.AspNetCore.Authentication.Facebook": "1.1.0", - "Microsoft.AspNetCore.Authentication.Twitter": "1.1.0", - "Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.1.0", - "Microsoft.AspNetCore.Authentication": "1.1.0", - "IdentityServer4.AccessTokenValidation": "1.0.2", - "Microsoft.AspNetCore.Mvc": "1.1.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", - "Microsoft.NETCore.App": "1.1.0", - "CacheManager.Core": "0.9.2", - "CacheManager.Microsoft.Extensions.Configuration": "0.9.2", - "CacheManager.Microsoft.Extensions.Logging": "0.9.2", - "Consul": "0.7.2.1", - "Polly": "5.0.3" - }, + "dependencies": { + "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", + "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", + "Microsoft.Extensions.Configuration.Json": "1.1.0", + "Microsoft.Extensions.Logging": "1.1.0", + "Microsoft.Extensions.Logging.Console": "1.1.0", + "Microsoft.Extensions.Logging.Debug": "1.1.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", + "Microsoft.AspNetCore.Http": "1.1.0", + "System.Text.RegularExpressions": "4.3.0", + "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", + "Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0", + "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0", + "Microsoft.AspNetCore.Authentication.Cookies": "1.1.0", + "Microsoft.AspNetCore.Authentication.Google": "1.1.0", + "Microsoft.AspNetCore.Authentication.Facebook": "1.1.0", + "Microsoft.AspNetCore.Authentication.Twitter": "1.1.0", + "Microsoft.AspNetCore.Authentication.MicrosoftAccount": "1.1.0", + "Microsoft.AspNetCore.Authentication": "1.1.0", + "IdentityServer4.AccessTokenValidation": "1.0.2", + "Microsoft.AspNetCore.Mvc": "1.1.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", + "Microsoft.NETCore.App": "1.1.0", + "CacheManager.Core": "0.9.2", + "CacheManager.Microsoft.Extensions.Configuration": "0.9.2", + "CacheManager.Microsoft.Extensions.Logging": "0.9.2", + "Consul": "0.7.2.1", + "Polly": "5.0.3", + "IdentityServer4": "1.0.1" + }, "runtimes": { "win10-x64": {}, "osx.10.11-x64": {}, diff --git a/test/Ocelot.AcceptanceTests/AdministrationTests.cs b/test/Ocelot.AcceptanceTests/AdministrationTests.cs index 525d9be5..93c4a8b9 100644 --- a/test/Ocelot.AcceptanceTests/AdministrationTests.cs +++ b/test/Ocelot.AcceptanceTests/AdministrationTests.cs @@ -1,10 +1,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Net; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; using TestStack.BDDfy; using Xunit; @@ -13,7 +9,6 @@ namespace Ocelot.AcceptanceTests { public class AdministrationTests : IDisposable { - private IWebHost _builder; private readonly Steps _steps; public AdministrationTests() @@ -81,7 +76,6 @@ namespace Ocelot.AcceptanceTests public void Dispose() { - _builder?.Dispose(); _steps.Dispose(); } } From 3c9ad87b73924fa7d53a8649d787b0415bd19190 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 19 Feb 2017 15:03:11 +0000 Subject: [PATCH 098/113] Fix cake on Mac. Also added compile-time support for OSX.10.12-x64. The acceptance tests still fail on this version of OS X, however, because of the funky stuff going on in TestConfiguration.cs. Fixing this is outside the scope of this issue. --- build.cake | 32 ++++++++++++++---------- test/Ocelot.AcceptanceTests/project.json | 7 +++--- test/Ocelot.Benchmarks/project.json | 5 ++-- test/Ocelot.ManualTest/project.json | 5 ++-- test/Ocelot.UnitTests/project.json | 5 ++-- 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/build.cake b/build.cake index 579f4e82..9a12e73a 100644 --- a/build.cake +++ b/build.cake @@ -80,7 +80,7 @@ Task("Version") if (AppVeyor.IsRunningOnAppVeyor) { Information("Persisting version number..."); - PersistVersion(nugetVersion); + PersistVersion(committedVersion, nugetVersion); buildVersion = nugetVersion; } else @@ -158,7 +158,7 @@ Task("CreatePackages") { EnsureDirectoryExists(packagesDir); - GenerateReleaseNotes(); + GenerateReleaseNotes(releaseNotesFile); var settings = new DotNetCorePackSettings { @@ -189,7 +189,7 @@ Task("ReleasePackagesToUnstableFeed") .IsDependentOn("CreatePackages") .Does(() => { - PublishPackages(nugetFeedUnstableKey, nugetFeedUnstableUploadUrl, nugetFeedUnstableSymbolsUploadUrl); + PublishPackages(packagesDir, artifactsFile, nugetFeedUnstableKey, nugetFeedUnstableUploadUrl, nugetFeedUnstableSymbolsUploadUrl); }); Task("EnsureStableReleaseRequirements") @@ -241,7 +241,7 @@ Task("ReleasePackagesToStableFeed") .IsDependentOn("DownloadGitHubReleaseArtifacts") .Does(() => { - PublishPackages(nugetFeedStableKey, nugetFeedStableUploadUrl, nugetFeedStableSymbolsUploadUrl); + PublishPackages(packagesDir, artifactsFile, nugetFeedStableKey, nugetFeedStableUploadUrl, nugetFeedStableSymbolsUploadUrl); }); Task("Release") @@ -262,9 +262,9 @@ private string GetNuGetVersionForCommit() } /// Updates project version in all of our projects -private void PersistVersion(string version) +private void PersistVersion(string committedVersion, string newVersion) { - Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, version)); + Information(string.Format("We'll search all project.json files for {0} and replace with {1}...", committedVersion, newVersion)); var projectJsonFiles = GetFiles("./**/project.json"); @@ -275,24 +275,30 @@ private void PersistVersion(string version) Information(string.Format("Updating {0}...", file)); var updatedProjectJson = System.IO.File.ReadAllText(file) - .Replace(committedVersion, version); + .Replace(committedVersion, newVersion); System.IO.File.WriteAllText(file, updatedProjectJson); } } /// generates release notes based on issues closed in GitHub since the last release -private void GenerateReleaseNotes() +private void GenerateReleaseNotes(string file) { - Information("Generating release notes at " + releaseNotesFile); + if (!IsRunningOnWindows()) + { + Warning("We can't generate release notes as we're not running on Windows."); + return; + } + + Information("Generating release notes at " + file); var releaseNotesExitCode = StartProcess( @"tools/GitReleaseNotes/tools/gitreleasenotes.exe", - new ProcessSettings { Arguments = ". /o " + releaseNotesFile }); + new ProcessSettings { Arguments = ". /o " + file }); - if (string.IsNullOrEmpty(System.IO.File.ReadAllText(releaseNotesFile))) + if (string.IsNullOrEmpty(System.IO.File.ReadAllText(file))) { - System.IO.File.WriteAllText(releaseNotesFile, "No issues closed since last release"); + System.IO.File.WriteAllText(file, "No issues closed since last release"); } if (releaseNotesExitCode != 0) @@ -302,7 +308,7 @@ private void GenerateReleaseNotes() } /// Publishes code and symbols packages to nuget feed, based on contents of artifacts file -private void PublishPackages(string feedApiKey, string codeFeedUrl, string symbolFeedUrl) +private void PublishPackages(string packagesDir, string artifactsFile, string feedApiKey, string codeFeedUrl, string symbolFeedUrl) { var artifacts = System.IO.File .ReadAllLines(artifactsFile) diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index 41594e1f..7739992a 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -38,9 +38,10 @@ "xunit": "2.2.0-rc1-build3507" }, "runtimes": { - "win10-x64": {}, - "osx.10.11-x64": {}, - "win7-x64": {} + "osx.10.11-x64":{}, + "osx.10.12-x64":{}, + "win7-x64": {}, + "win10-x64": {} }, "frameworks": { "netcoreapp1.1": { diff --git a/test/Ocelot.Benchmarks/project.json b/test/Ocelot.Benchmarks/project.json index 061a2223..615b9d0e 100644 --- a/test/Ocelot.Benchmarks/project.json +++ b/test/Ocelot.Benchmarks/project.json @@ -9,9 +9,10 @@ "BenchmarkDotNet": "0.10.2" }, "runtimes": { - "win10-x64": {}, "osx.10.11-x64":{}, - "win7-x64": {} + "osx.10.12-x64":{}, + "win7-x64": {}, + "win10-x64": {} }, "frameworks": { "netcoreapp1.1": { diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index cf67f9bd..433b1dc0 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -19,9 +19,10 @@ "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "runtimes": { - "win10-x64": {}, "osx.10.11-x64":{}, - "win7-x64": {} + "osx.10.12-x64":{}, + "win7-x64": {}, + "win10-x64": {} }, "frameworks": { "netcoreapp1.1": { diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index 2f42c283..30ad3684 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -27,9 +27,10 @@ "xunit": "2.2.0-rc1-build3507" }, "runtimes": { - "win10-x64": {}, "osx.10.11-x64":{}, - "win7-x64": {} + "osx.10.12-x64":{}, + "win7-x64": {}, + "win10-x64": {} }, "frameworks": { "netcoreapp1.1": { From d548a86327d2c4fd8ffbb09a660f5c52f7463788 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 19 Feb 2017 15:29:32 +0000 Subject: [PATCH 099/113] Added integration test project as acceptance style doesnt work when running the new admin area because identityserver needs to use proper networking --- Ocelot.sln | 9 +- build.cake | 23 +- .../FileConfigurationController.cs | 3 +- .../ServiceCollectionExtensions.cs | 4 +- .../Middleware/OcelotMiddlewareExtensions.cs | 4 +- .../AdministrationTests.cs | 82 ------- test/Ocelot.AcceptanceTests/Steps.cs | 22 +- .../AdministrationTests.cs | 209 ++++++++++++++++++ test/Ocelot.IntegrationTests/BearerToken.cs | 16 ++ .../Ocelot.IntegrationTests.xproj | 22 ++ .../Properties/AssemblyInfo.cs | 19 ++ test/Ocelot.IntegrationTests/appsettings.json | 10 + .../configuration.json | 1 + test/Ocelot.IntegrationTests/project.json | 50 +++++ 14 files changed, 384 insertions(+), 90 deletions(-) delete mode 100644 test/Ocelot.AcceptanceTests/AdministrationTests.cs create mode 100644 test/Ocelot.IntegrationTests/AdministrationTests.cs create mode 100644 test/Ocelot.IntegrationTests/BearerToken.cs create mode 100644 test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.xproj create mode 100644 test/Ocelot.IntegrationTests/Properties/AssemblyInfo.cs create mode 100644 test/Ocelot.IntegrationTests/appsettings.json create mode 100644 test/Ocelot.IntegrationTests/configuration.json create mode 100644 test/Ocelot.IntegrationTests/project.json diff --git a/Ocelot.sln b/Ocelot.sln index 6c0006b0..d88020d3 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -8,7 +8,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FA7C349-DBE8-4904-A2CE-015B8869CE6C}" ProjectSection(SolutionItems) = preProject .gitignore = .gitignore - appveyor.yml = appveyor.yml build-and-release-unstable.ps1 = build-and-release-unstable.ps1 build-and-run-tests.ps1 = build-and-run-tests.ps1 build.cake = build.cake @@ -19,7 +18,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution GitVersion.yml = GitVersion.yml global.json = global.json LICENSE.md = LICENSE.md - Ocelot.nuspec = Ocelot.nuspec README.md = README.md release.ps1 = release.ps1 ReleaseNotes.md = ReleaseNotes.md @@ -41,6 +39,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot.ManualTest", "test\O EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot.Benchmarks", "test\Ocelot.Benchmarks\Ocelot.Benchmarks.xproj", "{106B49E6-95F6-4A7B-B81C-96BFA74AF035}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ocelot.IntegrationTests", "test\Ocelot.IntegrationTests\Ocelot.IntegrationTests.xproj", "{D4575572-99CA-4530-8737-C296EDA326F8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -67,6 +67,10 @@ Global {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Debug|Any CPU.Build.0 = Debug|Any CPU {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.ActiveCfg = Release|Any CPU {106B49E6-95F6-4A7B-B81C-96BFA74AF035}.Release|Any CPU.Build.0 = Release|Any CPU + {D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4575572-99CA-4530-8737-C296EDA326F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4575572-99CA-4530-8737-C296EDA326F8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -77,5 +81,6 @@ Global {F8C224FE-36BE-45F5-9B0E-666D8F4A9B52} = {5B401523-36DA-4491-B73A-7590A26E420B} {02BBF4C5-517E-4157-8D21-4B8B9E118B7A} = {5B401523-36DA-4491-B73A-7590A26E420B} {106B49E6-95F6-4A7B-B81C-96BFA74AF035} = {5B401523-36DA-4491-B73A-7590A26E420B} + {D4575572-99CA-4530-8737-C296EDA326F8} = {5B401523-36DA-4491-B73A-7590A26E420B} EndGlobalSection EndGlobal diff --git a/build.cake b/build.cake index 579f4e82..d3aee78f 100644 --- a/build.cake +++ b/build.cake @@ -18,7 +18,9 @@ var unitTestAssemblies = @"./test/Ocelot.UnitTests"; // acceptance testing var artifactsForAcceptanceTestsDir = artifactsDir + Directory("AcceptanceTests"); -var acceptanceTestAssemblies = @"./test/Ocelot.AcceptanceTests"; + +// integration testing +var artifactsForIntegrationTestsDir = artifactsDir + Directory("IntegrationTests"); // benchmark testing var artifactsForBenchmarkTestsDir = artifactsDir + Directory("BenchmarkTests"); @@ -129,6 +131,24 @@ Task("RunAcceptanceTests") }); + Task("RunIntegrationTests") + .IsDependentOn("Restore") + .Does(() => + { + var buildSettings = new DotNetCoreTestSettings + { + Configuration = "Debug", //int test config is hard-coded for debug + }; + + EnsureDirectoryExists(artifactsForIntegrationTestsDir); + + DoInDirectory("test/Ocelot.IntegrationTests", () => + { + DotNetCoreTest(".", buildSettings); + }); + + }); + Task("RunBenchmarkTests") .IsDependentOn("Restore") .Does(() => @@ -149,6 +169,7 @@ Task("RunBenchmarkTests") Task("RunTests") .IsDependentOn("RunUnitTests") .IsDependentOn("RunAcceptanceTests") + .IsDependentOn("RunIntegrationTests") .Does(() => { }); diff --git a/src/Ocelot/Controllers/FileConfigurationController.cs b/src/Ocelot/Controllers/FileConfigurationController.cs index 3589bdb5..3dc22303 100644 --- a/src/Ocelot/Controllers/FileConfigurationController.cs +++ b/src/Ocelot/Controllers/FileConfigurationController.cs @@ -4,7 +4,7 @@ using Ocelot.Services; namespace Ocelot.Controllers { - [Authorize(Roles = "Admin")] + [Authorize] [Route("configuration")] public class FileConfigurationController : Controller { @@ -18,7 +18,6 @@ namespace Ocelot.Controllers [HttpGet] public IActionResult Get() { - var user = this.HttpContext.User; return new OkObjectResult(_getFileConfig.Invoke().Data); } } diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index c06d965b..c213aa6b 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -112,7 +112,9 @@ namespace Ocelot.DependencyInjection SubjectId = "admin", } }); - services.AddMvcCore().AddJsonFormatters(); + services.AddMvcCore() + .AddAuthorization() + .AddJsonFormatters(); services.AddLogging(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index ab51e8da..fa113771 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -148,9 +148,11 @@ namespace Ocelot.Middleware { builder.Map(configuration.AdministrationPath, app => { + var identityServerUrl = $"http://localhost:5000/{configuration.AdministrationPath.Remove(0,1)}"; + app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions { - Authority = "http://localhost:5000/admin", + Authority = identityServerUrl, ApiName = "admin", RequireHttpsMetadata = false, AllowedScopes = new List(), diff --git a/test/Ocelot.AcceptanceTests/AdministrationTests.cs b/test/Ocelot.AcceptanceTests/AdministrationTests.cs deleted file mode 100644 index 93c4a8b9..00000000 --- a/test/Ocelot.AcceptanceTests/AdministrationTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using Ocelot.Configuration.File; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - public class AdministrationTests : IDisposable - { - private readonly Steps _steps; - - public AdministrationTests() - { - _steps = new Steps(); - } - - [Fact] - public void should_return_response_200_with_call_re_routes_controller() - { - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - AdministrationPath = "/administration" - } - }; - - this.Given(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_return_file_configuration() - { - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - AdministrationPath = "/administration" - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHost = "localhost", - DownstreamPort = 80, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = "get", - UpstreamPathTemplate = "/" - }, - new FileReRoute() - { - DownstreamHost = "localhost", - DownstreamPort = 80, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = "get", - UpstreamPathTemplate = "/test" - } - } - }; - - this.Given(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseShouldBe(configuration)) - .BDDfy(); - } - - public void Dispose() - { - _steps.Dispose(); - } - } -} diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 4a946947..1f4a8da1 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -133,7 +133,7 @@ namespace Ocelot.AcceptanceTests }) .Configure(a => { - a.UseOcelot(ocelotMiddlewareConfig); + a.UseOcelot(ocelotMiddlewareConfig).Wait(); })); _ocelotClient = _ocelotServer.CreateClient(); @@ -167,6 +167,26 @@ namespace Ocelot.AcceptanceTests } } + public void GivenIHaveAnOcelotToken(string adminPath) + { + var tokenUrl = $"{adminPath}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "admin"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "admin"), + new KeyValuePair("username", "admin"), + new KeyValuePair("password", "admin"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + var response = _ocelotClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + public void VerifyIdentiryServerStarted(string url) { using (var httpClient = new HttpClient()) diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs new file mode 100644 index 00000000..6ea1aa32 --- /dev/null +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using Microsoft.AspNetCore.Hosting; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.ManualTest; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.IntegrationTests +{ + public class AdministrationTests : IDisposable + { + private readonly HttpClient _httpClient; + private HttpResponseMessage _response; + private IWebHost _builder; + private readonly string _ocelotBaseUrl; + private BearerToken _token; + + public AdministrationTests() + { + _httpClient = new HttpClient(); + _ocelotBaseUrl = "http://localhost:5000"; + _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); + } + + [Fact] + public void should_return_response_401_with_call_re_routes_controller() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + } + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_call_re_routes_controller() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + } + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void should_return_file_configuration() + { + var configuration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + }, + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = "get", + UpstreamPathTemplate = "/" + }, + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = "get", + UpstreamPathTemplate = "/test" + } + } + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseShouldBe(configuration)) + .BDDfy(); + } + + 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", "admin"), + 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() + { + _builder = new WebHostBuilder() + .UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup() + .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); + + configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + private void WhenIGetUrlOnTheApiGateway(string url) + { + _response = _httpClient.GetAsync(url).Result; + } + + private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) + { + _response.StatusCode.ShouldBe(expectedHttpStatusCode); + } + + public void Dispose() + { + _builder?.Dispose(); + _httpClient?.Dispose(); + } + } +} diff --git a/test/Ocelot.IntegrationTests/BearerToken.cs b/test/Ocelot.IntegrationTests/BearerToken.cs new file mode 100644 index 00000000..efd35c33 --- /dev/null +++ b/test/Ocelot.IntegrationTests/BearerToken.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Ocelot.IntegrationTests +{ + class BearerToken + { + [JsonProperty("access_token")] + public string AccessToken { get; set; } + + [JsonProperty("expires_in")] + public int ExpiresIn { get; set; } + + [JsonProperty("token_type")] + public string TokenType { get; set; } + } +} diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.xproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.xproj new file mode 100644 index 00000000..ece2b3b5 --- /dev/null +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + d4575572-99ca-4530-8737-c296eda326f8 + Ocelot.IntegrationTests + .\obj + .\bin\ + v4.6.1 + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/Properties/AssemblyInfo.cs b/test/Ocelot.IntegrationTests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..5535cd70 --- /dev/null +++ b/test/Ocelot.IntegrationTests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Ocelot.IntegrationTests")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d4575572-99ca-4530-8737-c296eda326f8")] diff --git a/test/Ocelot.IntegrationTests/appsettings.json b/test/Ocelot.IntegrationTests/appsettings.json new file mode 100644 index 00000000..d73b7dcb --- /dev/null +++ b/test/Ocelot.IntegrationTests/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": true, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/test/Ocelot.IntegrationTests/configuration.json b/test/Ocelot.IntegrationTests/configuration.json new file mode 100644 index 00000000..3f39532c --- /dev/null +++ b/test/Ocelot.IntegrationTests/configuration.json @@ -0,0 +1 @@ +{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration"}} \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/project.json b/test/Ocelot.IntegrationTests/project.json new file mode 100644 index 00000000..7454b3a0 --- /dev/null +++ b/test/Ocelot.IntegrationTests/project.json @@ -0,0 +1,50 @@ +{ + "version": "0.0.0-dev", + + "buildOptions": { + "copyToOutput": { + "include": [ + "configuration.json", + "appsettings.json" + ] + } + }, + + "testRunner": "xunit", + + "dependencies": { + "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", + "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", + "Microsoft.Extensions.Configuration.Json": "1.1.0", + "Microsoft.Extensions.Logging": "1.1.0", + "Microsoft.Extensions.Logging.Console": "1.1.0", + "Microsoft.Extensions.Logging.Debug": "1.1.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", + "Microsoft.AspNetCore.Http": "1.1.0", + "Microsoft.DotNet.InternalAbstractions": "1.0.0", + "Ocelot": "0.0.0-dev", + "xunit": "2.2.0-beta2-build3300", + "dotnet-test-xunit": "2.2.0-preview2-build1029", + "Ocelot.ManualTest": "0.0.0-dev", + "Microsoft.AspNetCore.TestHost": "1.1.0", + "IdentityServer4": "1.0.1", + "Microsoft.AspNetCore.Mvc": "1.1.0", + "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", + "Microsoft.NETCore.App": "1.1.0", + "Shouldly": "2.8.2", + "TestStack.BDDfy": "4.3.2", + "Consul": "0.7.2.1" + }, + "runtimes": { + "win10-x64": {}, + "osx.10.11-x64": {}, + "win7-x64": {} + }, + "frameworks": { + "netcoreapp1.1": { + "imports": [ + ] + } + } +} From eb5dfc697538f8956e1dd3f04edf45fba3c686f6 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sun, 19 Feb 2017 15:43:58 +0000 Subject: [PATCH 100/113] removed test configuration nonsense --- .../TestConfiguration.cs | 36 +++---------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index ce802efb..3265955d 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -1,36 +1,10 @@ -namespace Ocelot.AcceptanceTests -{ - using System.Runtime.InteropServices; +using System; +using System.IO; +namespace Ocelot.AcceptanceTests +{ public static class TestConfiguration { - public static double Version => 1.1; - public static string ConfigurationPath => GetConfigurationPath(); - - public static string GetConfigurationPath() - { - var osArchitecture = RuntimeInformation.OSArchitecture.ToString(); - - if(RuntimeInformation.OSDescription.ToLower().Contains("darwin")) - { - return FormatConfigurationPath("osx.10.11", osArchitecture); - } - - if(RuntimeInformation.OSDescription.ToLower().Contains("microsoft windows 10")) - { - return FormatConfigurationPath("win10", osArchitecture); - } - - return FormatConfigurationPath("win7", osArchitecture); - } - - private static string FormatConfigurationPath(string oSDescription, string osArchitecture) - { - var runTime = $"{oSDescription}-{osArchitecture}".ToLower(); - - var configPath = $"./bin/Debug/netcoreapp{Version}/{runTime}/configuration.json"; - - return configPath; - } + public static string ConfigurationPath => Path.Combine(AppContext.BaseDirectory, "configuration.json"); } } From f1a403ae29bbc5b5d2d096e5a67bd07c5d8c66f1 Mon Sep 17 00:00:00 2001 From: Philip Wood Date: Sun, 19 Feb 2017 20:56:05 +0000 Subject: [PATCH 101/113] Fix package publishing --- build.cake | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/build.cake b/build.cake index 9a12e73a..94af4431 100644 --- a/build.cake +++ b/build.cake @@ -282,7 +282,7 @@ private void PersistVersion(string committedVersion, string newVersion) } /// generates release notes based on issues closed in GitHub since the last release -private void GenerateReleaseNotes(string file) +private void GenerateReleaseNotes(ConvertableFilePath file) { if (!IsRunningOnWindows()) { @@ -308,7 +308,7 @@ private void GenerateReleaseNotes(string file) } /// Publishes code and symbols packages to nuget feed, based on contents of artifacts file -private void PublishPackages(string packagesDir, string artifactsFile, string feedApiKey, string codeFeedUrl, string symbolFeedUrl) +private void PublishPackages(ConvertableDirectoryPath packagesDir, ConvertableFilePath artifactsFile, string feedApiKey, string codeFeedUrl, string symbolFeedUrl) { var artifacts = System.IO.File .ReadAllLines(artifactsFile) @@ -317,8 +317,7 @@ private void PublishPackages(string packagesDir, string artifactsFile, string fe var codePackage = packagesDir + File(artifacts["nuget"]); - Information("Pushing package"); - + Information("Pushing package " + codePackage); NuGetPush( codePackage, new NuGetPushSettings { From 2dfdf0bb865f35cec5c665dd9528185a0d02f8c5 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Tue, 21 Feb 2017 07:34:47 +0000 Subject: [PATCH 102/113] after much hacking unit tests passing --- Ocelot.nuspec | 48 ---------- appveyor.yml | 8 -- .../Cache/Middleware/OutputCacheMiddleware.cs | 1 - .../Middleware/ClaimsBuilderMiddleware.cs | 1 - .../Configuration/Builder/ReRouteBuilder.cs | 3 +- .../Creator/FileOcelotConfigurationCreator.cs | 13 ++- .../Creator/IOcelotConfigurationCreator.cs | 2 + .../Configuration/File/FileQoSOptions.cs | 7 +- .../Provider/IOcelotConfigurationProvider.cs | 5 +- .../Provider/OcelotConfigurationProvider.cs | 25 +---- src/Ocelot/Configuration/ReRoute.cs | 3 +- .../Setter/FileConfigurationSetter.cs | 32 +++++++ .../Setter/IFileConfigurationSetter.cs | 11 +++ .../FileConfigurationController.cs | 33 ++++++- .../ServiceCollectionExtensions.cs | 7 +- .../Finder/DownstreamRouteFinder.cs | 5 +- .../Finder/IDownstreamRouteFinder.cs | 5 +- .../DownstreamRouteFinderMiddleware.cs | 3 +- .../DownstreamUrlCreator/IUrlBuilder.cs | 4 +- .../DownstreamUrlCreatorMiddleware.cs | 3 - src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs | 2 - .../Middleware/ExceptionHandlerMiddleware.cs | 2 - .../HttpRequestHeadersBuilderMiddleware.cs | 1 - src/Ocelot/Headers/RemoveOutputHeaders.cs | 2 - .../LoadBalancers/ILoadBalancer.cs | 1 - .../LoadBalancers/LoadBalancerHouse.cs | 5 +- .../Middleware/OcelotMiddlewareExtensions.cs | 23 ++++- src/Ocelot/Properties/AssemblyInfo.cs | 1 - .../QueryStringBuilderMiddleware.cs | 1 - src/Ocelot/Responses/ErrorResponse.cs | 3 + ...ration.cs => FileConfigurationProvider.cs} | 4 +- ...ation.cs => IFileConfigurationProvider.cs} | 4 +- src/Ocelot/project.json | 1 + test/Ocelot.AcceptanceTests/project.json | 1 + test/Ocelot.Benchmarks/project.json | 1 + test/Ocelot.IntegrationTests/project.json | 1 + test/Ocelot.ManualTest/project.json | 1 + .../FileConfigurationProviderTests.cs | 39 +------- .../FileConfigurationSetterTests.cs | 86 +++++++++++++++++ .../FileConfigurationControllerTests.cs | 96 +++++++++++++++++-- .../DownstreamRouteFinderMiddlewareTests.cs | 2 +- .../DownstreamRouteFinderTests.cs | 4 +- ...sts.cs => FileConfigurationGetterTests.cs} | 6 +- test/Ocelot.UnitTests/project.json | 1 + 44 files changed, 313 insertions(+), 194 deletions(-) delete mode 100644 Ocelot.nuspec delete mode 100644 appveyor.yml create mode 100644 src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs create mode 100644 src/Ocelot/Configuration/Setter/IFileConfigurationSetter.cs rename src/Ocelot/Services/{GetFileConfiguration.cs => FileConfigurationProvider.cs} (79%) rename src/Ocelot/Services/{IGetFileConfiguration.cs => IFileConfigurationProvider.cs} (52%) create mode 100644 test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs rename test/Ocelot.UnitTests/Services/{GetFileConfigurationTests.cs => FileConfigurationGetterTests.cs} (95%) diff --git a/Ocelot.nuspec b/Ocelot.nuspec deleted file mode 100644 index d236d60d..00000000 --- a/Ocelot.nuspec +++ /dev/null @@ -1,48 +0,0 @@ - - - - Ocelot - 1.0.0 - Tom Pallister - Tom Pallister - https://github.com/TomPallister/Ocelot/blob/develop/LICENSE.md - https://github.com/TomPallister/Ocelot - false - Ocelot Api Gateway - Latest Ocelot - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 6e8b16f6..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: 1.0.{build} -configuration: -- Release -platform: Any CPU -build_script: -- build.ps1 -cache: -- '%USERPROFILE%\.nuget\packages' \ No newline at end of file diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 77024e70..948a7397 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -2,7 +2,6 @@ using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; diff --git a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs index 1f6486f6..1b1745e2 100644 --- a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs +++ b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs @@ -1,7 +1,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 382cf374..8282a11c 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Net.Http; using Ocelot.Values; diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 8025f59d..049c0fff 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -56,14 +56,21 @@ namespace Ocelot.Configuration.Creator public async Task> Create() { - var config = await SetUpConfiguration(); + var config = await SetUpConfiguration(_options.Value); return new OkResponse(config); } - private async Task SetUpConfiguration() + public async Task> Create(FileConfiguration fileConfiguration) + { + var config = await SetUpConfiguration(fileConfiguration); + + return new OkResponse(config); + } + + private async Task SetUpConfiguration(FileConfiguration fileConfiguration) { - var response = _configurationValidator.IsValid(_options.Value); + var response = _configurationValidator.IsValid(fileConfiguration); if (response.Data.IsError) { diff --git a/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs index 7547d91f..99d1d39d 100644 --- a/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Ocelot.Configuration.File; using Ocelot.Responses; namespace Ocelot.Configuration.Creator @@ -6,5 +7,6 @@ namespace Ocelot.Configuration.Creator public interface IOcelotConfigurationCreator { Task> Create(); + Task> Create(FileConfiguration fileConfiguration); } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/File/FileQoSOptions.cs b/src/Ocelot/Configuration/File/FileQoSOptions.cs index dfac3ac6..876a3fd5 100644 --- a/src/Ocelot/Configuration/File/FileQoSOptions.cs +++ b/src/Ocelot/Configuration/File/FileQoSOptions.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.Configuration.File +namespace Ocelot.Configuration.File { public class FileQoSOptions { diff --git a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs index 3256e44a..30ded2e9 100644 --- a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs @@ -1,10 +1,9 @@ -using System.Threading.Tasks; -using Ocelot.Responses; +using Ocelot.Responses; namespace Ocelot.Configuration.Provider { public interface IOcelotConfigurationProvider { - Task> Get(); + Response Get(); } } diff --git a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs index 80fd5697..e416ed8e 100644 --- a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs @@ -1,6 +1,4 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.Repository; +using Ocelot.Configuration.Repository; using Ocelot.Responses; namespace Ocelot.Configuration.Provider @@ -11,16 +9,13 @@ namespace Ocelot.Configuration.Provider public class OcelotConfigurationProvider : IOcelotConfigurationProvider { private readonly IOcelotConfigurationRepository _repo; - private readonly IOcelotConfigurationCreator _creator; - public OcelotConfigurationProvider(IOcelotConfigurationRepository repo, - IOcelotConfigurationCreator creator) + public OcelotConfigurationProvider(IOcelotConfigurationRepository repo) { _repo = repo; - _creator = creator; } - public async Task> Get() + public Response Get() { var repoConfig = _repo.Get(); @@ -29,20 +24,6 @@ namespace Ocelot.Configuration.Provider return new ErrorResponse(repoConfig.Errors); } - if (repoConfig.Data == null) - { - var creatorConfig = await _creator.Create(); - - if (creatorConfig.IsError) - { - return new ErrorResponse(creatorConfig.Errors); - } - - _repo.AddOrReplace(creatorConfig.Data); - - return new OkResponse(creatorConfig.Data); - } - return new OkResponse(repoConfig.Data); } } diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index ab9405d0..42a0aa79 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Net.Http; using Ocelot.Values; diff --git a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs new file mode 100644 index 00000000..28957cc7 --- /dev/null +++ b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Repository; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Setter +{ + public class FileConfigurationSetter : IFileConfigurationSetter + { + private readonly IOcelotConfigurationRepository _configRepo; + private readonly IOcelotConfigurationCreator _configCreator; + + public FileConfigurationSetter(IOcelotConfigurationRepository configRepo, IOcelotConfigurationCreator configCreator) + { + _configRepo = configRepo; + _configCreator = configCreator; + } + + public async Task Set(FileConfiguration fileConfig) + { + var config = await _configCreator.Create(fileConfig); + + if(!config.IsError) + { + _configRepo.AddOrReplace(config.Data); + } + + return new ErrorResponse(config.Errors); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Setter/IFileConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/IFileConfigurationSetter.cs new file mode 100644 index 00000000..8ab31d1b --- /dev/null +++ b/src/Ocelot/Configuration/Setter/IFileConfigurationSetter.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Setter +{ + public interface IFileConfigurationSetter + { + Task Set(FileConfiguration config); + } +} \ No newline at end of file diff --git a/src/Ocelot/Controllers/FileConfigurationController.cs b/src/Ocelot/Controllers/FileConfigurationController.cs index 3dc22303..a378f530 100644 --- a/src/Ocelot/Controllers/FileConfigurationController.cs +++ b/src/Ocelot/Controllers/FileConfigurationController.cs @@ -1,5 +1,8 @@ +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Setter; using Ocelot.Services; namespace Ocelot.Controllers @@ -8,17 +11,39 @@ namespace Ocelot.Controllers [Route("configuration")] public class FileConfigurationController : Controller { - private readonly IGetFileConfiguration _getFileConfig; + private readonly IFileConfigurationProvider _configGetter; + private readonly IFileConfigurationSetter _configSetter; - public FileConfigurationController(IGetFileConfiguration getFileConfig) + public FileConfigurationController(IFileConfigurationProvider getFileConfig, IFileConfigurationSetter configSetter) { - _getFileConfig = getFileConfig; + _configGetter = getFileConfig; + _configSetter = configSetter; } [HttpGet] public IActionResult Get() { - return new OkObjectResult(_getFileConfig.Invoke().Data); + var response = _configGetter.Get(); + + if(response.IsError) + { + return new BadRequestObjectResult(response.Errors); + } + + return new OkObjectResult(response.Data); + } + + [HttpPost] + public async Task Post(FileConfiguration fileConfiguration) + { + var response = await _configSetter.Set(fileConfiguration); + + if(response.IsError) + { + return new BadRequestObjectResult(response.Errors); + } + + return new OkObjectResult(fileConfiguration); } } } \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index c213aa6b..43ffefe3 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -1,14 +1,12 @@ using System; using System.Collections.Generic; using System.Net.Http; -using System.Security.Claims; using CacheManager.Core; using IdentityServer4.Models; using IdentityServer4.Test; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Ocelot.Authentication.Handler.Creator; using Ocelot.Authentication.Handler.Factory; using Ocelot.Authorisation; @@ -46,7 +44,6 @@ namespace Ocelot.DependencyInjection { var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", settings); var ocelotCacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); - services.AddSingleton>(cacheManagerOutputCache); services.AddSingleton>(ocelotCacheManager); @@ -55,11 +52,9 @@ namespace Ocelot.DependencyInjection public static IServiceCollection AddOcelotFileConfiguration(this IServiceCollection services, IConfigurationRoot configurationRoot) { services.Configure(configurationRoot); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - return services; } @@ -116,7 +111,7 @@ namespace Ocelot.DependencyInjection .AddAuthorization() .AddJsonFormatters(); services.AddLogging(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 8b6447c7..6773feb8 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using Ocelot.Configuration.Provider; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Errors; @@ -22,9 +21,9 @@ namespace Ocelot.DownstreamRouteFinder.Finder _urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; } - public async Task> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod) + public Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod) { - var configuration = await _configProvider.Get(); + var configuration = _configProvider.Get(); var applicableReRoutes = configuration.Data.ReRoutes.Where(r => string.Equals(r.UpstreamHttpMethod.Method, upstreamHttpMethod, StringComparison.CurrentCultureIgnoreCase)); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs index 7ae3ff79..e351ab2f 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs @@ -1,10 +1,9 @@ -using System.Threading.Tasks; -using Ocelot.Responses; +using Ocelot.Responses; namespace Ocelot.DownstreamRouteFinder.Finder { public interface IDownstreamRouteFinder { - Task> FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod); + Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod); } } diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index e88bfde8..02e3e4f6 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -1,6 +1,5 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; @@ -34,7 +33,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware _logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath); - var downstreamRoute = await _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method); + var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.Request.Method); if (downstreamRoute.IsError) { diff --git a/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs index 18683e62..8a07a066 100644 --- a/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs +++ b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs @@ -1,6 +1,4 @@ -using Ocelot.Configuration; -using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; -using Ocelot.Responses; +using Ocelot.Responses; using Ocelot.Values; namespace Ocelot.DownstreamUrlCreator diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 80365074..631e278a 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -1,12 +1,9 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using Ocelot.Configuration; using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; -using Ocelot.Values; namespace Ocelot.DownstreamUrlCreator.Middleware { diff --git a/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs index 2124ce3b..43a63715 100644 --- a/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs +++ b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using Ocelot.Configuration; -using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; using Ocelot.Errors; using Ocelot.Responses; using Ocelot.Values; diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs index 6e6b511e..ea5d2c84 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs @@ -1,8 +1,6 @@ using System; -using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; diff --git a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs index cc262048..a89d2ec2 100644 --- a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs @@ -1,7 +1,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; diff --git a/src/Ocelot/Headers/RemoveOutputHeaders.cs b/src/Ocelot/Headers/RemoveOutputHeaders.cs index 7a40534b..f3020036 100644 --- a/src/Ocelot/Headers/RemoveOutputHeaders.cs +++ b/src/Ocelot/Headers/RemoveOutputHeaders.cs @@ -3,8 +3,6 @@ using Ocelot.Responses; namespace Ocelot.Headers { - using System; - public class RemoveOutputHeaders : IRemoveOutputHeaders { /// diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs index 73d25d48..77dc328d 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs @@ -1,4 +1,3 @@ -using System; using System.Threading.Tasks; using Ocelot.Responses; using Ocelot.Values; diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs index cc6ea73b..68e42fe2 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LoadBalancerHouse.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.Collections.Generic; using Ocelot.Responses; namespace Ocelot.LoadBalancer.LoadBalancers diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index fa113771..9aa7442f 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -20,8 +20,11 @@ namespace Ocelot.Middleware using System.Threading.Tasks; using Authorisation.Middleware; using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Options; using Ocelot.Configuration; + using Ocelot.Configuration.File; using Ocelot.Configuration.Provider; + using Ocelot.Configuration.Setter; using Ocelot.LoadBalancer.Middleware; public static class OcelotMiddlewareExtensions @@ -127,17 +130,27 @@ namespace Ocelot.Middleware private static async Task CreateConfiguration(IApplicationBuilder builder) { + var fileConfig = (IOptions)builder.ApplicationServices.GetService(typeof(IOptions)); + + var configSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); + var configProvider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider)); - var config = await configProvider.Get(); + var config = await configSetter.Set(fileConfig.Value); - //todo move this to config validators - if(config == null || config.Data == null || config.IsError) + if(config == null || config.IsError) { - throw new Exception("Unable to start Ocelot: configuration was invalid"); + throw new Exception("Unable to start Ocelot: configuration was not set up correctly."); } - return config.Data; + var ocelotConfiguration = configProvider.Get(); + + if(ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError) + { + throw new Exception("Unable to start Ocelot: ocelot configuration was not returned by provider."); + } + + return ocelotConfiguration.Data; } private static async Task CreateAdministrationArea(IApplicationBuilder builder) diff --git a/src/Ocelot/Properties/AssemblyInfo.cs b/src/Ocelot/Properties/AssemblyInfo.cs index ad12027e..dd8b7610 100644 --- a/src/Ocelot/Properties/AssemblyInfo.cs +++ b/src/Ocelot/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs index 1424d713..edeee51c 100644 --- a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs +++ b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs @@ -1,7 +1,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; diff --git a/src/Ocelot/Responses/ErrorResponse.cs b/src/Ocelot/Responses/ErrorResponse.cs index 9f541507..bf20dbd1 100644 --- a/src/Ocelot/Responses/ErrorResponse.cs +++ b/src/Ocelot/Responses/ErrorResponse.cs @@ -5,6 +5,9 @@ namespace Ocelot.Responses { public class ErrorResponse : Response { + public ErrorResponse(Error error) : base(new List{error}) + { + } public ErrorResponse(List errors) : base(errors) { } diff --git a/src/Ocelot/Services/GetFileConfiguration.cs b/src/Ocelot/Services/FileConfigurationProvider.cs similarity index 79% rename from src/Ocelot/Services/GetFileConfiguration.cs rename to src/Ocelot/Services/FileConfigurationProvider.cs index 536a0b46..abac93f9 100644 --- a/src/Ocelot/Services/GetFileConfiguration.cs +++ b/src/Ocelot/Services/FileConfigurationProvider.cs @@ -6,9 +6,9 @@ using Ocelot.Responses; namespace Ocelot.Services { - public class GetFileConfiguration : IGetFileConfiguration + public class FileConfigurationProvider : IFileConfigurationProvider { - public Response Invoke() + public Response Get() { var configFilePath = $"{AppContext.BaseDirectory}/configuration.json"; var json = File.ReadAllText(configFilePath); diff --git a/src/Ocelot/Services/IGetFileConfiguration.cs b/src/Ocelot/Services/IFileConfigurationProvider.cs similarity index 52% rename from src/Ocelot/Services/IGetFileConfiguration.cs rename to src/Ocelot/Services/IFileConfigurationProvider.cs index 08b950a8..725f7463 100644 --- a/src/Ocelot/Services/IGetFileConfiguration.cs +++ b/src/Ocelot/Services/IFileConfigurationProvider.cs @@ -3,8 +3,8 @@ using Ocelot.Responses; namespace Ocelot.Services { - public interface IGetFileConfiguration + public interface IFileConfigurationProvider { - Response Invoke(); + Response Get(); } } \ No newline at end of file diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 7b169503..1cb4b0c9 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -34,6 +34,7 @@ "runtimes": { "win10-x64": {}, "osx.10.11-x64": {}, + "osx.10.12-x64": {}, "win7-x64": {} }, "frameworks": { diff --git a/test/Ocelot.AcceptanceTests/project.json b/test/Ocelot.AcceptanceTests/project.json index f1aa378b..b77fe483 100644 --- a/test/Ocelot.AcceptanceTests/project.json +++ b/test/Ocelot.AcceptanceTests/project.json @@ -38,6 +38,7 @@ "runtimes": { "win10-x64": {}, "osx.10.11-x64": {}, + "osx.10.12-x64": {}, "win7-x64": {} }, "frameworks": { diff --git a/test/Ocelot.Benchmarks/project.json b/test/Ocelot.Benchmarks/project.json index 061a2223..98a414f1 100644 --- a/test/Ocelot.Benchmarks/project.json +++ b/test/Ocelot.Benchmarks/project.json @@ -11,6 +11,7 @@ "runtimes": { "win10-x64": {}, "osx.10.11-x64":{}, + "osx.10.12-x64": {}, "win7-x64": {} }, "frameworks": { diff --git a/test/Ocelot.IntegrationTests/project.json b/test/Ocelot.IntegrationTests/project.json index 7454b3a0..578f9a7f 100644 --- a/test/Ocelot.IntegrationTests/project.json +++ b/test/Ocelot.IntegrationTests/project.json @@ -39,6 +39,7 @@ "runtimes": { "win10-x64": {}, "osx.10.11-x64": {}, + "osx.10.12-x64": {}, "win7-x64": {} }, "frameworks": { diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index 04dba415..a31fdca2 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -23,6 +23,7 @@ "runtimes": { "win10-x64": {}, "osx.10.11-x64":{}, + "osx.10.12-x64": {}, "win7-x64": {} }, "frameworks": { diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs index 2573e32f..c7d240f8 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs @@ -16,14 +16,12 @@ namespace Ocelot.UnitTests.Configuration { private readonly IOcelotConfigurationProvider _ocelotConfigurationProvider; private readonly Mock _configurationRepository; - private readonly Mock _creator; private Response _result; public FileConfigurationProviderTests() { - _creator = new Mock(); _configurationRepository = new Mock(); - _ocelotConfigurationProvider = new OcelotConfigurationProvider(_configurationRepository.Object, _creator.Object); + _ocelotConfigurationProvider = new OcelotConfigurationProvider(_configurationRepository.Object); } [Fact] @@ -35,16 +33,6 @@ namespace Ocelot.UnitTests.Configuration .BDDfy(); } - [Fact] - public void should_create_config_if_it_doesnt_exist() - { - this.Given(x => x.GivenTheRepoReturns(new OkResponse(null))) - .And(x => x.GivenTheCreatorReturns(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) - .When(x => x.WhenIGetTheConfig()) - .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) - .BDDfy(); - } - [Fact] public void should_return_error() { @@ -61,29 +49,6 @@ namespace Ocelot.UnitTests.Configuration .BDDfy(); } - [Fact] - public void should_return_error_if_creator_errors() - { - this.Given(x => x.GivenTheRepoReturns(new OkResponse(null))) - .And(x => x.GivenTheCreatorReturns(new ErrorResponse(new List - { - new AnyError() - }))) - .When(x => x.WhenIGetTheConfig()) - .Then(x => x.TheFollowingIsReturned(new ErrorResponse(new List - { - new AnyError() - }))) - .BDDfy(); - } - - private void GivenTheCreatorReturns(Response config) - { - _creator - .Setup(x => x.Create()) - .ReturnsAsync(config); - } - private void GivenTheRepoReturns(Response config) { _configurationRepository @@ -93,7 +58,7 @@ namespace Ocelot.UnitTests.Configuration private void WhenIGetTheConfig() { - _result = _ocelotConfigurationProvider.Get().Result; + _result = _ocelotConfigurationProvider.Get(); } private void TheFollowingIsReturned(Response expected) diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs new file mode 100644 index 00000000..aa9b935a --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Repository; +using Ocelot.Configuration.Setter; +using Ocelot.Errors; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class FileConfigurationSetterTests + { + private FileConfiguration _fileConfiguration; + private FileConfigurationSetter _configSetter; + private Mock _configRepo; + private Mock _configCreator; + private Response _configuration; + private object _result; + + public FileConfigurationSetterTests() + { + _configRepo = new Mock(); + _configCreator = new Mock(); + _configSetter = new FileConfigurationSetter(_configRepo.Object, _configCreator.Object); + } + + [Fact] + public void should_set_configuration() + { + var fileConfig = new FileConfiguration(); + var config = new OcelotConfiguration(new List(), string.Empty); + + this.Given(x => GivenTheFollowingConfiguration(fileConfig)) + .And(x => GivenTheCreatorReturns(new OkResponse(config))) + .When(x => WhenISetTheConfiguration()) + .Then(x => ThenTheConfigurationRepositoryIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_unable_to_set_configuration() + { + var fileConfig = new FileConfiguration(); + + this.Given(x => GivenTheFollowingConfiguration(fileConfig)) + .And(x => GivenTheCreatorReturns(new ErrorResponse(It.IsAny()))) + .When(x => WhenISetTheConfiguration()) + .And(x => ThenAnErrorResponseIsReturned()) + .BDDfy(); + } + + private void ThenAnErrorResponseIsReturned() + { + _result.ShouldBeOfType(); + } + + private void GivenTheCreatorReturns(Response configuration) + { + _configuration = configuration; + _configCreator + .Setup(x => x.Create(_fileConfiguration)) + .ReturnsAsync(_configuration); + } + + private void GivenTheFollowingConfiguration(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + } + + private void WhenISetTheConfiguration() + { + _result = _configSetter.Set(_fileConfiguration).Result; + } + + private void ThenTheConfigurationRepositoryIsCalledCorrectly() + { + _configRepo + .Verify(x => x.AddOrReplace(_configuration.Data), Times.Once); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs index cd2ab4ce..2638b4a5 100644 --- a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs @@ -1,28 +1,36 @@ +using System; +using System.Net; using Microsoft.AspNetCore.Mvc; using Moq; using Ocelot.Configuration.File; +using Ocelot.Configuration.Setter; using Ocelot.Controllers; +using Ocelot.Errors; using Ocelot.Responses; using Ocelot.Services; using TestStack.BDDfy; using Xunit; +using Shouldly; namespace Ocelot.UnitTests.Controllers { public class FileConfigurationControllerTests { private FileConfigurationController _controller; - private Mock _getFileConfig; + private Mock _configGetter; + private Mock _configSetter; private IActionResult _result; + private FileConfiguration _fileConfiguration; public FileConfigurationControllerTests() { - _getFileConfig = new Mock(); - _controller = new FileConfigurationController(_getFileConfig.Object); + _configGetter = new Mock(); + _configSetter = new Mock(); + _controller = new FileConfigurationController(_configGetter.Object, _configSetter.Object); } [Fact] - public void should_return_file_configuration() + public void should_get_file_configuration() { var expected = new OkResponse(new FileConfiguration()); @@ -32,10 +40,75 @@ namespace Ocelot.UnitTests.Controllers .BDDfy(); } + [Fact] + public void should_return_error_when_cannot_get_config() + { + var expected = new ErrorResponse(It.IsAny()); + + this.Given(x => x.GivenTheGetConfigurationReturns(expected)) + .When(x => x.WhenIGetTheFileConfiguration()) + .Then(x => x.TheTheGetFileConfigurationIsCalledCorrectly()) + .And(x => x.ThenTheResponseIs()) + .BDDfy(); + } + + [Fact] + public void should_post_file_configuration() + { + var expected = new FileConfiguration(); + + this.Given(x => GivenTheFileConfiguration(expected)) + .And(x => GivenTheConfigSetterReturnsAnError(new OkResponse())) + .When(x => WhenIPostTheFileConfiguration()) + .Then(x => x.ThenTheConfigrationSetterIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_return_error_when_cannot_set_config() + { + var expected = new FileConfiguration(); + + this.Given(x => GivenTheFileConfiguration(expected)) + .And(x => GivenTheConfigSetterReturnsAnError(new ErrorResponse(new FakeError()))) + .When(x => WhenIPostTheFileConfiguration()) + .Then(x => x.ThenTheConfigrationSetterIsCalledCorrectly()) + .And(x => ThenTheResponseIs()) + .BDDfy(); + } + + private void GivenTheConfigSetterReturnsAnError(Response response) + { + _configSetter + .Setup(x => x.Set(It.IsAny())) + .ReturnsAsync(response); + } + + private void ThenTheConfigrationSetterIsCalledCorrectly() + { + _configSetter + .Verify(x => x.Set(_fileConfiguration), Times.Once); + } + + private void WhenIPostTheFileConfiguration() + { + _result = _controller.Post(_fileConfiguration).Result; + } + + private void GivenTheFileConfiguration(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + } + + private void ThenTheResponseIs() + { + _result.ShouldBeOfType(); + } + private void GivenTheGetConfigurationReturns(Response fileConfiguration) { - _getFileConfig - .Setup(x => x.Invoke()) + _configGetter + .Setup(x => x.Get()) .Returns(fileConfiguration); } @@ -46,8 +119,15 @@ namespace Ocelot.UnitTests.Controllers private void TheTheGetFileConfigurationIsCalledCorrectly() { - _getFileConfig - .Verify(x => x.Invoke(), Times.Once); + _configGetter + .Verify(x => x.Get(), Times.Once); + } + + class FakeError : Error + { + public FakeError() : base(string.Empty, OcelotErrorCode.CannotAddDataError) + { + } } } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index e7718f37..90a35ad8 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -90,7 +90,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _downstreamRoute = new OkResponse(downstreamRoute); _downstreamRouteFinder .Setup(x => x.FindDownstreamRoute(It.IsAny(), It.IsAny())) - .ReturnsAsync(_downstreamRoute); + .Returns(_downstreamRoute); } public void Dispose() diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 0d272c63..49e9b01b 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -199,7 +199,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _reRoutesConfig = reRoutesConfig; _mockConfig .Setup(x => x.Get()) - .ReturnsAsync(new OkResponse(new OcelotConfiguration(_reRoutesConfig, adminPath))); + .Returns(new OkResponse(new OcelotConfiguration(_reRoutesConfig, adminPath))); } private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) @@ -209,7 +209,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void WhenICallTheFinder() { - _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod).Result; + _result = _downstreamRouteFinder.FindDownstreamRoute(_upstreamUrlPath, _upstreamHttpMethod); } private void ThenTheFollowingIsReturned(DownstreamRoute expected) diff --git a/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs b/test/Ocelot.UnitTests/Services/FileConfigurationGetterTests.cs similarity index 95% rename from test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs rename to test/Ocelot.UnitTests/Services/FileConfigurationGetterTests.cs index fa86ced6..32266c44 100644 --- a/test/Ocelot.UnitTests/Services/GetFileConfigurationTests.cs +++ b/test/Ocelot.UnitTests/Services/FileConfigurationGetterTests.cs @@ -15,12 +15,12 @@ namespace Ocelot.UnitTests.Services { public class GetFileConfigurationTests { - private readonly IGetFileConfiguration _getReRoutes; + private readonly IFileConfigurationProvider _getReRoutes; private FileConfiguration _result; public GetFileConfigurationTests() { - _getReRoutes = new GetFileConfiguration(); + _getReRoutes = new FileConfigurationProvider(); } [Fact] @@ -74,7 +74,7 @@ namespace Ocelot.UnitTests.Services private void WhenIGetTheReRoutes() { - _result = _getReRoutes.Invoke().Data; + _result = _getReRoutes.Get().Data; } private void ThenTheFollowingIsReturned(FileConfiguration expected) diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index 3151ac57..e61760d6 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -29,6 +29,7 @@ "runtimes": { "win10-x64": {}, "osx.10.11-x64":{}, + "osx.10.12-x64": {}, "win7-x64": {} }, "frameworks": { From bf90b12f2c1bbb181db0173260665550d3448c1b Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Tue, 21 Feb 2017 07:39:09 +0000 Subject: [PATCH 103/113] acceptance tests passing after fixing registrations --- src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs | 2 ++ test/Ocelot.AcceptanceTests/RoutingTests.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 43ffefe3..19b13e31 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -17,6 +17,7 @@ using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; using Ocelot.Configuration.Provider; using Ocelot.Configuration.Repository; +using Ocelot.Configuration.Setter; using Ocelot.Configuration.Validator; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.UrlMatcher; @@ -111,6 +112,7 @@ namespace Ocelot.DependencyInjection .AddAuthorization() .AddJsonFormatters(); services.AddLogging(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 58e65bff..c1a6716c 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -30,7 +30,7 @@ namespace Ocelot.AcceptanceTests .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) .BDDfy(); } - + [Fact] public void should_return_response_200_with_simple_url() { From aa0d8fe59a6e41226f6ed4d9b827fa5f9b3dc6fe Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 22 Feb 2017 07:48:49 +0000 Subject: [PATCH 104/113] all tests passing, now to do authentication config provider --- configuration.json | 1 + .../File/FileGlobalConfiguration.cs | 4 +- .../File/FileServiceDiscoveryProvider.cs | 1 + .../Provider/FileConfigurationProvider.cs | 25 +++++ .../Provider}/IFileConfigurationProvider.cs | 2 +- .../Repository/FileConfigurationRepository.cs | 42 ++++++++ .../IFileConfigurationRepository.cs | 11 +++ .../Setter/FileConfigurationSetter.cs | 12 ++- .../FileConfigurationController.cs | 4 +- .../ServiceCollectionExtensions.cs | 4 +- .../Services/FileConfigurationProvider.cs | 19 ---- .../AdministrationTests.cs | 95 ++++++++++++++++++- .../configuration.json | 0 .../FileConfigurationProviderTests.cs | 75 ++++++--------- .../FileConfigurationRepositoryTests.cs} | 78 +++++++++++++-- .../FileConfigurationSetterTests.cs | 29 +++++- .../OcelotConfigurationProviderTests.cs | 77 +++++++++++++++ .../FileConfigurationControllerTests.cs | 2 +- 18 files changed, 399 insertions(+), 82 deletions(-) create mode 100755 configuration.json create mode 100644 src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs rename src/Ocelot/{Services => Configuration/Provider}/IFileConfigurationProvider.cs (80%) create mode 100644 src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs create mode 100644 src/Ocelot/Configuration/Repository/IFileConfigurationRepository.cs delete mode 100644 src/Ocelot/Services/FileConfigurationProvider.cs mode change 100644 => 100755 test/Ocelot.IntegrationTests/configuration.json rename test/Ocelot.UnitTests/{Services/FileConfigurationGetterTests.cs => Configuration/FileConfigurationRepositoryTests.cs} (51%) create mode 100644 test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs diff --git a/configuration.json b/configuration.json new file mode 100755 index 00000000..a2784d8c --- /dev/null +++ b/configuration.json @@ -0,0 +1 @@ +{"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":"https","DownstreamHost":"localhost","DownstreamPort":80,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null},{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/test","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":"https","DownstreamHost":"localhost","DownstreamPort":80,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":"RequestId","ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration"}} \ No newline at end of file diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs index fd0f41a9..95e56f95 100644 --- a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs @@ -1,4 +1,5 @@ -namespace Ocelot.Configuration.File + +namespace Ocelot.Configuration.File { public class FileGlobalConfiguration { @@ -6,6 +7,7 @@ { ServiceDiscoveryProvider = new FileServiceDiscoveryProvider(); } + public string RequestIdKey { get; set; } public FileServiceDiscoveryProvider ServiceDiscoveryProvider {get;set;} public string AdministrationPath {get;set;} diff --git a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs index 2f26b6ea..70a3c42f 100644 --- a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs +++ b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs @@ -2,6 +2,7 @@ namespace Ocelot.Configuration.File { public class FileServiceDiscoveryProvider { + public string Provider {get;set;} public string Host {get;set;} public int Port { get; set; } diff --git a/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs new file mode 100644 index 00000000..5f6dbe08 --- /dev/null +++ b/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Repository; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Provider +{ + public class FileConfigurationProvider : IFileConfigurationProvider + { + private IFileConfigurationRepository _repo; + + public FileConfigurationProvider(IFileConfigurationRepository repo) + { + _repo = repo; + } + + public Response Get() + { + var fileConfig = _repo.Get(); + return new OkResponse(fileConfig.Data); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Services/IFileConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs similarity index 80% rename from src/Ocelot/Services/IFileConfigurationProvider.cs rename to src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs index 725f7463..3a4dca14 100644 --- a/src/Ocelot/Services/IFileConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs @@ -1,7 +1,7 @@ using Ocelot.Configuration.File; using Ocelot.Responses; -namespace Ocelot.Services +namespace Ocelot.Configuration.Provider { public interface IFileConfigurationProvider { diff --git a/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs new file mode 100644 index 00000000..f32dcaec --- /dev/null +++ b/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs @@ -0,0 +1,42 @@ +using System; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Repository +{ + public class FileConfigurationRepository : IFileConfigurationRepository + { + private static readonly object _lock = new object(); + public Response Get() + { + var configFilePath = $"{AppContext.BaseDirectory}/configuration.json"; + string json = string.Empty; + lock(_lock) + { + json = System.IO.File.ReadAllText(configFilePath); + } + var fileConfiguration = JsonConvert.DeserializeObject(json); + return new OkResponse(fileConfiguration); + } + + public Response Set(FileConfiguration fileConfiguration) + { + var configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + lock(_lock) + { + if (System.IO.File.Exists(configurationPath)) + { + System.IO.File.Delete(configurationPath); + } + + System.IO.File.WriteAllText(configurationPath, jsonConfiguration); + } + + return new OkResponse(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/IFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/IFileConfigurationRepository.cs new file mode 100644 index 00000000..26726b46 --- /dev/null +++ b/src/Ocelot/Configuration/Repository/IFileConfigurationRepository.cs @@ -0,0 +1,11 @@ +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Repository +{ + public interface IFileConfigurationRepository + { + Response Get(); + Response Set(FileConfiguration fileConfiguration); + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs index 28957cc7..93dd4d85 100644 --- a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs +++ b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs @@ -10,15 +10,25 @@ namespace Ocelot.Configuration.Setter { private readonly IOcelotConfigurationRepository _configRepo; private readonly IOcelotConfigurationCreator _configCreator; + private readonly IFileConfigurationRepository _repo; - public FileConfigurationSetter(IOcelotConfigurationRepository configRepo, IOcelotConfigurationCreator configCreator) + public FileConfigurationSetter(IOcelotConfigurationRepository configRepo, + IOcelotConfigurationCreator configCreator, IFileConfigurationRepository repo) { _configRepo = configRepo; _configCreator = configCreator; + _repo = repo; } public async Task Set(FileConfiguration fileConfig) { + var response = _repo.Set(fileConfig); + + if(response.IsError) + { + return new ErrorResponse(response.Errors); + } + var config = await _configCreator.Create(fileConfig); if(!config.IsError) diff --git a/src/Ocelot/Controllers/FileConfigurationController.cs b/src/Ocelot/Controllers/FileConfigurationController.cs index a378f530..5e36c64b 100644 --- a/src/Ocelot/Controllers/FileConfigurationController.cs +++ b/src/Ocelot/Controllers/FileConfigurationController.cs @@ -2,8 +2,8 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Ocelot.Configuration.File; +using Ocelot.Configuration.Provider; using Ocelot.Configuration.Setter; -using Ocelot.Services; namespace Ocelot.Controllers { @@ -34,7 +34,7 @@ namespace Ocelot.Controllers } [HttpPost] - public async Task Post(FileConfiguration fileConfiguration) + public async Task Post([FromBody]FileConfiguration fileConfiguration) { var response = await _configSetter.Set(fileConfiguration); diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 19b13e31..fdc78bd1 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -34,7 +34,6 @@ using Ocelot.Requester; using Ocelot.Requester.QoS; using Ocelot.Responder; using Ocelot.ServiceDiscovery; -using Ocelot.Services; namespace Ocelot.DependencyInjection { @@ -112,8 +111,9 @@ namespace Ocelot.DependencyInjection .AddAuthorization() .AddJsonFormatters(); services.AddLogging(); + services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/Services/FileConfigurationProvider.cs b/src/Ocelot/Services/FileConfigurationProvider.cs deleted file mode 100644 index abac93f9..00000000 --- a/src/Ocelot/Services/FileConfigurationProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.IO; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Services -{ - public class FileConfigurationProvider : IFileConfigurationProvider - { - public Response Get() - { - var configFilePath = $"{AppContext.BaseDirectory}/configuration.json"; - var json = File.ReadAllText(configFilePath); - var fileConfiguration = JsonConvert.DeserializeObject(json); - return new OkResponse(fileConfiguration); - } - } -} \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 6ea1aa32..6661a2cc 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -74,7 +74,14 @@ namespace Ocelot.IntegrationTests { GlobalConfiguration = new FileGlobalConfiguration { - AdministrationPath = "/administration" + AdministrationPath = "/administration", + RequestIdKey = "RequestId", + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "127.0.0.1", + Provider = "test" + } + }, ReRoutes = new List() { @@ -109,6 +116,88 @@ namespace Ocelot.IntegrationTests .BDDfy(); } + [Fact] + public void should_get_file_configuration_edit_and_post_updated_version() + { + var initialConfiguration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + }, + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = "get", + UpstreamPathTemplate = "/" + }, + new FileReRoute() + { + DownstreamHost = "localhost", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/", + UpstreamHttpMethod = "get", + UpstreamPathTemplate = "/test" + } + } + }; + + var updatedConfiguration = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "/administration" + }, + ReRoutes = new List() + { + new FileReRoute() + { + DownstreamHost = "127.0.0.1", + DownstreamPort = 80, + DownstreamScheme = "http", + DownstreamPathTemplate = "/geoffrey", + UpstreamHttpMethod = "get", + UpstreamPathTemplate = "/" + }, + new FileReRoute() + { + DownstreamHost = "123.123.123", + DownstreamPort = 443, + DownstreamScheme = "https", + DownstreamPathTemplate = "/blooper/{productId}", + UpstreamHttpMethod = "post", + UpstreamPathTemplate = "/test" + } + } + }; + + this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) + .And(x => GivenOcelotIsRunning()) + .And(x => GivenIHaveAnOcelotToken("/administration")) + .And(x => GivenIHaveAddedATokenToMyRequest()) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration)) + .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => ThenTheResponseShouldBe(updatedConfiguration)) + .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) + .And(x => ThenTheResponseShouldBe(updatedConfiguration)) + .BDDfy(); + } + + 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); @@ -180,6 +269,8 @@ namespace Ocelot.IntegrationTests File.WriteAllText(configurationPath, jsonConfiguration); + var text = File.ReadAllText(configurationPath); + configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; if (File.Exists(configurationPath)) @@ -188,6 +279,8 @@ namespace Ocelot.IntegrationTests } File.WriteAllText(configurationPath, jsonConfiguration); + + text = File.ReadAllText(configurationPath); } private void WhenIGetUrlOnTheApiGateway(string url) diff --git a/test/Ocelot.IntegrationTests/configuration.json b/test/Ocelot.IntegrationTests/configuration.json old mode 100644 new mode 100755 diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs index c7d240f8..a1ea4af7 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs @@ -1,77 +1,62 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Moq; using Ocelot.Configuration; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.Provider; -using Ocelot.Configuration.Repository; -using Ocelot.Errors; +using Ocelot.Configuration.File; using Ocelot.Responses; using Shouldly; using TestStack.BDDfy; using Xunit; +using Newtonsoft.Json; +using System.IO; +using Ocelot.Configuration.Provider; +using Ocelot.Configuration.Repository; namespace Ocelot.UnitTests.Configuration { public class FileConfigurationProviderTests { - private readonly IOcelotConfigurationProvider _ocelotConfigurationProvider; - private readonly Mock _configurationRepository; - private Response _result; + private readonly IFileConfigurationProvider _provider; + private Mock _repo; + private FileConfiguration _result; + private FileConfiguration _fileConfiguration; public FileConfigurationProviderTests() { - _configurationRepository = new Mock(); - _ocelotConfigurationProvider = new OcelotConfigurationProvider(_configurationRepository.Object); + _repo = new Mock(); + _provider = new FileConfigurationProvider(_repo.Object); } [Fact] - public void should_get_config() + public void should_return_file_configuration() { - this.Given(x => x.GivenTheRepoReturns(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) - .When(x => x.WhenIGetTheConfig()) - .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) + var config = new FileConfiguration(); + + this.Given(x => x.GivenTheConfigurationIs(config)) + .When(x => x.WhenIGetTheReRoutes()) + .Then(x => x.ThenTheRepoIsCalledCorrectly()) .BDDfy(); } - [Fact] - public void should_return_error() - { - this.Given(x => x.GivenTheRepoReturns(new ErrorResponse(new List - { - new AnyError() - }))) - .When(x => x.WhenIGetTheConfig()) - .Then(x => x.TheFollowingIsReturned( - new ErrorResponse(new List - { - new AnyError() - }))) - .BDDfy(); - } + - private void GivenTheRepoReturns(Response config) + private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) { - _configurationRepository + _fileConfiguration = fileConfiguration; + _repo .Setup(x => x.Get()) - .Returns(config); + .Returns(new OkResponse(fileConfiguration)); } - private void WhenIGetTheConfig() + private void WhenIGetTheReRoutes() { - _result = _ocelotConfigurationProvider.Get(); + _result = _provider.Get().Data; } - private void TheFollowingIsReturned(Response expected) + private void ThenTheRepoIsCalledCorrectly() { - _result.IsError.ShouldBe(expected.IsError); - } - - class AnyError : Error - { - public AnyError() - : base("blamo", OcelotErrorCode.UnknownError) - { - } + _repo + .Verify(x => x.Get(), Times.Once); } } -} +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Services/FileConfigurationGetterTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs similarity index 51% rename from test/Ocelot.UnitTests/Services/FileConfigurationGetterTests.cs rename to test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs index 32266c44..dfa8f67a 100644 --- a/test/Ocelot.UnitTests/Services/FileConfigurationGetterTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs @@ -7,20 +7,21 @@ using Ocelot.Responses; using Shouldly; using TestStack.BDDfy; using Xunit; -using Ocelot.Services; using Newtonsoft.Json; using System.IO; +using Ocelot.Configuration.Repository; -namespace Ocelot.UnitTests.Services +namespace Ocelot.UnitTests.Configuration { - public class GetFileConfigurationTests + public class FileConfigurationRepositoryTests { - private readonly IFileConfigurationProvider _getReRoutes; + private readonly IFileConfigurationRepository _repo; private FileConfiguration _result; + private FileConfiguration _fileConfiguration; - public GetFileConfigurationTests() + public FileConfigurationRepositoryTests() { - _getReRoutes = new FileConfigurationProvider(); + _repo = new FileConfigurationRepository(); } [Fact] @@ -58,6 +59,69 @@ namespace Ocelot.UnitTests.Services .BDDfy(); } + [Fact] + public void should_set_file_configuration() + { + var reRoutes = new List + { + new FileReRoute + { + DownstreamHost = "123.12.12.12", + DownstreamPort = 80, + DownstreamScheme = "https", + DownstreamPathTemplate = "/asdfs/test/{test}" + } + }; + + var globalConfiguration = new FileGlobalConfiguration + { + AdministrationPath = "asdas", + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Provider = "consul", + Port = 198, + Host = "blah" + } + }; + + var config = new FileConfiguration(); + config.GlobalConfiguration = globalConfiguration; + config.ReRoutes.AddRange(reRoutes); + + this.Given(x => GivenIHaveAConfiguration(config)) + .When(x => WhenISetTheConfiguration()) + .Then(x => ThenTheConfigurationIsStoredAs(config)) + .BDDfy(); + } + + private void GivenIHaveAConfiguration(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + } + + private void WhenISetTheConfiguration() + { + _repo.Set(_fileConfiguration); + _result = _repo.Get().Data; + } + + private void ThenTheConfigurationIsStoredAs(FileConfiguration expected) + { + _result.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath); + _result.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Provider.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Provider); + + for(var i = 0; i < _result.ReRoutes.Count; i++) + { + _result.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); + _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); + _result.ReRoutes[i].DownstreamPort.ShouldBe(expected.ReRoutes[i].DownstreamPort); + _result.ReRoutes[i].DownstreamScheme.ShouldBe(expected.ReRoutes[i].DownstreamScheme); + } + } + private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) { var configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; @@ -74,7 +138,7 @@ namespace Ocelot.UnitTests.Services private void WhenIGetTheReRoutes() { - _result = _getReRoutes.Get().Data; + _result = _repo.Get().Data; } private void ThenTheFollowingIsReturned(FileConfiguration expected) diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs index aa9b935a..bf4804f6 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Moq; using Ocelot.Configuration; @@ -21,12 +22,14 @@ namespace Ocelot.UnitTests.Configuration private Mock _configCreator; private Response _configuration; private object _result; + private Mock _repo; public FileConfigurationSetterTests() { + _repo = new Mock(); _configRepo = new Mock(); _configCreator = new Mock(); - _configSetter = new FileConfigurationSetter(_configRepo.Object, _configCreator.Object); + _configSetter = new FileConfigurationSetter(_configRepo.Object, _configCreator.Object, _repo.Object); } [Fact] @@ -36,24 +39,46 @@ namespace Ocelot.UnitTests.Configuration var config = new OcelotConfiguration(new List(), string.Empty); this.Given(x => GivenTheFollowingConfiguration(fileConfig)) + .And(x => GivenTheRepoReturns(new OkResponse())) .And(x => GivenTheCreatorReturns(new OkResponse(config))) .When(x => WhenISetTheConfiguration()) .Then(x => ThenTheConfigurationRepositoryIsCalledCorrectly()) .BDDfy(); } + [Fact] - public void should_return_error_if_unable_to_set_configuration() + public void should_return_error_if_unable_to_set_file_configuration() { var fileConfig = new FileConfiguration(); this.Given(x => GivenTheFollowingConfiguration(fileConfig)) + .And(x => GivenTheRepoReturns(new ErrorResponse(It.IsAny()))) + .When(x => WhenISetTheConfiguration()) + .And(x => ThenAnErrorResponseIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_unable_to_set_ocelot_configuration() + { + var fileConfig = new FileConfiguration(); + + this.Given(x => GivenTheFollowingConfiguration(fileConfig)) + .And(x => GivenTheRepoReturns(new OkResponse())) .And(x => GivenTheCreatorReturns(new ErrorResponse(It.IsAny()))) .When(x => WhenISetTheConfiguration()) .And(x => ThenAnErrorResponseIsReturned()) .BDDfy(); } + private void GivenTheRepoReturns(Response response) + { + _repo + .Setup(x => x.Set(It.IsAny())) + .Returns(response); + } + private void ThenAnErrorResponseIsReturned() { _result.ShouldBeOfType(); diff --git a/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs new file mode 100644 index 00000000..9be55087 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.Provider; +using Ocelot.Configuration.Repository; +using Ocelot.Errors; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class OcelotConfigurationProviderTests + { + private readonly IOcelotConfigurationProvider _ocelotConfigurationProvider; + private readonly Mock _configurationRepository; + private Response _result; + + public OcelotConfigurationProviderTests() + { + _configurationRepository = new Mock(); + _ocelotConfigurationProvider = new OcelotConfigurationProvider(_configurationRepository.Object); + } + + [Fact] + public void should_get_config() + { + this.Given(x => x.GivenTheRepoReturns(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) + .When(x => x.WhenIGetTheConfig()) + .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List(), string.Empty)))) + .BDDfy(); + } + + [Fact] + public void should_return_error() + { + this.Given(x => x.GivenTheRepoReturns(new ErrorResponse(new List + { + new AnyError() + }))) + .When(x => x.WhenIGetTheConfig()) + .Then(x => x.TheFollowingIsReturned( + new ErrorResponse(new List + { + new AnyError() + }))) + .BDDfy(); + } + + private void GivenTheRepoReturns(Response config) + { + _configurationRepository + .Setup(x => x.Get()) + .Returns(config); + } + + private void WhenIGetTheConfig() + { + _result = _ocelotConfigurationProvider.Get(); + } + + private void TheFollowingIsReturned(Response expected) + { + _result.IsError.ShouldBe(expected.IsError); + } + + class AnyError : Error + { + public AnyError() + : base("blamo", OcelotErrorCode.UnknownError) + { + } + } + } +} diff --git a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs index 2638b4a5..7f4d7b87 100644 --- a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs @@ -7,10 +7,10 @@ using Ocelot.Configuration.Setter; using Ocelot.Controllers; using Ocelot.Errors; using Ocelot.Responses; -using Ocelot.Services; using TestStack.BDDfy; using Xunit; using Shouldly; +using Ocelot.Configuration.Provider; namespace Ocelot.UnitTests.Controllers { From f8804f5d9deba1a373ba86ba1128a10590ce1833 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 22 Feb 2017 20:04:51 +0000 Subject: [PATCH 105/113] started adding some kind of auth config --- ...odedIdentityServerConfigurationProvider.cs | 86 +++++++++++++++++++ .../ServiceCollectionExtensions.cs | 44 ++++------ .../Middleware/OcelotMiddlewareExtensions.cs | 11 ++- 3 files changed, 111 insertions(+), 30 deletions(-) create mode 100644 src/Ocelot/Configuration/Provider/HardCodedIdentityServerConfigurationProvider.cs diff --git a/src/Ocelot/Configuration/Provider/HardCodedIdentityServerConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/HardCodedIdentityServerConfigurationProvider.cs new file mode 100644 index 00000000..09780bc6 --- /dev/null +++ b/src/Ocelot/Configuration/Provider/HardCodedIdentityServerConfigurationProvider.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; +using IdentityServer4.Test; + +namespace Ocelot.Configuration.Provider +{ + public class HardCodedIdentityServerConfigurationProvider : IIdentityServerConfigurationProvider + { + public IdentityServerConfiguration Get() + { + var url = ""; + return new IdentityServerConfiguration( + url, + "admin", + false, + SupportedTokens.Both, + "secret", + new List {"admin", "openid", "offline_access"}, + "Ocelot Administration", + true, + GrantTypes.ResourceOwnerPassword, + AccessTokenType.Jwt, + false, + new List { + new TestUser + { + Username = "admin", + Password = "admin", + SubjectId = "admin", + } + } + ); + } + } + + public interface IIdentityServerConfigurationProvider + { + IdentityServerConfiguration Get(); + } + + public class IdentityServerConfiguration + { + public IdentityServerConfiguration( + string identityServerUrl, + string apiName, + bool requireHttps, + SupportedTokens supportedTokens, + string apiSecret, + List allowedScopes, + string description, + bool enabled, + IEnumerable grantType, + AccessTokenType accessTokenType, + bool requireClientSecret, + List users) + { + IdentityServerUrl = identityServerUrl; + ApiName = apiName; + RequireHttps = requireHttps; + SupportedTokens = supportedTokens; + ApiSecret = apiSecret; + AllowedScopes = allowedScopes; + Description = description; + Enabled = enabled; + AllowedGrantTypes = grantType; + AccessTokenType = accessTokenType; + RequireClientSecret = requireClientSecret; + Users = users; + } + + public string IdentityServerUrl { get; private set; } + public string ApiName { get; private set; } + public bool RequireHttps { get; private set; } + public List AllowedScopes { get; private set; } + public SupportedTokens SupportedTokens { get; private set; } + public string ApiSecret { get; private set; } + public string Description {get;private set;} + public bool Enabled {get;private set;} + public IEnumerable AllowedGrantTypes {get;private set;} + public AccessTokenType AccessTokenType {get;private set;} + public bool RequireClientSecret = false; + public List Users {get;private set;} + } +} \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index fdc78bd1..0529c938 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net.Http; using CacheManager.Core; using IdentityServer4.Models; @@ -60,27 +61,25 @@ namespace Ocelot.DependencyInjection public static IServiceCollection AddOcelot(this IServiceCollection services) { + var authProvider = new HardCodedIdentityServerConfigurationProvider(); + var identityServerConfig = authProvider.Get(); + services.AddIdentityServer() .AddTemporarySigningCredential() .AddInMemoryApiResources(new List { new ApiResource { - Name = "admin", - Description = "Ocelot Administration", - Enabled = true, - DisplayName = "admin", - Scopes = new List() - { - new Scope("admin"), - new Scope("openid"), - new Scope("offline_access") - }, + Name = identityServerConfig.ApiName, + Description = identityServerConfig.Description, + Enabled = identityServerConfig.Enabled, + DisplayName = identityServerConfig.ApiName, + Scopes = identityServerConfig.AllowedScopes.Select(x => new Scope(x)).ToList(), ApiSecrets = new List { new Secret { - Value = "secret".Sha256() + Value = identityServerConfig.ApiSecret.Sha256() } } } @@ -89,24 +88,17 @@ namespace Ocelot.DependencyInjection { new Client { - ClientId = "admin", + ClientId = identityServerConfig.ApiName, AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret("secret".Sha256())}, - AllowedScopes = new List {"admin", "openid", "offline_access"}, - AccessTokenType = AccessTokenType.Jwt, - Enabled = true, - RequireClientSecret = false + ClientSecrets = new List {new Secret(identityServerConfig.ApiSecret.Sha256())}, + AllowedScopes = identityServerConfig.AllowedScopes, + AccessTokenType = identityServerConfig.AccessTokenType, + Enabled = identityServerConfig.Enabled, + RequireClientSecret = identityServerConfig.RequireClientSecret } }) - .AddTestUsers(new List - { - new TestUser - { - Username = "admin", - Password = "admin", - SubjectId = "admin", - } - }); + .AddTestUsers(identityServerConfig.Users); + services.AddMvcCore() .AddAuthorization() .AddJsonFormatters(); diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 9aa7442f..57ed9bf3 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -157,6 +157,9 @@ namespace Ocelot.Middleware { var configuration = await CreateConfiguration(builder); + var authProvider = new HardCodedIdentityServerConfigurationProvider(); + var identityServerConfig = authProvider.Get(); + if(!string.IsNullOrEmpty(configuration.AdministrationPath)) { builder.Map(configuration.AdministrationPath, app => @@ -166,11 +169,11 @@ namespace Ocelot.Middleware app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions { Authority = identityServerUrl, - ApiName = "admin", - RequireHttpsMetadata = false, - AllowedScopes = new List(), + ApiName = identityServerConfig.ApiName, + RequireHttpsMetadata = identityServerConfig.RequireHttps, + AllowedScopes = identityServerConfig.AllowedScopes, SupportedTokens = SupportedTokens.Both, - ApiSecret = "secret" + ApiSecret = identityServerConfig.ApiSecret }); app.UseIdentityServer(); From bd07af692657e140b67f9638d7ae856e136dfb96 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 22 Feb 2017 22:13:35 +0000 Subject: [PATCH 106/113] more work towards getting identity server and admin area set up --- README.md | 18 +++++ configuration.json | 2 +- .../ServiceCollectionExtensions.cs | 68 ++++++++++--------- .../Middleware/OcelotMiddlewareExtensions.cs | 43 ++++++++---- test/Ocelot.AcceptanceTests/Steps.cs | 22 +++++- .../AdministrationTests.cs | 12 +++- test/Ocelot.ManualTest/Program.cs | 15 ++-- test/Ocelot.ManualTest/Startup.cs | 10 ++- 8 files changed, 132 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index dfbdfd1e..d0db1e39 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,24 @@ Currently this is the only way to get configuration into Ocelot. } } +Then in your Program.cs you will want to have the following.. + + 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! diff --git a/configuration.json b/configuration.json index a2784d8c..2faeadfa 100755 --- a/configuration.json +++ b/configuration.json @@ -1 +1 @@ -{"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":"https","DownstreamHost":"localhost","DownstreamPort":80,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null},{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/test","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":"https","DownstreamHost":"localhost","DownstreamPort":80,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":"RequestId","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":"https","DownstreamHost":"localhost","DownstreamPort":80,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null},{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/test","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":"https","DownstreamHost":"localhost","DownstreamPort":80,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":"RequestId","ServiceDiscoveryProvider":{"Provider":"test","Host":"127.0.0.1","Port":0},"AdministrationPath":"/administration"}} \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 0529c938..52c47a25 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using System.Net.Http; using CacheManager.Core; using IdentityServer4.Models; using IdentityServer4.Test; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -61,44 +62,49 @@ namespace Ocelot.DependencyInjection public static IServiceCollection AddOcelot(this IServiceCollection services) { - var authProvider = new HardCodedIdentityServerConfigurationProvider(); - var identityServerConfig = authProvider.Get(); + return AddOcelot(services, null); + } - services.AddIdentityServer() - .AddTemporarySigningCredential() - .AddInMemoryApiResources(new List - { - new ApiResource + public static IServiceCollection AddOcelot(this IServiceCollection services, IdentityServerConfiguration identityServerConfiguration) + { + if(identityServerConfiguration != null) + { + services.AddIdentityServer() + .AddTemporarySigningCredential() + .AddInMemoryApiResources(new List { - Name = identityServerConfig.ApiName, - Description = identityServerConfig.Description, - Enabled = identityServerConfig.Enabled, - DisplayName = identityServerConfig.ApiName, - Scopes = identityServerConfig.AllowedScopes.Select(x => new Scope(x)).ToList(), - ApiSecrets = new List + new ApiResource { - new Secret + Name = identityServerConfiguration.ApiName, + Description = identityServerConfiguration.Description, + Enabled = identityServerConfiguration.Enabled, + DisplayName = identityServerConfiguration.ApiName, + Scopes = identityServerConfiguration.AllowedScopes.Select(x => new Scope(x)).ToList(), + ApiSecrets = new List { - Value = identityServerConfig.ApiSecret.Sha256() + new Secret + { + Value = identityServerConfiguration.ApiSecret.Sha256() + } } } - } - }) - .AddInMemoryClients(new List - { - new Client + }) + .AddInMemoryClients(new List { - ClientId = identityServerConfig.ApiName, - AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret(identityServerConfig.ApiSecret.Sha256())}, - AllowedScopes = identityServerConfig.AllowedScopes, - AccessTokenType = identityServerConfig.AccessTokenType, - Enabled = identityServerConfig.Enabled, - RequireClientSecret = identityServerConfig.RequireClientSecret - } - }) - .AddTestUsers(identityServerConfig.Users); - + new Client + { + ClientId = identityServerConfiguration.ApiName, + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + ClientSecrets = new List {new Secret(identityServerConfiguration.ApiSecret.Sha256())}, + AllowedScopes = identityServerConfiguration.AllowedScopes, + AccessTokenType = identityServerConfiguration.AccessTokenType, + Enabled = identityServerConfiguration.Enabled, + RequireClientSecret = identityServerConfiguration.RequireClientSecret + } + }) + .AddTestUsers(identityServerConfiguration.Users); + } + services.AddMvcCore() .AddAuthorization() .AddJsonFormatters(); diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 57ed9bf3..99509650 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -19,6 +19,7 @@ namespace Ocelot.Middleware using System; using System.Threading.Tasks; using Authorisation.Middleware; + using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Ocelot.Configuration; @@ -36,7 +37,21 @@ namespace Ocelot.Middleware /// public static async Task UseOcelot(this IApplicationBuilder builder) { - await builder.UseOcelot(new OcelotMiddlewareConfiguration()); + await builder.UseOcelot(new OcelotMiddlewareConfiguration(), null); + + return builder; + } + + public static async Task UseOcelot(this IApplicationBuilder builder,IdentityServerConfiguration identityServerConfiguration) + { + await builder.UseOcelot(new OcelotMiddlewareConfiguration(), identityServerConfiguration); + + return builder; + } + + public static async Task UseOcelot(this IApplicationBuilder builder,OcelotMiddlewareConfiguration middlewareConfiguration) + { + await builder.UseOcelot(middlewareConfiguration, null); return builder; } @@ -47,9 +62,9 @@ namespace Ocelot.Middleware /// /// /// - public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) + public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration, IdentityServerConfiguration identityServerConfiguration) { - await CreateAdministrationArea(builder); + await CreateAdministrationArea(builder, identityServerConfiguration); // This is registered to catch any global exceptions that are not handled builder.UseExceptionHandlerMiddleware(); @@ -153,27 +168,28 @@ namespace Ocelot.Middleware return ocelotConfiguration.Data; } - private static async Task CreateAdministrationArea(IApplicationBuilder builder) + private static async Task CreateAdministrationArea(IApplicationBuilder builder, IdentityServerConfiguration identityServerConfiguration) { var configuration = await CreateConfiguration(builder); - var authProvider = new HardCodedIdentityServerConfigurationProvider(); - var identityServerConfig = authProvider.Get(); - - if(!string.IsNullOrEmpty(configuration.AdministrationPath)) + if(!string.IsNullOrEmpty(configuration.AdministrationPath) && identityServerConfiguration != null) { + var webHostBuilder = (IWebHostBuilder)builder.ApplicationServices.GetService(typeof(IWebHostBuilder)); + + var baseSchemeUrlAndPort = webHostBuilder.GetSetting(WebHostDefaults.ServerUrlsKey); + builder.Map(configuration.AdministrationPath, app => { - var identityServerUrl = $"http://localhost:5000/{configuration.AdministrationPath.Remove(0,1)}"; + var identityServerUrl = $"{baseSchemeUrlAndPort}/{configuration.AdministrationPath.Remove(0,1)}"; app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions { Authority = identityServerUrl, - ApiName = identityServerConfig.ApiName, - RequireHttpsMetadata = identityServerConfig.RequireHttps, - AllowedScopes = identityServerConfig.AllowedScopes, + ApiName = identityServerConfiguration.ApiName, + RequireHttpsMetadata = identityServerConfiguration.RequireHttps, + AllowedScopes = identityServerConfiguration.AllowedScopes, SupportedTokens = SupportedTokens.Both, - ApiSecret = identityServerConfig.ApiSecret + ApiSecret = identityServerConfiguration.ApiSecret }); app.UseIdentityServer(); @@ -182,7 +198,6 @@ namespace Ocelot.Middleware }); } } - private static void UseIfNotNull(this IApplicationBuilder builder, Func, Task> middleware) { if (middleware != null) diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 1f4a8da1..d923148a 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -11,6 +11,7 @@ using CacheManager.Core; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Ocelot.Configuration.File; @@ -32,6 +33,7 @@ namespace Ocelot.AcceptanceTests public HttpClient OcelotClient => _ocelotClient; public string RequestIdKey = "OcRequestId"; private readonly Random _random; + private IWebHostBuilder _webHostBuilder; public Steps() { @@ -69,7 +71,14 @@ namespace Ocelot.AcceptanceTests /// public void GivenOcelotIsRunning() { - _ocelotServer = new TestServer(new WebHostBuilder() + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder.ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + }); + + _ocelotServer = new TestServer(_webHostBuilder .UseStartup()); _ocelotClient = _ocelotServer.CreateClient(); @@ -109,7 +118,14 @@ namespace Ocelot.AcceptanceTests var configuration = builder.Build(); - _ocelotServer = new TestServer(new WebHostBuilder() + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder.ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + }); + + _ocelotServer = new TestServer(_webHostBuilder .UseConfiguration(configuration) .ConfigureServices(s => { @@ -121,7 +137,7 @@ namespace Ocelot.AcceptanceTests }) .WithDictionaryHandle(); }; - + s.AddOcelotOutputCaching(settings); s.AddOcelotFileConfiguration(configuration); s.AddOcelot(); diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 6661a2cc..06e7bd5d 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.ManualTest; @@ -19,6 +20,7 @@ namespace Ocelot.IntegrationTests private readonly HttpClient _httpClient; private HttpResponseMessage _response; private IWebHost _builder; + private IWebHostBuilder _webHostBuilder; private readonly string _ocelotBaseUrl; private BearerToken _token; @@ -246,12 +248,16 @@ namespace Ocelot.IntegrationTests private void GivenOcelotIsRunning() { - _builder = new WebHostBuilder() + _webHostBuilder = new WebHostBuilder() .UseUrls(_ocelotBaseUrl) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup() - .Build(); + .ConfigureServices(x => { + x.AddSingleton(_webHostBuilder); + }) + .UseStartup(); + + _builder = _webHostBuilder.Build(); _builder.Start(); } diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index a049d3ea..a545819a 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -1,5 +1,6 @@ using System.IO; using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; namespace Ocelot.ManualTest { @@ -7,11 +8,17 @@ namespace Ocelot.ManualTest { public static void Main(string[] args) { - var host = new WebHostBuilder() - .UseKestrel() + IWebHostBuilder builder = new WebHostBuilder(); + + builder.ConfigureServices(s => { + s.AddSingleton(builder); + }); + + builder.UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup() - .Build(); + .UseStartup(); + + var host = builder.Build(); host.Run(); } diff --git a/test/Ocelot.ManualTest/Startup.cs b/test/Ocelot.ManualTest/Startup.cs index b187f6b4..aa34c9a2 100644 --- a/test/Ocelot.ManualTest/Startup.cs +++ b/test/Ocelot.ManualTest/Startup.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Ocelot.Configuration.Provider; using Ocelot.DependencyInjection; using Ocelot.Middleware; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; @@ -14,6 +15,8 @@ namespace Ocelot.ManualTest { public class Startup { + private IdentityServerConfiguration _identityServerConfig; + public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() @@ -24,6 +27,9 @@ namespace Ocelot.ManualTest .AddEnvironmentVariables(); Configuration = builder.Build(); + + var identityServerConfigProvider = new HardCodedIdentityServerConfigurationProvider(); + _identityServerConfig = identityServerConfigProvider.Get(); } public IConfigurationRoot Configuration { get; } @@ -41,14 +47,14 @@ namespace Ocelot.ManualTest services.AddOcelotOutputCaching(settings); services.AddOcelotFileConfiguration(Configuration); - services.AddOcelot(); + services.AddOcelot(_identityServerConfig); } public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); - await app.UseOcelot(); + await app.UseOcelot(_identityServerConfig); } } } From 112a9c303e53f34a95eafcc488f4f0d545be816d Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 24 Feb 2017 19:52:48 +0000 Subject: [PATCH 107/113] hacky auth working --- README.md | 48 ++++++- configuration.json | 2 +- .../Authentication/HashMatcher.cs | 22 ++++ .../Authentication/IHashMatcher.cs | 7 ++ .../OcelotResourceOwnerPasswordValidator.cs | 53 ++++++++ .../Provider/IIdentityServerConfiguration.cs | 21 ++++ ...ider.cs => IdentityServerConfiguration.cs} | 47 +------ src/Ocelot/Configuration/Provider/User.cs | 17 +++ .../ServiceCollectionExtensions.cs | 26 ++-- .../Middleware/OcelotMiddlewareExtensions.cs | 24 +--- src/Ocelot/project.json | 3 +- test/Ocelot.AcceptanceTests/Steps.cs | 3 +- .../AdministrationTests.cs | 2 +- test/Ocelot.ManualTest/Startup.cs | 35 ++++-- test/Ocelot.ManualTest/project.json | 3 +- .../Configuration/HashCreationTests.cs | 33 +++++ .../Configuration/HashMatcherTests.cs | 76 ++++++++++++ ...elotResourceOwnerPasswordValidatorTests.cs | 117 ++++++++++++++++++ test/Ocelot.UnitTests/project.json | 3 +- 19 files changed, 448 insertions(+), 94 deletions(-) create mode 100644 src/Ocelot/Configuration/Authentication/HashMatcher.cs create mode 100644 src/Ocelot/Configuration/Authentication/IHashMatcher.cs create mode 100644 src/Ocelot/Configuration/Authentication/OcelotResourceOwnerPasswordValidator.cs create mode 100644 src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs rename src/Ocelot/Configuration/Provider/{HardCodedIdentityServerConfigurationProvider.cs => IdentityServerConfiguration.cs} (52%) create mode 100644 src/Ocelot/Configuration/Provider/User.cs create mode 100644 test/Ocelot.UnitTests/Configuration/HashCreationTests.cs create mode 100644 test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs create mode 100644 test/Ocelot.UnitTests/Configuration/OcelotResourceOwnerPasswordValidatorTests.cs diff --git a/README.md b/README.md index d0db1e39..d2316c9a 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ [![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) +[![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results) + Attempt at a .NET Api Gateway This project is aimed at people using .NET running @@ -73,7 +75,7 @@ More information on how to use these options is below.. An example startup using a json file for configuration can be seen below. Currently this is the only way to get configuration into Ocelot. - public class Startup + public class Startup { public Startup(IHostingEnvironment env) { @@ -101,15 +103,14 @@ Currently this is the only way to get configuration into Ocelot. }; services.AddOcelotOutputCaching(settings); - services.AddOcelotFileConfiguration(Configuration); - services.AddOcelot(); + services.AddOcelot(Configuration); } - public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); - app.UseOcelot(); + await app.UseOcelot(); } } @@ -386,6 +387,43 @@ In orde to use caching on a route in your ReRoute configuration add this setting In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds. +## 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 support is provided by the amazing IdentityServer +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. + + "GlobalConfiguration": { + "AdministrationPath": "/administration" + } + +This will get the admin area set up but not the authentication. 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 MS doc I found online TODO find and link... + +OK next thing is to get this config into Ocelot... + + +At the moment Ocelot supports really limited options in terms of users and authentication for the admin API. At +least your stuff needs to be hashed! + + + + ## Ocelot Middleware injection and overrides Warning use with caution. If you are seeing any exceptions or strange behavior in your middleware diff --git a/configuration.json b/configuration.json index 2faeadfa..3f39532c 100755 --- a/configuration.json +++ b/configuration.json @@ -1 +1 @@ -{"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":"https","DownstreamHost":"localhost","DownstreamPort":80,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null},{"DownstreamPathTemplate":"/","UpstreamPathTemplate":"/test","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":"https","DownstreamHost":"localhost","DownstreamPort":80,"QoSOptions":{"ExceptionsAllowedBeforeBreaking":0,"DurationOfBreak":0,"TimeoutValue":0},"LoadBalancer":null}],"GlobalConfiguration":{"RequestIdKey":"RequestId","ServiceDiscoveryProvider":{"Provider":"test","Host":"127.0.0.1","Port":0},"AdministrationPath":"/administration"}} \ No newline at end of file +{"ReRoutes":[],"GlobalConfiguration":{"RequestIdKey":null,"ServiceDiscoveryProvider":{"Provider":null,"Host":null,"Port":0},"AdministrationPath":"/administration"}} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Authentication/HashMatcher.cs b/src/Ocelot/Configuration/Authentication/HashMatcher.cs new file mode 100644 index 00000000..08773332 --- /dev/null +++ b/src/Ocelot/Configuration/Authentication/HashMatcher.cs @@ -0,0 +1,22 @@ +using System; +using Microsoft.AspNetCore.Cryptography.KeyDerivation; + +namespace Ocelot.Configuration.Authentication +{ + public class HashMatcher : IHashMatcher + { + public bool Match(string password, string salt, string hash) + { + byte[] s = Convert.FromBase64String(salt); + + string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2( + password: password, + salt: s, + prf: KeyDerivationPrf.HMACSHA256, + iterationCount: 10000, + numBytesRequested: 256 / 8)); + + return hashed == hash; + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Authentication/IHashMatcher.cs b/src/Ocelot/Configuration/Authentication/IHashMatcher.cs new file mode 100644 index 00000000..ad0d8e03 --- /dev/null +++ b/src/Ocelot/Configuration/Authentication/IHashMatcher.cs @@ -0,0 +1,7 @@ +namespace Ocelot.Configuration.Authentication +{ + public interface IHashMatcher + { + bool Match(string password, string salt, string hash); + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Authentication/OcelotResourceOwnerPasswordValidator.cs b/src/Ocelot/Configuration/Authentication/OcelotResourceOwnerPasswordValidator.cs new file mode 100644 index 00000000..416c8ec2 --- /dev/null +++ b/src/Ocelot/Configuration/Authentication/OcelotResourceOwnerPasswordValidator.cs @@ -0,0 +1,53 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Models; +using IdentityServer4.Validation; +using Ocelot.Configuration.Provider; + +namespace Ocelot.Configuration.Authentication +{ + public class OcelotResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator + { + private readonly IHashMatcher _matcher; + private readonly IIdentityServerConfiguration _identityServerConfiguration; + + public OcelotResourceOwnerPasswordValidator(IHashMatcher matcher, IIdentityServerConfiguration identityServerConfiguration) + { + _identityServerConfiguration = identityServerConfiguration; + _matcher = matcher; + } + + public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) + { + try + { + var user = _identityServerConfiguration.Users.FirstOrDefault(u => u.UserName == context.UserName); + + if(user == null) + { + context.Result = new GrantValidationResult( + TokenRequestErrors.InvalidGrant, + "invalid custom credential"); + } + else if(_matcher.Match(context.Password, user.Salt, user.Hash)) + { + context.Result = new GrantValidationResult( + subject: "admin", + authenticationMethod: "custom"); + } + else + { + context.Result = new GrantValidationResult( + TokenRequestErrors.InvalidGrant, + "invalid custom credential"); + } + } + catch(Exception ex) + { + Console.WriteLine(ex); + } + + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs b/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs new file mode 100644 index 00000000..bb66265f --- /dev/null +++ b/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; + +namespace Ocelot.Configuration.Provider +{ + public interface IIdentityServerConfiguration + { + string ApiName { get; } + bool RequireHttps { get; } + List AllowedScopes { get; } + SupportedTokens SupportedTokens { get; } + string ApiSecret { get; } + string Description {get;} + bool Enabled {get;} + IEnumerable AllowedGrantTypes {get;} + AccessTokenType AccessTokenType {get;} + bool RequireClientSecret {get;} + List Users {get;} + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/HardCodedIdentityServerConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs similarity index 52% rename from src/Ocelot/Configuration/Provider/HardCodedIdentityServerConfigurationProvider.cs rename to src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs index 09780bc6..f0f6897d 100644 --- a/src/Ocelot/Configuration/Provider/HardCodedIdentityServerConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs @@ -1,49 +1,12 @@ -using System; using System.Collections.Generic; using IdentityServer4.AccessTokenValidation; using IdentityServer4.Models; -using IdentityServer4.Test; namespace Ocelot.Configuration.Provider { - public class HardCodedIdentityServerConfigurationProvider : IIdentityServerConfigurationProvider - { - public IdentityServerConfiguration Get() - { - var url = ""; - return new IdentityServerConfiguration( - url, - "admin", - false, - SupportedTokens.Both, - "secret", - new List {"admin", "openid", "offline_access"}, - "Ocelot Administration", - true, - GrantTypes.ResourceOwnerPassword, - AccessTokenType.Jwt, - false, - new List { - new TestUser - { - Username = "admin", - Password = "admin", - SubjectId = "admin", - } - } - ); - } - } - - public interface IIdentityServerConfigurationProvider - { - IdentityServerConfiguration Get(); - } - - public class IdentityServerConfiguration + public class IdentityServerConfiguration : IIdentityServerConfiguration { public IdentityServerConfiguration( - string identityServerUrl, string apiName, bool requireHttps, SupportedTokens supportedTokens, @@ -54,9 +17,8 @@ namespace Ocelot.Configuration.Provider IEnumerable grantType, AccessTokenType accessTokenType, bool requireClientSecret, - List users) + List users) { - IdentityServerUrl = identityServerUrl; ApiName = apiName; RequireHttps = requireHttps; SupportedTokens = supportedTokens; @@ -70,7 +32,6 @@ namespace Ocelot.Configuration.Provider Users = users; } - public string IdentityServerUrl { get; private set; } public string ApiName { get; private set; } public bool RequireHttps { get; private set; } public List AllowedScopes { get; private set; } @@ -80,7 +41,7 @@ namespace Ocelot.Configuration.Provider public bool Enabled {get;private set;} public IEnumerable AllowedGrantTypes {get;private set;} public AccessTokenType AccessTokenType {get;private set;} - public bool RequireClientSecret = false; - public List Users {get;private set;} + public bool RequireClientSecret {get;private set;} + public List Users {get;private set;} } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/User.cs b/src/Ocelot/Configuration/Provider/User.cs new file mode 100644 index 00000000..f61ff4e5 --- /dev/null +++ b/src/Ocelot/Configuration/Provider/User.cs @@ -0,0 +1,17 @@ +namespace Ocelot.Configuration.Provider +{ + public class User + { + public User(string subject, string userName, string hash, string salt) + { + Subject = subject; + UserName = userName; + Hash = hash; + Salt = salt; + } + public string Subject { get; private set; } + public string UserName { get; private set; } + public string Hash { get; private set; } + public string Salt { get; private set; } + } +} \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 52c47a25..ccd63e64 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -14,6 +14,7 @@ using Ocelot.Authentication.Handler.Factory; using Ocelot.Authorisation; using Ocelot.Cache; using Ocelot.Claims; +using Ocelot.Configuration.Authentication; using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; using Ocelot.Configuration.Parser; @@ -41,7 +42,6 @@ namespace Ocelot.DependencyInjection { public static class ServiceCollectionExtensions { - public static IServiceCollection AddOcelotOutputCaching(this IServiceCollection services, Action settings) { var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", settings); @@ -51,24 +51,23 @@ namespace Ocelot.DependencyInjection return services; } - public static IServiceCollection AddOcelotFileConfiguration(this IServiceCollection services, IConfigurationRoot configurationRoot) + + public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot) + { + return AddOcelot(services, configurationRoot, null); + } + + public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot, IIdentityServerConfiguration identityServerConfiguration) { services.Configure(configurationRoot); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - return services; - } - - public static IServiceCollection AddOcelot(this IServiceCollection services) - { - return AddOcelot(services, null); - } - - public static IServiceCollection AddOcelot(this IServiceCollection services, IdentityServerConfiguration identityServerConfiguration) - { + if(identityServerConfiguration != null) { + services.AddSingleton(identityServerConfiguration); + services.AddSingleton(); services.AddIdentityServer() .AddTemporarySigningCredential() .AddInMemoryApiResources(new List @@ -101,8 +100,7 @@ namespace Ocelot.DependencyInjection Enabled = identityServerConfiguration.Enabled, RequireClientSecret = identityServerConfiguration.RequireClientSecret } - }) - .AddTestUsers(identityServerConfiguration.Users); + }).AddResourceOwnerValidator(); } services.AddMvcCore() diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 99509650..553b05b8 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -37,21 +37,7 @@ namespace Ocelot.Middleware /// public static async Task UseOcelot(this IApplicationBuilder builder) { - await builder.UseOcelot(new OcelotMiddlewareConfiguration(), null); - - return builder; - } - - public static async Task UseOcelot(this IApplicationBuilder builder,IdentityServerConfiguration identityServerConfiguration) - { - await builder.UseOcelot(new OcelotMiddlewareConfiguration(), identityServerConfiguration); - - return builder; - } - - public static async Task UseOcelot(this IApplicationBuilder builder,OcelotMiddlewareConfiguration middlewareConfiguration) - { - await builder.UseOcelot(middlewareConfiguration, null); + await builder.UseOcelot(new OcelotMiddlewareConfiguration()); return builder; } @@ -62,9 +48,9 @@ namespace Ocelot.Middleware /// /// /// - public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration, IdentityServerConfiguration identityServerConfiguration) + public static async Task UseOcelot(this IApplicationBuilder builder, OcelotMiddlewareConfiguration middlewareConfiguration) { - await CreateAdministrationArea(builder, identityServerConfiguration); + await CreateAdministrationArea(builder); // This is registered to catch any global exceptions that are not handled builder.UseExceptionHandlerMiddleware(); @@ -168,10 +154,12 @@ namespace Ocelot.Middleware return ocelotConfiguration.Data; } - private static async Task CreateAdministrationArea(IApplicationBuilder builder, IdentityServerConfiguration identityServerConfiguration) + private static async Task CreateAdministrationArea(IApplicationBuilder builder) { var configuration = await CreateConfiguration(builder); + var identityServerConfiguration = (IIdentityServerConfiguration)builder.ApplicationServices.GetService(typeof(IIdentityServerConfiguration)); + if(!string.IsNullOrEmpty(configuration.AdministrationPath) && identityServerConfiguration != null) { var webHostBuilder = (IWebHostBuilder)builder.ApplicationServices.GetService(typeof(IWebHostBuilder)); diff --git a/src/Ocelot/project.json b/src/Ocelot/project.json index 1cb4b0c9..1ca9d403 100644 --- a/src/Ocelot/project.json +++ b/src/Ocelot/project.json @@ -29,7 +29,8 @@ "CacheManager.Microsoft.Extensions.Logging": "0.9.2", "Consul": "0.7.2.1", "Polly": "5.0.3", - "IdentityServer4": "1.0.1" + "IdentityServer4": "1.0.1", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0" }, "runtimes": { "win10-x64": {}, diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index d923148a..5a256c81 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -139,8 +139,7 @@ namespace Ocelot.AcceptanceTests }; s.AddOcelotOutputCaching(settings); - s.AddOcelotFileConfiguration(configuration); - s.AddOcelot(); + s.AddOcelot(configuration); }) .ConfigureLogging(l => { diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 06e7bd5d..210c7ac5 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -235,7 +235,7 @@ namespace Ocelot.IntegrationTests new KeyValuePair("client_secret", "secret"), new KeyValuePair("scope", "admin"), new KeyValuePair("username", "admin"), - new KeyValuePair("password", "admin"), + new KeyValuePair("password", "secret"), new KeyValuePair("grant_type", "password") }; var content = new FormUrlEncodedContent(formData); diff --git a/test/Ocelot.ManualTest/Startup.cs b/test/Ocelot.ManualTest/Startup.cs index aa34c9a2..c1277af7 100644 --- a/test/Ocelot.ManualTest/Startup.cs +++ b/test/Ocelot.ManualTest/Startup.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using CacheManager.Core; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -15,7 +18,7 @@ namespace Ocelot.ManualTest { public class Startup { - private IdentityServerConfiguration _identityServerConfig; + private IIdentityServerConfiguration _identityServerConfig; public Startup(IHostingEnvironment env) { @@ -27,9 +30,6 @@ namespace Ocelot.ManualTest .AddEnvironmentVariables(); Configuration = builder.Build(); - - var identityServerConfigProvider = new HardCodedIdentityServerConfigurationProvider(); - _identityServerConfig = identityServerConfigProvider.Get(); } public IConfigurationRoot Configuration { get; } @@ -46,15 +46,36 @@ namespace Ocelot.ManualTest }; services.AddOcelotOutputCaching(settings); - services.AddOcelotFileConfiguration(Configuration); - services.AddOcelot(_identityServerConfig); + + var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME"); + var hash = Environment.GetEnvironmentVariable("OCELOT_HASH"); + var salt = Environment.GetEnvironmentVariable("OCELOT_SALT"); + + _identityServerConfig = new IdentityServerConfiguration( + "admin", + false, + SupportedTokens.Both, + "secret", + new List {"admin", "openid", "offline_access"}, + "Ocelot Administration", + true, + GrantTypes.ResourceOwnerPassword, + AccessTokenType.Jwt, + false, + new List + { + new User("admin", username, hash, salt) + } + ); + + services.AddOcelot(Configuration, _identityServerConfig); } public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); - await app.UseOcelot(_identityServerConfig); + await app.UseOcelot(); } } } diff --git a/test/Ocelot.ManualTest/project.json b/test/Ocelot.ManualTest/project.json index a31fdca2..52a8962f 100644 --- a/test/Ocelot.ManualTest/project.json +++ b/test/Ocelot.ManualTest/project.json @@ -14,7 +14,8 @@ "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", "Microsoft.NETCore.App": "1.1.0", "Consul": "0.7.2.1", - "Polly": "5.0.3" + "Polly": "5.0.3", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0" }, "tools": { diff --git a/test/Ocelot.UnitTests/Configuration/HashCreationTests.cs b/test/Ocelot.UnitTests/Configuration/HashCreationTests.cs new file mode 100644 index 00000000..d99efd58 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/HashCreationTests.cs @@ -0,0 +1,33 @@ +using System; +using System.Security.Cryptography; +using Microsoft.AspNetCore.Cryptography.KeyDerivation; +using Shouldly; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class HashCreationTests + { + [Fact] + public void should_create_hash_and_salt() + { + var password = "secret"; + + var salt = new byte[128 / 8]; + + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(salt); + } + + var storedSalt = Convert.ToBase64String(salt); + + var storedHash = Convert.ToBase64String(KeyDerivation.Pbkdf2( + password: password, + salt: salt, + prf: KeyDerivationPrf.HMACSHA256, + iterationCount: 10000, + numBytesRequested: 256 / 8)); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs b/test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs new file mode 100644 index 00000000..34e9477e --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs @@ -0,0 +1,76 @@ +using Ocelot.Configuration.Authentication; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class HashMatcherTests + { + private string _password; + private string _hash; + private string _salt; + private bool _result; + private HashMatcher _hashMatcher; + + public HashMatcherTests() + { + _hashMatcher = new HashMatcher(); + } + + [Fact] + public void should_match_hash() + { + var hash = "kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4="; + var salt = "zzWITpnDximUNKYLiUam/w=="; + var password = "secret"; + + this.Given(x => GivenThePassword(password)) + .And(x => GivenTheHash(hash)) + .And(x => GivenTheSalt(salt)) + .When(x => WhenIMatch()) + .Then(x => ThenTheResultIs(true)) + .BDDfy(); + } + + [Fact] + public void should_not_match_hash() + { + var hash = "kE/mxd1hO9h9Sl2VhGhwJUd9xZEv4NP6qXoN39nIqM4="; + var salt = "zzWITpnDximUNKYLiUam/w=="; + var password = "secret1"; + + this.Given(x => GivenThePassword(password)) + .And(x => GivenTheHash(hash)) + .And(x => GivenTheSalt(salt)) + .When(x => WhenIMatch()) + .Then(x => ThenTheResultIs(false)) + .BDDfy(); + } + + private void GivenThePassword(string password) + { + _password = password; + } + + private void GivenTheHash(string hash) + { + _hash = hash; + } + + private void GivenTheSalt(string salt) + { + _salt = salt; + } + + private void WhenIMatch() + { + _result = _hashMatcher.Match(_password, _salt, _hash); + } + + private void ThenTheResultIs(bool expected) + { + _result.ShouldBe(expected); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/OcelotResourceOwnerPasswordValidatorTests.cs b/test/Ocelot.UnitTests/Configuration/OcelotResourceOwnerPasswordValidatorTests.cs new file mode 100644 index 00000000..a8d11713 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/OcelotResourceOwnerPasswordValidatorTests.cs @@ -0,0 +1,117 @@ +using Ocelot.Configuration.Authentication; +using Xunit; +using Shouldly; +using TestStack.BDDfy; +using Moq; +using IdentityServer4.Validation; +using Ocelot.Configuration.Provider; +using System.Collections.Generic; + +namespace Ocelot.UnitTests.Configuration +{ + public class OcelotResourceOwnerPasswordValidatorTests + { + private OcelotResourceOwnerPasswordValidator _validator; + private Mock _matcher; + private string _userName; + private string _password; + private ResourceOwnerPasswordValidationContext _context; + private Mock _config; + private User _user; + + public OcelotResourceOwnerPasswordValidatorTests() + { + _matcher = new Mock(); + _config = new Mock(); + _validator = new OcelotResourceOwnerPasswordValidator(_matcher.Object, _config.Object); + } + + [Fact] + public void should_return_success() + { + this.Given(x => GivenTheUserName("tom")) + .And(x => GivenThePassword("password")) + .And(x => GivenTheUserIs(new User("sub", "tom", "xxx", "xxx"))) + .And(x => GivenTheMatcherReturns(true)) + .When(x => WhenIValidate()) + .Then(x => ThenTheUserIsValidated()) + .And(x => ThenTheMatcherIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_return_fail_when_no_user() + { + this.Given(x => GivenTheUserName("bob")) + .And(x => GivenTheUserIs(new User("sub", "tom", "xxx", "xxx"))) + .And(x => GivenTheMatcherReturns(true)) + .When(x => WhenIValidate()) + .Then(x => ThenTheUserIsNotValidated()) + .BDDfy(); + } + + [Fact] + public void should_return_fail_when_password_doesnt_match() + { + this.Given(x => GivenTheUserName("tom")) + .And(x => GivenThePassword("password")) + .And(x => GivenTheUserIs(new User("sub", "tom", "xxx", "xxx"))) + .And(x => GivenTheMatcherReturns(false)) + .When(x => WhenIValidate()) + .Then(x => ThenTheUserIsNotValidated()) + .And(x => ThenTheMatcherIsCalledCorrectly()) + .BDDfy(); + } + + private void ThenTheMatcherIsCalledCorrectly() + { + _matcher + .Verify(x => x.Match(_password, _user.Salt, _user.Hash), Times.Once); + } + + private void GivenThePassword(string password) + { + _password = password; + } + + private void GivenTheUserIs(User user) + { + _user = user; + _config + .Setup(x => x.Users) + .Returns(new List{_user}); + } + + private void GivenTheMatcherReturns(bool expected) + { + _matcher + .Setup(x => x.Match(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(expected); + } + + private void GivenTheUserName(string userName) + { + _userName = userName; + } + + private void WhenIValidate() + { + _context = new ResourceOwnerPasswordValidationContext + { + UserName = _userName, + Password = _password + }; + _validator.ValidateAsync(_context).Wait(); + } + + private void ThenTheUserIsValidated() + { + _context.Result.IsError.ShouldBe(false); + } + + private void ThenTheUserIsNotValidated() + { + _context.Result.IsError.ShouldBe(true); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/project.json b/test/Ocelot.UnitTests/project.json index e61760d6..bbc706b6 100644 --- a/test/Ocelot.UnitTests/project.json +++ b/test/Ocelot.UnitTests/project.json @@ -24,7 +24,8 @@ "Shouldly": "2.8.2", "TestStack.BDDfy": "4.3.2", "Microsoft.AspNetCore.Authentication.OAuth": "1.1.0", - "Microsoft.DotNet.InternalAbstractions": "1.0.0" + "Microsoft.DotNet.InternalAbstractions": "1.0.0", + "Microsoft.AspNetCore.Cryptography.KeyDerivation": "1.1.0" }, "runtimes": { "win10-x64": {}, From 07c334cc985455e969e88ada89478d2b58e8f955 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sat, 25 Feb 2017 16:09:00 +0000 Subject: [PATCH 108/113] moved stuff around so a bit less crap --- .../ServiceCollectionExtensions.cs | 32 ++++++++++++++++--- test/Ocelot.ManualTest/Startup.cs | 31 +----------------- 2 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index ccd63e64..825edea4 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using CacheManager.Core; +using IdentityServer4.AccessTokenValidation; using IdentityServer4.Models; using IdentityServer4.Test; using Microsoft.AspNetCore.Hosting; @@ -53,16 +54,13 @@ namespace Ocelot.DependencyInjection } public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot) - { - return AddOcelot(services, configurationRoot, null); - } - - public static IServiceCollection AddOcelot(this IServiceCollection services, IConfigurationRoot configurationRoot, IIdentityServerConfiguration identityServerConfiguration) { services.Configure(configurationRoot); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + + var identityServerConfiguration = GetIdentityServerConfiguration(); if(identityServerConfiguration != null) { @@ -143,5 +141,29 @@ namespace Ocelot.DependencyInjection return services; } + + private static IdentityServerConfiguration GetIdentityServerConfiguration() + { + var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME"); + var hash = Environment.GetEnvironmentVariable("OCELOT_HASH"); + var salt = Environment.GetEnvironmentVariable("OCELOT_SALT"); + + return new IdentityServerConfiguration( + "admin", + false, + SupportedTokens.Both, + "secret", + new List {"admin", "openid", "offline_access"}, + "Ocelot Administration", + true, + GrantTypes.ResourceOwnerPassword, + AccessTokenType.Jwt, + false, + new List + { + new User("admin", username, hash, salt) + } + ); + } } } diff --git a/test/Ocelot.ManualTest/Startup.cs b/test/Ocelot.ManualTest/Startup.cs index c1277af7..3c6d2949 100644 --- a/test/Ocelot.ManualTest/Startup.cs +++ b/test/Ocelot.ManualTest/Startup.cs @@ -1,15 +1,10 @@ using System; -using System.Collections.Generic; -using System.Threading.Tasks; using CacheManager.Core; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Ocelot.Configuration.Provider; using Ocelot.DependencyInjection; using Ocelot.Middleware; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; @@ -18,8 +13,6 @@ namespace Ocelot.ManualTest { public class Startup { - private IIdentityServerConfiguration _identityServerConfig; - public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() @@ -46,29 +39,7 @@ namespace Ocelot.ManualTest }; services.AddOcelotOutputCaching(settings); - - var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME"); - var hash = Environment.GetEnvironmentVariable("OCELOT_HASH"); - var salt = Environment.GetEnvironmentVariable("OCELOT_SALT"); - - _identityServerConfig = new IdentityServerConfiguration( - "admin", - false, - SupportedTokens.Both, - "secret", - new List {"admin", "openid", "offline_access"}, - "Ocelot Administration", - true, - GrantTypes.ResourceOwnerPassword, - AccessTokenType.Jwt, - false, - new List - { - new User("admin", username, hash, salt) - } - ); - - services.AddOcelot(Configuration, _identityServerConfig); + services.AddOcelot(Configuration); } public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) From be24f9a9ca699a8ecbacd161186bb35c8fdd4eb7 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 25 Feb 2017 17:02:36 +0000 Subject: [PATCH 109/113] Added base url finder for when nothing set in Program.cs --- .../ServiceCollectionExtensions.cs | 7 ++- src/Ocelot/Middleware/BaseUrlFinder.cs | 21 +++++++ src/Ocelot/Middleware/IBaseUrlFinder.cs | 7 +++ .../Middleware/OcelotMiddlewareExtensions.cs | 7 ++- .../Middleware/BaseUrlFinderTests.cs | 61 +++++++++++++++++++ 5 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 src/Ocelot/Middleware/BaseUrlFinder.cs create mode 100644 src/Ocelot/Middleware/IBaseUrlFinder.cs create mode 100644 test/Ocelot.UnitTests/Middleware/BaseUrlFinderTests.cs diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 825edea4..5f83bec8 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -5,8 +5,6 @@ using System.Net.Http; using CacheManager.Core; using IdentityServer4.AccessTokenValidation; using IdentityServer4.Models; -using IdentityServer4.Test; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -32,12 +30,14 @@ using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Infrastructure.RequestData; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Logging; +using Ocelot.Middleware; using Ocelot.QueryStrings; using Ocelot.Request.Builder; using Ocelot.Requester; using Ocelot.Requester.QoS; using Ocelot.Responder; using Ocelot.ServiceDiscovery; +using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider; namespace Ocelot.DependencyInjection { @@ -59,6 +59,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); var identityServerConfiguration = GetIdentityServerConfiguration(); @@ -107,7 +108,7 @@ namespace Ocelot.DependencyInjection services.AddLogging(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Ocelot/Middleware/BaseUrlFinder.cs b/src/Ocelot/Middleware/BaseUrlFinder.cs new file mode 100644 index 00000000..da1fda4e --- /dev/null +++ b/src/Ocelot/Middleware/BaseUrlFinder.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Hosting; + +namespace Ocelot.Middleware +{ + public class BaseUrlFinder : IBaseUrlFinder + { + private readonly IWebHostBuilder _webHostBuilder; + + public BaseUrlFinder(IWebHostBuilder webHostBuilder) + { + _webHostBuilder = webHostBuilder; + } + + public string Find() + { + var baseSchemeUrlAndPort = _webHostBuilder.GetSetting(WebHostDefaults.ServerUrlsKey); + + return string.IsNullOrEmpty(baseSchemeUrlAndPort) ? "http://localhost:5000" : baseSchemeUrlAndPort; + } + } +} diff --git a/src/Ocelot/Middleware/IBaseUrlFinder.cs b/src/Ocelot/Middleware/IBaseUrlFinder.cs new file mode 100644 index 00000000..df54c0fe --- /dev/null +++ b/src/Ocelot/Middleware/IBaseUrlFinder.cs @@ -0,0 +1,7 @@ +namespace Ocelot.Middleware +{ + public interface IBaseUrlFinder + { + string Find(); + } +} \ No newline at end of file diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 553b05b8..bf384dd2 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -162,9 +162,9 @@ namespace Ocelot.Middleware if(!string.IsNullOrEmpty(configuration.AdministrationPath) && identityServerConfiguration != null) { - var webHostBuilder = (IWebHostBuilder)builder.ApplicationServices.GetService(typeof(IWebHostBuilder)); - - var baseSchemeUrlAndPort = webHostBuilder.GetSetting(WebHostDefaults.ServerUrlsKey); + var urlFinder = (IBaseUrlFinder)builder.ApplicationServices.GetService(typeof(IBaseUrlFinder)); + + var baseSchemeUrlAndPort = urlFinder.Find(); builder.Map(configuration.AdministrationPath, app => { @@ -186,6 +186,7 @@ namespace Ocelot.Middleware }); } } + private static void UseIfNotNull(this IApplicationBuilder builder, Func, Task> middleware) { if (middleware != null) diff --git a/test/Ocelot.UnitTests/Middleware/BaseUrlFinderTests.cs b/test/Ocelot.UnitTests/Middleware/BaseUrlFinderTests.cs new file mode 100644 index 00000000..c0777a92 --- /dev/null +++ b/test/Ocelot.UnitTests/Middleware/BaseUrlFinderTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Moq; +using Ocelot.Middleware; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Middleware +{ + public class BaseUrlFinderTests + { + private readonly BaseUrlFinder _baseUrlFinder; + private readonly Mock _webHostBuilder; + private string _result; + + public BaseUrlFinderTests() + { + _webHostBuilder = new Mock(); + _baseUrlFinder = new BaseUrlFinder(_webHostBuilder.Object); + } + + [Fact] + public void should_find_base_url_based_on_webhostbuilder() + { + this.Given(x => GivenTheWebHostBuilderReturns("http://localhost:7000")) + .When(x => WhenIFindTheUrl()) + .Then(x => ThenTheUrlIs("http://localhost:7000")) + .BDDfy(); + } + + [Fact] + public void should_use_default_base_url() + { + this.Given(x => GivenTheWebHostBuilderReturns("")) + .When(x => WhenIFindTheUrl()) + .Then(x => ThenTheUrlIs("http://localhost:5000")) + .BDDfy(); + } + + private void GivenTheWebHostBuilderReturns(string url) + { + _webHostBuilder + .Setup(x => x.GetSetting(WebHostDefaults.ServerUrlsKey)) + .Returns(url); + } + + private void WhenIFindTheUrl() + { + _result = _baseUrlFinder.Find(); + } + + private void ThenTheUrlIs(string expected) + { + _result.ShouldBe(expected); + } + } +} From a983af35a1b7c85398018a372bece15133c13b77 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 25 Feb 2017 17:47:24 +0000 Subject: [PATCH 110/113] change to catch not modified response and get config working correctly --- README.md | 15 ++++---- .../Creator/FileOcelotConfigurationCreator.cs | 6 ++-- .../IdentityServerConfigurationCreator.cs | 35 +++++++++++++++++++ .../ServiceCollectionExtensions.cs | 27 +------------- src/Ocelot/Responder/HttpContextResponder.cs | 6 +++- 5 files changed, 52 insertions(+), 37 deletions(-) create mode 100644 src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs diff --git a/README.md b/README.md index d2316c9a..7f9a410d 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,8 @@ Currently this is the only way to get configuration into Ocelot. } } -Then in your Program.cs you will want to have the following.. +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. IWebHostBuilder builder = new WebHostBuilder(); @@ -390,7 +391,7 @@ In this example ttl seconds is set to 15 which means the cache will expire after ## 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 support is provided by the amazing IdentityServer +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 @@ -403,7 +404,7 @@ to the Ocelot middleware. "AdministrationPath": "/administration" } -This will get the admin area set up but not the authentication. You need to set 3 environmental variables. +This will get the admin area set up but not the authentication. You need to set 3 environmental variables. OCELOT_USERNAME OCELOT_HASH @@ -413,13 +414,13 @@ These need to be the admin username you want to use with Ocelot and the hash and 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 MS doc I found online TODO find and link... +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. -OK next thing is to get this config into Ocelot... +Now if you went with the configuration options above and want to access the API you can make the following requests. -At the moment Ocelot supports really limited options in terms of users and authentication for the admin API. At -least your stuff needs to be hashed! diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 049c0fff..2182070d 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -86,13 +86,13 @@ namespace Ocelot.Configuration.Creator var reRoutes = new List(); - foreach (var reRoute in _options.Value.ReRoutes) + foreach (var reRoute in fileConfiguration.ReRoutes) { - var ocelotReRoute = await SetUpReRoute(reRoute, _options.Value.GlobalConfiguration); + var ocelotReRoute = await SetUpReRoute(reRoute, fileConfiguration.GlobalConfiguration); reRoutes.Add(ocelotReRoute); } - return new OcelotConfiguration(reRoutes, _options.Value.GlobalConfiguration.AdministrationPath); + return new OcelotConfiguration(reRoutes, fileConfiguration.GlobalConfiguration.AdministrationPath); } private async Task SetUpReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) diff --git a/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs new file mode 100644 index 00000000..48819608 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Models; +using Ocelot.Configuration.Provider; + +namespace Ocelot.Configuration.Creator +{ + public static class IdentityServerConfigurationCreator + { + public static IdentityServerConfiguration GetIdentityServerConfiguration() + { + var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME"); + var hash = Environment.GetEnvironmentVariable("OCELOT_HASH"); + var salt = Environment.GetEnvironmentVariable("OCELOT_SALT"); + + return new IdentityServerConfiguration( + "admin", + false, + SupportedTokens.Both, + "secret", + new List { "admin", "openid", "offline_access" }, + "Ocelot Administration", + true, + GrantTypes.ResourceOwnerPassword, + AccessTokenType.Jwt, + false, + new List + { + new User("admin", username, hash, salt) + } + ); + } + } +} diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index 5f83bec8..ad2a4a8d 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using CacheManager.Core; -using IdentityServer4.AccessTokenValidation; using IdentityServer4.Models; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; @@ -61,7 +60,7 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); - var identityServerConfiguration = GetIdentityServerConfiguration(); + var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(); if(identityServerConfiguration != null) { @@ -142,29 +141,5 @@ namespace Ocelot.DependencyInjection return services; } - - private static IdentityServerConfiguration GetIdentityServerConfiguration() - { - var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME"); - var hash = Environment.GetEnvironmentVariable("OCELOT_HASH"); - var salt = Environment.GetEnvironmentVariable("OCELOT_SALT"); - - return new IdentityServerConfiguration( - "admin", - false, - SupportedTokens.Both, - "secret", - new List {"admin", "openid", "offline_access"}, - "Ocelot Administration", - true, - GrantTypes.ResourceOwnerPassword, - AccessTokenType.Jwt, - false, - new List - { - new User("admin", username, hash, salt) - } - ); - } } } diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index 40b60c30..20313e8f 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -1,5 +1,6 @@ using System.IO; using System.Linq; +using System.Net; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -54,7 +55,10 @@ namespace Ocelot.Responder using (Stream stream = new MemoryStream(content)) { - await stream.CopyToAsync(context.Response.Body); + if (response.StatusCode != HttpStatusCode.NotModified) + { + await stream.CopyToAsync(context.Response.Body); + } } } From c2f98f0d69def5f938a812c59e8e2e490a357995 Mon Sep 17 00:00:00 2001 From: TomPallister Date: Sat, 25 Feb 2017 18:06:38 +0000 Subject: [PATCH 111/113] final changes to readme and added postman scripts --- Ocelot.sln | 1 + README.md | 76 ++++++++++----------- ocelot.postman_collection.json | 117 +++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 37 deletions(-) create mode 100644 ocelot.postman_collection.json diff --git a/Ocelot.sln b/Ocelot.sln index d88020d3..9a34aed2 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution GitVersion.yml = GitVersion.yml global.json = global.json LICENSE.md = LICENSE.md + ocelot.postman_collection.json = ocelot.postman_collection.json README.md = README.md release.ps1 = release.ps1 ReleaseNotes.md = ReleaseNotes.md diff --git a/README.md b/README.md index 7f9a410d..d72f6b01 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,45 @@ This means that when Ocelot tries to match the incoming upstream url with an ups 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. + + "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 @@ -388,43 +427,6 @@ In orde to use caching on a route in your ReRoute configuration add this setting In this example ttl seconds is set to 15 which means the cache will expire after 15 seconds. -## 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. - - "GlobalConfiguration": { - "AdministrationPath": "/administration" - } - -This will get the admin area set up but not the authentication. 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 make the following requests. - - - - - - ## Ocelot Middleware injection and overrides Warning use with caution. If you are seeing any exceptions or strange behavior in your middleware diff --git a/ocelot.postman_collection.json b/ocelot.postman_collection.json new file mode 100644 index 00000000..1da66b38 --- /dev/null +++ b/ocelot.postman_collection.json @@ -0,0 +1,117 @@ +{ + "id": "23a49657-e24b-b967-7ec0-943ff1368680", + "name": "Ocelot Admin", + "description": "", + "order": [ + "59162efa-27ce-c230-f523-81d31ead603d", + "e0defe09-c1b2-9e95-8237-67df4bbab284", + "30007c41-565c-5b87-ea34-42170dd386d7" + ], + "folders": [], + "timestamp": 1488042899799, + "owner": "212120", + "public": false, + "requests": [ + { + "id": "30007c41-565c-5b87-ea34-42170dd386d7", + "headers": "Authorization: Bearer {{AccessToken}}\n", + "url": "http://localhost:5000/admin/configuration", + "preRequestScript": null, + "pathVariables": {}, + "method": "GET", + "data": null, + "dataMode": "params", + "version": 2, + "tests": null, + "currentHelper": "normal", + "helperAttributes": "{}", + "time": 1487515927978, + "name": "POST http://localhost:5000/admin/configuration", + "description": "", + "collectionId": "23a49657-e24b-b967-7ec0-943ff1368680", + "responses": [], + "isFromCollection": true, + "collectionRequestId": "59162efa-27ce-c230-f523-81d31ead603d" + }, + { + "id": "59162efa-27ce-c230-f523-81d31ead603d", + "headers": "Authorization: Bearer {{AccessToken}}\nContent-Type: application/json\n", + "url": "http://localhost:5000/admin/configuration", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [], + "dataMode": "raw", + "version": 2, + "tests": null, + "currentHelper": "normal", + "helperAttributes": {}, + "time": 1488044268493, + "name": "GET http://localhost:5000/admin/configuration", + "description": "", + "collectionId": "23a49657-e24b-b967-7ec0-943ff1368680", + "responses": [], + "rawModeData": "{\n \"reRoutes\": [\n {\n \"downstreamPathTemplate\": \"/\",\n \"upstreamPathTemplate\": \"/identityserverexample\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": \"IdentityServer\",\n \"providerRootUrl\": \"http://localhost:52888\",\n \"scopeName\": \"api\",\n \"requireHttps\": false,\n \"additionalScopes\": [\n \"openid\",\n \"offline_access\"\n ],\n \"scopeSecret\": \"secret\"\n },\n \"addHeadersToRequest\": {\n \"CustomerId\": \"Claims[CustomerId] > value\",\n \"LocationId\": \"Claims[LocationId] > value\",\n \"UserId\": \"Claims[sub] > value[1] > |\",\n \"UserType\": \"Claims[sub] > value[0] > |\"\n },\n \"addClaimsToRequest\": {\n \"CustomerId\": \"Claims[CustomerId] > value\",\n \"LocationId\": \"Claims[LocationId] > value\",\n \"UserId\": \"Claims[sub] > value[1] > |\",\n \"UserType\": \"Claims[sub] > value[0] > |\"\n },\n \"routeClaimsRequirement\": {\n \"UserType\": \"registered\"\n },\n \"addQueriesToRequest\": {\n \"CustomerId\": \"Claims[CustomerId] > value\",\n \"LocationId\": \"Claims[LocationId] > value\",\n \"UserId\": \"Claims[sub] > value[1] > |\",\n \"UserType\": \"Claims[sub] > value[0] > |\"\n },\n \"requestIdKey\": \"OcRequestId\",\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"localhost\",\n \"downstreamPort\": 52876,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/\",\n \"upstreamPathTemplate\": \"/posts\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"www.bbc.co.uk\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/posts/{postId}\",\n \"upstreamPathTemplate\": \"/posts/{postId}\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/posts/{postId}/comments\",\n \"upstreamPathTemplate\": \"/posts/{postId}/comments\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/comments\",\n \"upstreamPathTemplate\": \"/comments\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/posts\",\n \"upstreamPathTemplate\": \"/posts\",\n \"upstreamHttpMethod\": \"Post\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/posts/{postId}\",\n \"upstreamPathTemplate\": \"/posts/{postId}\",\n \"upstreamHttpMethod\": \"Put\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/posts/{postId}\",\n \"upstreamPathTemplate\": \"/posts/{postId}\",\n \"upstreamHttpMethod\": \"Patch\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/posts/{postId}\",\n \"upstreamPathTemplate\": \"/posts/{postId}\",\n \"upstreamHttpMethod\": \"Delete\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/products\",\n \"upstreamPathTemplate\": \"/products\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/products/{productId}\",\n \"upstreamPathTemplate\": \"/products/{productId}\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 0,\n \"durationOfBreak\": 0,\n \"timeoutValue\": 0\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/products\",\n \"upstreamPathTemplate\": \"/products\",\n \"upstreamHttpMethod\": \"Post\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 0\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"products20161126090340.azurewebsites.net\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/products/{productId}\",\n \"upstreamPathTemplate\": \"/products/{productId}\",\n \"upstreamHttpMethod\": \"Put\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"products20161126090340.azurewebsites.net\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/products/{productId}\",\n \"upstreamPathTemplate\": \"/products/{productId}\",\n \"upstreamHttpMethod\": \"Delete\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"products20161126090340.azurewebsites.net\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/customers\",\n \"upstreamPathTemplate\": \"/customers\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"customers20161126090811.azurewebsites.net\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/customers/{customerId}\",\n \"upstreamPathTemplate\": \"/customers/{customerId}\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"customers20161126090811.azurewebsites.net\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/customers\",\n \"upstreamPathTemplate\": \"/customers\",\n \"upstreamHttpMethod\": \"Post\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"customers20161126090811.azurewebsites.net\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/customers/{customerId}\",\n \"upstreamPathTemplate\": \"/customers/{customerId}\",\n \"upstreamHttpMethod\": \"Put\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"customers20161126090811.azurewebsites.net\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/api/customers/{customerId}\",\n \"upstreamPathTemplate\": \"/customers/{customerId}\",\n \"upstreamHttpMethod\": \"Delete\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"customers20161126090811.azurewebsites.net\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n },\n {\n \"downstreamPathTemplate\": \"/posts\",\n \"upstreamPathTemplate\": \"/posts/\",\n \"upstreamHttpMethod\": \"Get\",\n \"authenticationOptions\": {\n \"provider\": null,\n \"providerRootUrl\": null,\n \"scopeName\": null,\n \"requireHttps\": false,\n \"additionalScopes\": [],\n \"scopeSecret\": null\n },\n \"addHeadersToRequest\": {},\n \"addClaimsToRequest\": {},\n \"routeClaimsRequirement\": {},\n \"addQueriesToRequest\": {},\n \"requestIdKey\": null,\n \"fileCacheOptions\": {\n \"ttlSeconds\": 15\n },\n \"reRouteIsCaseSensitive\": false,\n \"serviceName\": null,\n \"downstreamScheme\": \"http\",\n \"downstreamHost\": \"jsonplaceholder.typicode.com\",\n \"downstreamPort\": 80,\n \"qoSOptions\": {\n \"exceptionsAllowedBeforeBreaking\": 3,\n \"durationOfBreak\": 10,\n \"timeoutValue\": 5000\n },\n \"loadBalancer\": null\n }\n ],\n \"globalConfiguration\": {\n \"requestIdKey\": \"OcRequestId\",\n \"serviceDiscoveryProvider\": {\n \"provider\": null,\n \"host\": null,\n \"port\": 0\n },\n \"administrationPath\": \"/admin\"\n }\n}" + }, + { + "id": "e0defe09-c1b2-9e95-8237-67df4bbab284", + "headers": "", + "url": "http://localhost:5000/admin/connect/token", + "preRequestScript": null, + "pathVariables": {}, + "method": "POST", + "data": [ + { + "key": "client_id", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "client_secret", + "value": "secret", + "type": "text", + "enabled": true + }, + { + "key": "scope", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "username", + "value": "admin", + "type": "text", + "enabled": true + }, + { + "key": "password", + "value": "secret", + "type": "text", + "enabled": true + }, + { + "key": "grant_type", + "value": "password", + "type": "text", + "enabled": true + } + ], + "dataMode": "params", + "version": 2, + "tests": "var jsonData = JSON.parse(responseBody);\npostman.setGlobalVariable(\"AccessToken\", jsonData.access_token);\npostman.setGlobalVariable(\"RefreshToken\", jsonData.refresh_token);", + "currentHelper": "normal", + "helperAttributes": "{}", + "time": 1487515922748, + "name": "POST http://localhost:5000/admin/connect/token", + "description": "", + "collectionId": "23a49657-e24b-b967-7ec0-943ff1368680", + "responses": [], + "rawModeData": null, + "descriptionFormat": null, + "isFromCollection": true, + "collectionRequestId": "e23e29a1-6abb-abd3-141a-f2202e3f582b" + } + ] +} \ No newline at end of file From 56971836666f369f06ba2651517f61feeca6e77d Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sat, 25 Feb 2017 19:20:09 +0000 Subject: [PATCH 112/113] updated readme --- README.md | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 885c556f..4e765516 100644 --- a/README.md +++ b/README.md @@ -119,19 +119,25 @@ Then in your Program.cs you will want to have the following. This can be changed 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 -IWebHostBuilder builder = new WebHostBuilder(); + public class Program + { + public static void Main(string[] args) + { + IWebHostBuilder builder = new WebHostBuilder(); -builder.ConfigureServices(s => { - s.AddSingleton(builder); -}); + builder.ConfigureServices(s => { + s.AddSingleton(builder); + }); -builder.UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup(); + builder.UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup(); -var host = builder.Build(); + var host = builder.Build(); -host.Run(); + host.Run(); + } + } ``` Sadly we need to inject the IWebHostBuilder interface to get the applications scheme, url and port later. I cannot From b09be0571f2b2c40880767d1449c10dcb6766806 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sat, 25 Feb 2017 19:25:45 +0000 Subject: [PATCH 113/113] moved codescene thing as quite big --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4e765516..642f843f 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ [![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) -[![](https://codescene.io/projects/697/status.svg) Get more details at **codescene.io**.](https://codescene.io/projects/697/jobs/latest-successful/results) - Attempt at a .NET Api Gateway This project is aimed at people using .NET running @@ -540,6 +538,8 @@ forwarded to the downstream service. Obviously this would break everything :( and doesnt check the response is OK. I think the fact you can even call stuff 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)