From 9cb201cfa93d0161fafbccc81255afc3e47c34eb Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Thu, 4 Jan 2018 21:35:44 +0000 Subject: [PATCH 01/50] Changed routing to support a catch all style --- docs/features/routing.rst | 35 ++++- .../Creator/UpstreamTemplatePatternCreator.cs | 1 + .../UrlPathPlaceholderNameAndValueFinder.cs | 24 ++++ test/Ocelot.AcceptanceTests/RoutingTests.cs | 129 ++++++++++++++++-- .../UrlMatcher/RegExUrlMatcherTests.cs | 12 ++ ...PathPlaceholderNameAndValueFinderTests.cs} | 82 ++++++++++- 6 files changed, 269 insertions(+), 14 deletions(-) rename test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/{TemplateVariableNameAndValueFinderTests.cs => UrlPathPlaceholderNameAndValueFinderTests.cs} (71%) diff --git a/docs/features/routing.rst b/docs/features/routing.rst index ed0d967e..3f9ddec8 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -62,4 +62,37 @@ In order to change this you can specify on a per ReRoute basis the following set This means that when Ocelot tries to match the incoming upstream url with an upstream template the evaluation will be case sensitive. This setting defaults to false so only set it if you want -the ReRoute to be case sensitive is my advice! \ No newline at end of file +the ReRoute to be case sensitive is my advice! + +Catch All +^^^^^^^^^ + +Ocelot's routing also supports a catch all style routing where the user can specify that they want to match all traffic if you set up your config like below the request will be proxied straight through (it doesnt have to be url any placeholder name will work). + +.. code-block:: json + + { + "DownstreamPathTemplate": "/{url}", + "DownstreamScheme": "https", + "DownstreamPort": 80, + "DownstreamHost" "localhost", + "UpstreamPathTemplate": "/{url}", + "UpstreamHttpMethod": [ "Get" ] + } + +There is a gotcha if you want to do this kind of thing. The order of the ReRoutes in the config will now matter. + +If you had this ReRoute after the catch all then it would never be matched. However if it was before the catch all it would match first. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "https", + "DownstreamPort": 80, + "DownstreamHost" "10.0.10.1", + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": [ "Get" ] + } + +This is because when Ocelot tries to match a request to a ReRoute it has to look at all the possible matches and uses a regular expression to test the url. \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs index a45d2864..9e7d9770 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs @@ -26,6 +26,7 @@ namespace Ocelot.Configuration.Creator var placeHolderName = upstreamTemplate.Substring(i, difference); placeholders.Add(placeHolderName); + //hack to handle /{url} case if(ForwardSlashAndOnePlaceHolder(upstreamTemplate, placeholders, postitionOfPlaceHolderClosingBracket)) { return RegExForwardSlashAndOnePlaceHolder; diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs index 946a365c..0715aeb1 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs @@ -34,6 +34,30 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher return new OkResponse>(templateKeysAndValues); } + //hacking to handle special case of /{url} + //if this char is a forward slash and the template starts with /{ and last character of string is the next } + else if(string.IsNullOrEmpty(upstreamUrlPath) || (upstreamUrlPath.Length > counterForUrl && upstreamUrlPath[counterForUrl] == '/') && upstreamUrlPathTemplate.Length > 1 + && upstreamUrlPathTemplate.Substring(0, 2) == "/{" + && upstreamUrlPathTemplate.IndexOf('}') == upstreamUrlPathTemplate.Length - 1) + { + var endOfPlaceholder = GetNextCounterPosition(upstreamUrlPathTemplate, counterForTemplate, '}'); + var variableName = GetPlaceholderVariableName(upstreamUrlPathTemplate, 1); + + UrlPathPlaceholderNameAndValue templateVariableNameAndValue; + + if(upstreamUrlPath.Length == 1 || upstreamUrlPath.Length == 0) + { + templateVariableNameAndValue = new UrlPathPlaceholderNameAndValue(variableName, ""); + } + else + { + var variableValue = GetPlaceholderVariableValue(upstreamUrlPathTemplate, variableName, upstreamUrlPath, counterForUrl + 1); + templateVariableNameAndValue = new UrlPathPlaceholderNameAndValue(variableName, variableValue); + } + + templateKeysAndValues.Add(templateVariableNameAndValue); + counterForTemplate = endOfPlaceholder; + } counterForUrl++; } diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 587327d1..44d0b8b6 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -52,7 +52,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -61,6 +61,108 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_return_response_200_favouring_forward_slash_with_path_route() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/{url}", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51880, + UpstreamPathTemplate = "/{url}", + UpstreamHttpMethod = new List { "Get" }, + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", "/test", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/test")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_favouring_forward_slash_route_because_it_is_first() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51880, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + }, + new FileReRoute + { + DownstreamPathTemplate = "/{url}", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamPathTemplate = "/{url}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", "/", 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_with_nothing_and_placeholder_only() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/{url}", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamPathTemplate = "/{url}", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 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_with_simple_url() { @@ -80,7 +182,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -378,7 +480,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "", 201, string.Empty)) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 201, string.Empty)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.GivenThePostHasContent("postContent")) @@ -406,7 +508,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/newThing", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/newThing?DeviceType=IphoneApp&Browser=moonpigIphone&BrowserString=-&CountryCode=123&DeviceName=iPhone 5 (GSM+CDMA)&OperatingSystem=iPhone OS 7.1.2&BrowserVersion=3708AdHoc&ipAddress=-")) @@ -434,7 +536,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/myApp1Name/api/products/1", 200, "Some Product")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/api/products/1", 200, "Some Product")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/myApp1Name/api/products/1")) @@ -490,7 +592,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -564,7 +666,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", "", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51899", "/api/swagger/lib/backbone-min.js", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/platform/swagger/lib/backbone-min.js")) @@ -587,8 +689,17 @@ namespace Ocelot.AcceptanceTests app.Run(async context => { _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); + + if(_downstreamPath != basePath) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } }); }) .Build(); diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs index 9f76228b..f57ed842 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs @@ -18,6 +18,18 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher _urlMatcher = new RegExUrlMatcher(); } + [Fact] + public void should_not_match_slash_becaue_we_need_to_match_something_after_it() + { + const string RegExForwardSlashAndOnePlaceHolder = "^/[0-9a-zA-Z].*"; + + this.Given(x => x.GivenIHaveAUpstreamPath("/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplatePattern(RegExForwardSlashAndOnePlaceHolder)) + .When(x => x.WhenIMatchThePaths()) + .And(x => x.ThenTheResultIsFalse()) + .BDDfy(); + } + [Fact] public void should_not_match_forward_slash_only_regex() { diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/TemplateVariableNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs similarity index 71% rename from test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/TemplateVariableNameAndValueFinderTests.cs rename to test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs index fded9ecf..ec955038 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/TemplateVariableNameAndValueFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs @@ -8,14 +8,14 @@ using Xunit; namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher { - public class UrlPathToUrlTemplateMatcherTests + public class UrlPathPlaceholderNameAndValueFinderTests { private readonly IUrlPathPlaceholderNameAndValueFinder _finder; private string _downstreamUrlPath; private string _downstreamPathTemplate; private Response> _result; - public UrlPathToUrlTemplateMatcherTests() + public UrlPathPlaceholderNameAndValueFinderTests() { _finder = new UrlPathPlaceholderNameAndValueFinder(); } @@ -30,6 +30,81 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher .BDDfy(); } + + [Fact] + public void can_match_down_stream_url_with_nothing_then_placeholder_no_value_is_blank() + { + var expectedTemplates = new List + { + new UrlPathPlaceholderNameAndValue("{url}", "") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_nothing_then_placeholder_value_is_test() + { + var expectedTemplates = new List + { + new UrlPathPlaceholderNameAndValue("{url}", "test") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/test")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_forward_slash_then_placeholder_no_value_is_blank() + { + var expectedTemplates = new List + { + new UrlPathPlaceholderNameAndValue("{url}", "") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_forward_slash() + { + var expectedTemplates = new List + { + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + + [Fact] + public void can_match_down_stream_url_with_forward_slash_then_placeholder_then_another_value() + { + var expectedTemplates = new List + { + new UrlPathPlaceholderNameAndValue("{url}", "1") + }; + + this.Given(x => x.GivenIHaveAUpstreamPath("/1/products")) + .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/{url}/products")) + .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) + .And(x => x.ThenTheTemplatesVariablesAre(expectedTemplates)) + .BDDfy(); + } + [Fact] public void should_not_find_anything() { @@ -169,8 +244,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher { foreach (var expectedResult in expectedResults) { - var result = _result.Data - .First(t => t.TemplateVariableName == expectedResult.TemplateVariableName); + var result = _result.Data.First(t => t.TemplateVariableName == expectedResult.TemplateVariableName); result.TemplateVariableValue.ShouldBe(expectedResult.TemplateVariableValue); } } From 9f8da1fbe466bfeacd32380309c86a56a15be618 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 5 Jan 2018 08:18:37 +0000 Subject: [PATCH 02/50] refactoring placeholder tuff --- .../DependencyInjection/OcelotBuilder.cs | 2 +- .../DownstreamRouteFinder/DownstreamRoute.cs | 4 +- .../Finder/DownstreamRouteFinder.cs | 4 +- .../IUrlPathPlaceholderNameAndValueFinder.cs | 4 +- .../UrlPathPlaceholderNameAndValue.cs | 12 ++-- .../UrlPathPlaceholderNameAndValueFinder.cs | 68 +++++++++--------- .../DownstreamUrlTemplateVariableReplacer.cs | 4 +- ...wnstreamUrlPathTemplateVariableReplacer.cs | 2 +- .../AuthenticationMiddlewareTests.cs | 2 +- .../AuthorisationMiddlewareTests.cs | 2 +- .../Cache/OutputCacheMiddlewareTests.cs | 2 +- .../Claims/ClaimsBuilderMiddlewareTests.cs | 2 +- .../DownstreamRouteFinderMiddlewareTests.cs | 2 +- .../DownstreamRouteFinderTests.cs | 44 ++++++------ ...lPathPlaceholderNameAndValueFinderTests.cs | 72 +++++++++---------- .../DownstreamUrlCreatorMiddlewareTests.cs | 4 +- ...eamUrlPathTemplateVariableReplacerTests.cs | 32 ++++----- ...ttpRequestHeadersBuilderMiddlewareTests.cs | 2 +- .../LoadBalancerMiddlewareTests.cs | 6 +- .../QueryStringBuilderMiddlewareTests.cs | 2 +- .../ClientRateLimitMiddlewareTests.cs | 4 +- .../HttpRequestBuilderMiddlewareTests.cs | 2 +- .../RequestId/RequestIdMiddlewareTests.cs | 4 +- 23 files changed, 143 insertions(+), 139 deletions(-) diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index b8ebc8a2..c5317a51 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -108,7 +108,7 @@ namespace Ocelot.DependencyInjection _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); - _services.TryAddSingleton(); + _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); diff --git a/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs b/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs index d4a117c0..7a4a66ea 100644 --- a/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs +++ b/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs @@ -6,12 +6,12 @@ namespace Ocelot.DownstreamRouteFinder { public class DownstreamRoute { - public DownstreamRoute(List templatePlaceholderNameAndValues, ReRoute reRoute) + public DownstreamRoute(List templatePlaceholderNameAndValues, ReRoute reRoute) { TemplatePlaceholderNameAndValues = templatePlaceholderNameAndValues; ReRoute = reRoute; } - public List TemplatePlaceholderNameAndValues { get; private set; } + public List TemplatePlaceholderNameAndValues { get; private set; } public ReRoute ReRoute { 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 2c3077cc..b568f31b 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -13,9 +13,9 @@ namespace Ocelot.DownstreamRouteFinder.Finder public class DownstreamRouteFinder : IDownstreamRouteFinder { private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; - private readonly IUrlPathPlaceholderNameAndValueFinder _urlPathPlaceholderNameAndValueFinder; + private readonly IPlaceholderNameAndValueFinder _urlPathPlaceholderNameAndValueFinder; - public DownstreamRouteFinder(IUrlPathToUrlTemplateMatcher urlMatcher, IUrlPathPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder) + public DownstreamRouteFinder(IUrlPathToUrlTemplateMatcher urlMatcher, IPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder) { _urlMatcher = urlMatcher; _urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathPlaceholderNameAndValueFinder.cs index 788299cb..678b1081 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathPlaceholderNameAndValueFinder.cs @@ -3,8 +3,8 @@ using Ocelot.Responses; namespace Ocelot.DownstreamRouteFinder.UrlMatcher { - public interface IUrlPathPlaceholderNameAndValueFinder + public interface IPlaceholderNameAndValueFinder { - Response> Find(string upstreamUrlPath, string upstreamUrlPathTemplate); + Response> Find(string path, string pathTemplate); } } diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValue.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValue.cs index cb690666..825f1bab 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValue.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValue.cs @@ -1,13 +1,13 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher { - public class UrlPathPlaceholderNameAndValue + public class PlaceholderNameAndValue { - public UrlPathPlaceholderNameAndValue(string templateVariableName, string templateVariableValue) + public PlaceholderNameAndValue(string name, string value) { - TemplateVariableName = templateVariableName; - TemplateVariableValue = templateVariableValue; + Name = name; + Value = value; } - public string TemplateVariableName {get;private set;} - public string TemplateVariableValue {get;private set;} + public string Name {get;private set;} + public string Value {get;private set;} } } \ No newline at end of file diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs index 0715aeb1..8b1c6acf 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinder.cs @@ -3,68 +3,72 @@ using Ocelot.Responses; namespace Ocelot.DownstreamRouteFinder.UrlMatcher { - public class UrlPathPlaceholderNameAndValueFinder : IUrlPathPlaceholderNameAndValueFinder + public class UrlPathPlaceholderNameAndValueFinder : IPlaceholderNameAndValueFinder { - public Response> Find(string upstreamUrlPath, string upstreamUrlPathTemplate) + public Response> Find(string path, string pathTemplate) { - var templateKeysAndValues = new List(); + var placeHolderNameAndValues = new List(); - int counterForUrl = 0; + int counterForPath = 0; - for (int counterForTemplate = 0; counterForTemplate < upstreamUrlPathTemplate.Length; counterForTemplate++) + for (int counterForTemplate = 0; counterForTemplate < pathTemplate.Length; counterForTemplate++) { - if ((upstreamUrlPath.Length > counterForUrl) && CharactersDontMatch(upstreamUrlPathTemplate[counterForTemplate], upstreamUrlPath[counterForUrl]) && ContinueScanningUrl(counterForUrl,upstreamUrlPath.Length)) + if ((path.Length > counterForPath) && CharactersDontMatch(pathTemplate[counterForTemplate], path[counterForPath]) && ContinueScanningUrl(counterForPath,path.Length)) { - if (IsPlaceholder(upstreamUrlPathTemplate[counterForTemplate])) + if (IsPlaceholder(pathTemplate[counterForTemplate])) { - var variableName = GetPlaceholderVariableName(upstreamUrlPathTemplate, counterForTemplate); + var placeholderName = GetPlaceholderName(pathTemplate, counterForTemplate); - var variableValue = GetPlaceholderVariableValue(upstreamUrlPathTemplate, variableName, upstreamUrlPath, counterForUrl); + var placeholderValue = GetPlaceholderValue(pathTemplate, placeholderName, path, counterForPath); - var templateVariableNameAndValue = new UrlPathPlaceholderNameAndValue(variableName, variableValue); + placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); - templateKeysAndValues.Add(templateVariableNameAndValue); + counterForTemplate = GetNextCounterPosition(pathTemplate, counterForTemplate, '}'); - counterForTemplate = GetNextCounterPosition(upstreamUrlPathTemplate, counterForTemplate, '}'); - - counterForUrl = GetNextCounterPosition(upstreamUrlPath, counterForUrl, '/'); + counterForPath = GetNextCounterPosition(path, counterForPath, '/'); continue; } - return new OkResponse>(templateKeysAndValues); + return new OkResponse>(placeHolderNameAndValues); } - //hacking to handle special case of /{url} - //if this char is a forward slash and the template starts with /{ and last character of string is the next } - else if(string.IsNullOrEmpty(upstreamUrlPath) || (upstreamUrlPath.Length > counterForUrl && upstreamUrlPath[counterForUrl] == '/') && upstreamUrlPathTemplate.Length > 1 - && upstreamUrlPathTemplate.Substring(0, 2) == "/{" - && upstreamUrlPathTemplate.IndexOf('}') == upstreamUrlPathTemplate.Length - 1) + else if(IsCatchAll(path, counterForPath, pathTemplate)) { - var endOfPlaceholder = GetNextCounterPosition(upstreamUrlPathTemplate, counterForTemplate, '}'); - var variableName = GetPlaceholderVariableName(upstreamUrlPathTemplate, 1); + var endOfPlaceholder = GetNextCounterPosition(pathTemplate, counterForTemplate, '}'); - UrlPathPlaceholderNameAndValue templateVariableNameAndValue; + var placeholderName = GetPlaceholderName(pathTemplate, 1); - if(upstreamUrlPath.Length == 1 || upstreamUrlPath.Length == 0) + if(NothingAfterFirstForwardSlash(path)) { - templateVariableNameAndValue = new UrlPathPlaceholderNameAndValue(variableName, ""); + placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, "")); } else { - var variableValue = GetPlaceholderVariableValue(upstreamUrlPathTemplate, variableName, upstreamUrlPath, counterForUrl + 1); - templateVariableNameAndValue = new UrlPathPlaceholderNameAndValue(variableName, variableValue); + var placeholderValue = GetPlaceholderValue(pathTemplate, placeholderName, path, counterForPath + 1); + placeHolderNameAndValues.Add(new PlaceholderNameAndValue(placeholderName, placeholderValue)); } - templateKeysAndValues.Add(templateVariableNameAndValue); counterForTemplate = endOfPlaceholder; } - counterForUrl++; + counterForPath++; } - return new OkResponse>(templateKeysAndValues); + return new OkResponse>(placeHolderNameAndValues); } - private string GetPlaceholderVariableValue(string urlPathTemplate, string variableName, string urlPath, int counterForUrl) + private bool IsCatchAll(string path, int counterForPath, string pathTemplate) + { + return string.IsNullOrEmpty(path) || (path.Length > counterForPath && path[counterForPath] == '/') && pathTemplate.Length > 1 + && pathTemplate.Substring(0, 2) == "/{" + && pathTemplate.IndexOf('}') == pathTemplate.Length - 1; + } + + private bool NothingAfterFirstForwardSlash(string path) + { + return path.Length == 1 || path.Length == 0; + } + + private string GetPlaceholderValue(string urlPathTemplate, string variableName, string urlPath, int counterForUrl) { var positionOfNextSlash = urlPath.IndexOf('/', counterForUrl); @@ -78,7 +82,7 @@ namespace Ocelot.DownstreamRouteFinder.UrlMatcher return variableValue; } - private string GetPlaceholderVariableName(string urlPathTemplate, int counterForTemplate) + private string GetPlaceholderName(string urlPathTemplate, int counterForTemplate) { var postitionOfPlaceHolderClosingBracket = urlPathTemplate.IndexOf('}', counterForTemplate) + 1; diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/DownstreamUrlTemplateVariableReplacer.cs index 3c42b4f4..1b744819 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(PathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues) + public Response Replace(PathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues) { var downstreamPath = new StringBuilder(); @@ -16,7 +16,7 @@ namespace Ocelot.DownstreamUrlCreator.UrlTemplateReplacer foreach (var placeholderVariableAndValue in urlPathPlaceholderNameAndValues) { - downstreamPath.Replace(placeholderVariableAndValue.TemplateVariableName, placeholderVariableAndValue.TemplateVariableValue); + downstreamPath.Replace(placeholderVariableAndValue.Name, placeholderVariableAndValue.Value); } return new OkResponse(new DownstreamPath(downstreamPath.ToString())); diff --git a/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs b/src/Ocelot/DownstreamUrlCreator/UrlTemplateReplacer/IDownstreamUrlPathTemplateVariableReplacer.cs index 647af63a..46e998d4 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(PathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues); + Response Replace(PathTemplate downstreamPathTemplate, List urlPathPlaceholderNameAndValues); } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs index e6bcc500..dcb54388 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs @@ -29,7 +29,7 @@ { this.Given(x => x.GivenTheDownStreamRouteIs( new DownstreamRoute( - new List(), + new List(), new ReRouteBuilder().WithUpstreamHttpMethod(new List { "Get" }).Build()))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheUserIsAuthenticated()) diff --git a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs index ca346974..9ab81f94 100644 --- a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs @@ -32,7 +32,7 @@ [Fact] public void should_call_authorisation_service() { - this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), + this.Given(x => x.GivenTheDownStreamRouteIs(new DownstreamRoute(new List(), new ReRouteBuilder() .WithIsAuthorised(true) .WithUpstreamHttpMethod(new List { "Get" }) diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index 9b78b3cc..1499e19d 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -94,7 +94,7 @@ .WithUpstreamHttpMethod(new List { "Get" }) .Build(); - var downstreamRoute = new DownstreamRoute(new List(), reRoute); + var downstreamRoute = new DownstreamRoute(new List(), reRoute); ScopedRepository .Setup(x => x.Get(It.IsAny())) diff --git a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs index 3eb86d1e..2470a6fb 100644 --- a/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Claims/ClaimsBuilderMiddlewareTests.cs @@ -31,7 +31,7 @@ [Fact] public void should_call_claims_to_request_correctly() { - var downstreamRoute = new DownstreamRoute(new List(), + var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("any old string") .WithClaimsToClaims(new List diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 9b241fbe..5781996b 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -38,7 +38,7 @@ this.Given(x => x.GivenTheDownStreamRouteFinderReturns( new DownstreamRoute( - new List(), + new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("any old string") .WithUpstreamHttpMethod(new List { "Get" }) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index a1839bea..3b935bb4 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -17,7 +17,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder { private readonly IDownstreamRouteFinder _downstreamRouteFinder; private readonly Mock _mockMatcher; - private readonly Mock _finder; + private readonly Mock _finder; private string _upstreamUrlPath; private Response _result; private List _reRoutesConfig; @@ -28,7 +28,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder public DownstreamRouteFinderTests() { _mockMatcher = new Mock(); - _finder = new Mock(); + _finder = new Mock(); _downstreamRouteFinder = new Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteFinder(_mockMatcher.Object, _finder.Object); } @@ -39,8 +39,8 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher/")) .And(x =>x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>( - new List()))) + new OkResponse>( + new List()))) .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() @@ -56,7 +56,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .When(x => x.WhenICallTheFinder()) .Then( x => x.ThenTheFollowingIsReturned(new DownstreamRoute( - new List(), + new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) @@ -74,8 +74,8 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder this.Given(x => x.GivenThereIsAnUpstreamUrlPath("matchInUrlMatcher")) .And(x =>x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>( - new List()))) + new OkResponse>( + new List()))) .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() @@ -91,7 +91,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .When(x => x.WhenICallTheFinder()) .Then( x => x.ThenTheFollowingIsReturned(new DownstreamRoute( - new List(), + new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) @@ -110,7 +110,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And( x => x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) + new OkResponse>(new List()))) .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() @@ -125,7 +125,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), + x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) @@ -144,7 +144,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And( x => x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) + new OkResponse>(new List()))) .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() @@ -165,7 +165,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .When(x => x.WhenICallTheFinder()) .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), + x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPathForAPost") .WithUpstreamHttpMethod(new List { "Post" }) @@ -208,7 +208,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And( x => x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) + new OkResponse>(new List()))) .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() @@ -223,7 +223,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .When(x => x.WhenICallTheFinder()) .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), + x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) @@ -241,7 +241,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And( x => x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) + new OkResponse>(new List()))) .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() @@ -256,7 +256,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) .When(x => x.WhenICallTheFinder()) .Then( - x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), + x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) @@ -274,7 +274,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And( x => x.GivenTheTemplateVariableAndNameFinderReturns( - new OkResponse>(new List()))) + new OkResponse>(new List()))) .And(x => x.GivenTheConfigurationIs(new List { new ReRouteBuilder() @@ -294,7 +294,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .BDDfy(); } - private void GivenTheTemplateVariableAndNameFinderReturns(Response> response) + private void GivenTheTemplateVariableAndNameFinderReturns(Response> response) { _finder .Setup(x => x.Find(It.IsAny(), It.IsAny())) @@ -359,11 +359,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder for (int i = 0; i < _result.Data.TemplatePlaceholderNameAndValues.Count; i++) { - _result.Data.TemplatePlaceholderNameAndValues[i].TemplateVariableName.ShouldBe( - expected.TemplatePlaceholderNameAndValues[i].TemplateVariableName); + _result.Data.TemplatePlaceholderNameAndValues[i].Name.ShouldBe( + expected.TemplatePlaceholderNameAndValues[i].Name); - _result.Data.TemplatePlaceholderNameAndValues[i].TemplateVariableValue.ShouldBe( - expected.TemplatePlaceholderNameAndValues[i].TemplateVariableValue); + _result.Data.TemplatePlaceholderNameAndValues[i].Value.ShouldBe( + expected.TemplatePlaceholderNameAndValues[i].Value); } _result.IsError.ShouldBeFalse(); diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs index ec955038..9fa9366b 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/UrlPathPlaceholderNameAndValueFinderTests.cs @@ -10,10 +10,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher { public class UrlPathPlaceholderNameAndValueFinderTests { - private readonly IUrlPathPlaceholderNameAndValueFinder _finder; + private readonly IPlaceholderNameAndValueFinder _finder; private string _downstreamUrlPath; private string _downstreamPathTemplate; - private Response> _result; + private Response> _result; public UrlPathPlaceholderNameAndValueFinderTests() { @@ -26,7 +26,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher this.Given(x => x.GivenIHaveAUpstreamPath("")) .And(x => x.GivenIHaveAnUpstreamUrlTemplate("")) .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) .BDDfy(); } @@ -34,9 +34,9 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher [Fact] public void can_match_down_stream_url_with_nothing_then_placeholder_no_value_is_blank() { - var expectedTemplates = new List + var expectedTemplates = new List { - new UrlPathPlaceholderNameAndValue("{url}", "") + new PlaceholderNameAndValue("{url}", "") }; this.Given(x => x.GivenIHaveAUpstreamPath("")) @@ -49,9 +49,9 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher [Fact] public void can_match_down_stream_url_with_nothing_then_placeholder_value_is_test() { - var expectedTemplates = new List + var expectedTemplates = new List { - new UrlPathPlaceholderNameAndValue("{url}", "test") + new PlaceholderNameAndValue("{url}", "test") }; this.Given(x => x.GivenIHaveAUpstreamPath("/test")) @@ -64,9 +64,9 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher [Fact] public void can_match_down_stream_url_with_forward_slash_then_placeholder_no_value_is_blank() { - var expectedTemplates = new List + var expectedTemplates = new List { - new UrlPathPlaceholderNameAndValue("{url}", "") + new PlaceholderNameAndValue("{url}", "") }; this.Given(x => x.GivenIHaveAUpstreamPath("/")) @@ -79,7 +79,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher [Fact] public void can_match_down_stream_url_with_forward_slash() { - var expectedTemplates = new List + var expectedTemplates = new List { }; @@ -93,9 +93,9 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher [Fact] public void can_match_down_stream_url_with_forward_slash_then_placeholder_then_another_value() { - var expectedTemplates = new List + var expectedTemplates = new List { - new UrlPathPlaceholderNameAndValue("{url}", "1") + new PlaceholderNameAndValue("{url}", "1") }; this.Given(x => x.GivenIHaveAUpstreamPath("/1/products")) @@ -111,7 +111,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher this.Given(x => x.GivenIHaveAUpstreamPath("/products")) .And(x => x.GivenIHaveAnUpstreamUrlTemplate("/products/")) .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) .BDDfy(); } @@ -121,7 +121,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher this.Given(x => x.GivenIHaveAUpstreamPath("api")) .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api")) .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) .BDDfy(); } @@ -131,7 +131,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher this.Given(x => x.GivenIHaveAUpstreamPath("api/")) .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/")) .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) .BDDfy(); } @@ -141,16 +141,16 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/")) .Given(x => x.GivenIHaveAnUpstreamUrlTemplate("api/product/products/")) .When(x => x.WhenIFindTheUrlVariableNamesAndValues()) - .And(x => x.ThenTheTemplatesVariablesAre(new List())) + .And(x => x.ThenTheTemplatesVariablesAre(new List())) .BDDfy(); } [Fact] public void can_match_down_stream_url_with_downstream_template_with_one_place_holder() { - var expectedTemplates = new List + var expectedTemplates = new List { - new UrlPathPlaceholderNameAndValue("{productId}", "1") + new PlaceholderNameAndValue("{productId}", "1") }; this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1")) @@ -163,10 +163,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher [Fact] public void can_match_down_stream_url_with_downstream_template_with_two_place_holders() { - var expectedTemplates = new List + var expectedTemplates = new List { - new UrlPathPlaceholderNameAndValue("{productId}", "1"), - new UrlPathPlaceholderNameAndValue("{categoryId}", "2") + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2") }; this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/2")) @@ -179,10 +179,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher [Fact] public void can_match_down_stream_url_with_downstream_template_with_two_place_holders_seperated_by_something() { - var expectedTemplates = new List + var expectedTemplates = new List { - new UrlPathPlaceholderNameAndValue("{productId}", "1"), - new UrlPathPlaceholderNameAndValue("{categoryId}", "2") + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2") }; this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2")) @@ -195,11 +195,11 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher [Fact] public void can_match_down_stream_url_with_downstream_template_with_three_place_holders_seperated_by_something() { - var expectedTemplates = new List + var expectedTemplates = new List { - new UrlPathPlaceholderNameAndValue("{productId}", "1"), - new UrlPathPlaceholderNameAndValue("{categoryId}", "2"), - new UrlPathPlaceholderNameAndValue("{variantId}", "123") + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2"), + new PlaceholderNameAndValue("{variantId}", "123") }; this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/123")) @@ -212,10 +212,10 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher [Fact] public void can_match_down_stream_url_with_downstream_template_with_three_place_holders() { - var expectedTemplates = new List + var expectedTemplates = new List { - new UrlPathPlaceholderNameAndValue("{productId}", "1"), - new UrlPathPlaceholderNameAndValue("{categoryId}", "2") + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{categoryId}", "2") }; this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/1/categories/2/variant/")) @@ -228,9 +228,9 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher [Fact] public void can_match_down_stream_url_with_downstream_template_with_place_holder_to_final_url_path() { - var expectedTemplates = new List + var expectedTemplates = new List { - new UrlPathPlaceholderNameAndValue("{finalUrlPath}", "product/products/categories/"), + new PlaceholderNameAndValue("{finalUrlPath}", "product/products/categories/"), }; this.Given(x => x.GivenIHaveAUpstreamPath("api/product/products/categories/")) @@ -240,12 +240,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher .BDDfy(); } - private void ThenTheTemplatesVariablesAre(List expectedResults) + private void ThenTheTemplatesVariablesAre(List expectedResults) { foreach (var expectedResult in expectedResults) { - var result = _result.Data.First(t => t.TemplateVariableName == expectedResult.TemplateVariableName); - result.TemplateVariableValue.ShouldBe(expectedResult.TemplateVariableValue); + var result = _result.Data.First(t => t.Name == expectedResult.Name); + result.Value.ShouldBe(expectedResult.Value); } } diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index 1af70f71..04afdf3a 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -47,7 +47,7 @@ { this.Given(x => x.GivenTheDownStreamRouteIs( new DownstreamRoute( - new List(), + new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("any old string") .WithUpstreamHttpMethod(new List { "Get" }) @@ -91,7 +91,7 @@ { _downstreamPath = new OkResponse(new DownstreamPath(path)); _downstreamUrlTemplateVariableReplacer - .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) + .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) .Returns(_downstreamPath); } diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs index 4939c901..7aa8fc16 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/UrlTemplateReplacer/UpstreamUrlPathTemplateVariableReplacerTests.cs @@ -27,7 +27,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer { this.Given(x => x.GivenThereIsAUrlMatch( new DownstreamRoute( - new List(), + new List(), new ReRouteBuilder() .WithUpstreamHttpMethod(new List { "Get" }) .Build()))) @@ -41,7 +41,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer { this.Given(x => x.GivenThereIsAUrlMatch( new DownstreamRoute( - new List(), + new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("/") .WithUpstreamHttpMethod(new List { "Get" }) @@ -54,7 +54,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_no_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("api") .WithUpstreamHttpMethod(new List { "Get" }) @@ -67,7 +67,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_one_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("api/") .WithUpstreamHttpMethod(new List { "Get" }) @@ -80,7 +80,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_multiple_slash() { - this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), + this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("api/product/products/") .WithUpstreamHttpMethod(new List { "Get" }) @@ -93,9 +93,9 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_one_template_variable() { - var templateVariables = new List() + var templateVariables = new List() { - new UrlPathPlaceholderNameAndValue("{productId}", "1") + new PlaceholderNameAndValue("{productId}", "1") }; this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, @@ -111,9 +111,9 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_one_template_variable_with_path_after() { - var templateVariables = new List() + var templateVariables = new List() { - new UrlPathPlaceholderNameAndValue("{productId}", "1") + new PlaceholderNameAndValue("{productId}", "1") }; this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, @@ -129,10 +129,10 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_two_template_variable() { - var templateVariables = new List() + var templateVariables = new List() { - new UrlPathPlaceholderNameAndValue("{productId}", "1"), - new UrlPathPlaceholderNameAndValue("{variantId}", "12") + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{variantId}", "12") }; this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, @@ -148,11 +148,11 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator.UrlTemplateReplacer [Fact] public void can_replace_url_three_template_variable() { - var templateVariables = new List() + var templateVariables = new List() { - new UrlPathPlaceholderNameAndValue("{productId}", "1"), - new UrlPathPlaceholderNameAndValue("{variantId}", "12"), - new UrlPathPlaceholderNameAndValue("{categoryId}", "34") + new PlaceholderNameAndValue("{productId}", "1"), + new PlaceholderNameAndValue("{variantId}", "12"), + new PlaceholderNameAndValue("{categoryId}", "34") }; this.Given(x => x.GivenThereIsAUrlMatch(new DownstreamRoute(templateVariables, diff --git a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs index 76a75d0a..20dbe84d 100644 --- a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs @@ -37,7 +37,7 @@ [Fact] public void should_call_add_headers_to_request_correctly() { - var downstreamRoute = new DownstreamRoute(new List(), + var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("any old string") .WithClaimsToHeaders(new List diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index 002e64a0..936e9249 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -47,7 +47,7 @@ namespace Ocelot.UnitTests.LoadBalancer [Fact] public void should_call_scoped_data_repository_correctly() { - var downstreamRoute = new DownstreamRoute(new List(), + var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() .WithUpstreamHttpMethod(new List { "Get" }) .Build()); @@ -68,7 +68,7 @@ namespace Ocelot.UnitTests.LoadBalancer [Fact] public void should_set_pipeline_error_if_cannot_get_load_balancer() { - var downstreamRoute = new DownstreamRoute(new List(), + var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() .WithUpstreamHttpMethod(new List { "Get" }) .Build()); @@ -88,7 +88,7 @@ namespace Ocelot.UnitTests.LoadBalancer [Fact] public void should_set_pipeline_error_if_cannot_get_least() { - var downstreamRoute = new DownstreamRoute(new List(), + var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() .WithUpstreamHttpMethod(new List { "Get" }) .Build()); diff --git a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs index 4bbcfd2c..a4a3b197 100644 --- a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs @@ -37,7 +37,7 @@ [Fact] public void should_call_add_queries_correctly() { - var downstreamRoute = new DownstreamRoute(new List(), + var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("any old string") .WithClaimsToQueries(new List diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index 1b73e233..e832760b 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -31,7 +31,7 @@ [Fact] public void should_call_middleware_and_ratelimiting() { - var downstreamRoute = new DownstreamRoute(new List(), + var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List(), false, "", "", new Ocelot.Configuration.RateLimitRule("1s", 100, 3), 429)) .WithUpstreamHttpMethod(new List { "Get" }) @@ -48,7 +48,7 @@ [Fact] public void should_call_middleware_withWhitelistClient() { - var downstreamRoute = new DownstreamRoute(new List(), + var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder().WithEnableRateLimiting(true).WithRateLimitOptions( new Ocelot.Configuration.RateLimitOptions(true, "ClientId", new List() { "ocelotclient2" }, false, "", "", new RateLimitRule( "1s", 100,3),429)) .WithUpstreamHttpMethod(new List { "Get" }) diff --git a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs index 4c72d137..10cbc120 100644 --- a/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/HttpRequestBuilderMiddlewareTests.cs @@ -47,7 +47,7 @@ public void should_call_scoped_data_repository_correctly() { - var downstreamRoute = new DownstreamRoute(new List(), + var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() .WithRequestIdKey("LSRequestId") .WithUpstreamHttpMethod(new List { "Get" }) diff --git a/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs b/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs index a34290f1..4d684084 100644 --- a/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RequestId/RequestIdMiddlewareTests.cs @@ -40,7 +40,7 @@ [Fact] public void should_pass_down_request_id_from_upstream_request() { - var downstreamRoute = new DownstreamRoute(new List(), + var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("any old string") .WithRequestIdKey("LSRequestId") @@ -59,7 +59,7 @@ [Fact] public void should_add_request_id_when_not_on_upstream_request() { - var downstreamRoute = new DownstreamRoute(new List(), + var downstreamRoute = new DownstreamRoute(new List(), new ReRouteBuilder() .WithDownstreamPathTemplate("any old string") .WithRequestIdKey("LSRequestId") From 9de00d6a46f635bb4faa1a08958b23f8bafd6e7c Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Fri, 5 Jan 2018 21:14:17 +0000 Subject: [PATCH 03/50] implemented simple priority in the routing --- docs/features/routing.rst | 8 +- .../Configuration/Builder/ReRouteBuilder.cs | 5 +- .../IUpstreamTemplatePatternCreator.cs | 3 +- .../Creator/UpstreamTemplatePatternCreator.cs | 9 +- src/Ocelot/Configuration/ReRoute.cs | 5 +- .../Finder/DownstreamRouteFinder.cs | 27 +++-- ...nstreamPathTemplate.cs => PathTemplate.cs} | 0 src/Ocelot/Values/UpstreamPathTemplate.cs | 14 +++ test/Ocelot.AcceptanceTests/RoutingTests.cs | 37 ++++++ .../FileConfigurationCreatorTests.cs | 7 +- .../UpstreamTemplatePatternCreatorTests.cs | 20 +++- .../DownstreamRouteFinderTests.cs | 108 +++++++++++++++--- 12 files changed, 197 insertions(+), 46 deletions(-) rename src/Ocelot/Values/{DownstreamPathTemplate.cs => PathTemplate.cs} (100%) create mode 100644 src/Ocelot/Values/UpstreamPathTemplate.cs diff --git a/docs/features/routing.rst b/docs/features/routing.rst index 3f9ddec8..0359d917 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -80,9 +80,7 @@ Ocelot's routing also supports a catch all style routing where the user can spec "UpstreamHttpMethod": [ "Get" ] } -There is a gotcha if you want to do this kind of thing. The order of the ReRoutes in the config will now matter. - -If you had this ReRoute after the catch all then it would never be matched. However if it was before the catch all it would match first. +The catch all has a lower priority than any other ReRoute. If you also have the ReRoute below in your config then Ocelot would match it before the catch all. .. code-block:: json @@ -93,6 +91,4 @@ If you had this ReRoute after the catch all then it would never be matched. Howe "DownstreamHost" "10.0.10.1", "UpstreamPathTemplate": "/", "UpstreamHttpMethod": [ "Get" ] - } - -This is because when Ocelot tries to match a request to a ReRoute it has to look at all the possible matches and uses a regular expression to test the url. \ No newline at end of file + } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 1a8877e7..cc5a61aa 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -2,6 +2,7 @@ using System.Net.Http; using Ocelot.Values; using System.Linq; +using Ocelot.Configuration.Creator; namespace Ocelot.Configuration.Builder { @@ -11,7 +12,7 @@ namespace Ocelot.Configuration.Builder private string _loadBalancerKey; private string _downstreamPathTemplate; private string _upstreamTemplate; - private string _upstreamTemplatePattern; + private UpstreamPathTemplate _upstreamTemplatePattern; private List _upstreamHttpMethod; private bool _isAuthenticated; private List _configHeaderExtractorProperties; @@ -65,7 +66,7 @@ namespace Ocelot.Configuration.Builder return this; } - public ReRouteBuilder WithUpstreamTemplatePattern(string input) + public ReRouteBuilder WithUpstreamTemplatePattern(UpstreamPathTemplate input) { _upstreamTemplatePattern = input; return this; diff --git a/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs index ae62c47a..14de619d 100644 --- a/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs @@ -1,9 +1,10 @@ using Ocelot.Configuration.File; +using Ocelot.Values; namespace Ocelot.Configuration.Creator { public interface IUpstreamTemplatePatternCreator { - string Create(FileReRoute reRoute); + UpstreamPathTemplate Create(FileReRoute reRoute); } } \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs index 9e7d9770..7816d118 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Ocelot.Configuration.File; +using Ocelot.Values; namespace Ocelot.Configuration.Creator { @@ -11,7 +12,7 @@ namespace Ocelot.Configuration.Creator private const string RegExForwardSlashOnly = "^/$"; private const string RegExForwardSlashAndOnePlaceHolder = "^/.*"; - public string Create(FileReRoute reRoute) + public UpstreamPathTemplate Create(FileReRoute reRoute) { var upstreamTemplate = reRoute.UpstreamPathTemplate; @@ -29,7 +30,7 @@ namespace Ocelot.Configuration.Creator //hack to handle /{url} case if(ForwardSlashAndOnePlaceHolder(upstreamTemplate, placeholders, postitionOfPlaceHolderClosingBracket)) { - return RegExForwardSlashAndOnePlaceHolder; + return new UpstreamPathTemplate(RegExForwardSlashAndOnePlaceHolder, 0); } } } @@ -41,7 +42,7 @@ namespace Ocelot.Configuration.Creator if (upstreamTemplate == "/") { - return RegExForwardSlashOnly; + return new UpstreamPathTemplate(RegExForwardSlashOnly, 1); } if(upstreamTemplate.EndsWith("/")) @@ -53,7 +54,7 @@ namespace Ocelot.Configuration.Creator ? $"^{upstreamTemplate}{RegExMatchEndString}" : $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; - return route; + return new UpstreamPathTemplate(route, 1); } private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List placeholders, int postitionOfPlaceHolderClosingBracket) diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 0d373425..18e068aa 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Net.Http; +using Ocelot.Configuration.Creator; using Ocelot.Values; namespace Ocelot.Configuration @@ -9,7 +10,7 @@ namespace Ocelot.Configuration public ReRoute(PathTemplate downstreamPathTemplate, PathTemplate upstreamPathTemplate, List upstreamHttpMethod, - string upstreamTemplatePattern, + UpstreamPathTemplate upstreamTemplatePattern, bool isAuthenticated, AuthenticationOptions authenticationOptions, List claimsToHeaders, @@ -67,7 +68,7 @@ namespace Ocelot.Configuration 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 UpstreamPathTemplate UpstreamTemplatePattern { get; private set; } public List UpstreamHttpMethod { get; private set; } public bool IsAuthenticated { get; private set; } public bool IsAuthorised { get; private set; } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index b568f31b..643a4464 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -13,34 +13,30 @@ namespace Ocelot.DownstreamRouteFinder.Finder public class DownstreamRouteFinder : IDownstreamRouteFinder { private readonly IUrlPathToUrlTemplateMatcher _urlMatcher; - private readonly IPlaceholderNameAndValueFinder _urlPathPlaceholderNameAndValueFinder; + private readonly IPlaceholderNameAndValueFinder __placeholderNameAndValueFinder; public DownstreamRouteFinder(IUrlPathToUrlTemplateMatcher urlMatcher, IPlaceholderNameAndValueFinder urlPathPlaceholderNameAndValueFinder) { _urlMatcher = urlMatcher; - _urlPathPlaceholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; + __placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; } - public Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod, IOcelotConfiguration configuration) + public Response FindDownstreamRoute(string path, string httpMethod, IOcelotConfiguration configuration) { - var applicableReRoutes = configuration.ReRoutes.Where(r => r.UpstreamHttpMethod.Count == 0 || r.UpstreamHttpMethod.Select(x => x.Method.ToLower()).Contains(upstreamHttpMethod.ToLower())); + var applicableReRoutes = configuration.ReRoutes.Where(r => r.UpstreamHttpMethod.Count == 0 || r.UpstreamHttpMethod.Select(x => x.Method.ToLower()).Contains(httpMethod.ToLower())).OrderByDescending(x => x.UpstreamTemplatePattern.Priority); foreach (var reRoute in applicableReRoutes) { - if (upstreamUrlPath == reRoute.UpstreamTemplatePattern) + if (path == reRoute.UpstreamTemplatePattern.Template) { - var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamPathTemplate.Value); - - return new OkResponse(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute)); + return GetPlaceholderNamesAndValues(path, reRoute); } - var urlMatch = _urlMatcher.Match(upstreamUrlPath, reRoute.UpstreamTemplatePattern); + var urlMatch = _urlMatcher.Match(path, reRoute.UpstreamTemplatePattern.Template); if (urlMatch.Data.Match) { - var templateVariableNameAndValues = _urlPathPlaceholderNameAndValueFinder.Find(upstreamUrlPath, reRoute.UpstreamPathTemplate.Value); - - return new OkResponse(new DownstreamRoute(templateVariableNameAndValues.Data, reRoute)); + return GetPlaceholderNamesAndValues(path, reRoute); } } @@ -49,5 +45,12 @@ namespace Ocelot.DownstreamRouteFinder.Finder new UnableToFindDownstreamRouteError() }); } + + private OkResponse GetPlaceholderNamesAndValues(string path, ReRoute reRoute) + { + var templatePlaceholderNameAndValues = __placeholderNameAndValueFinder.Find(path, reRoute.UpstreamPathTemplate.Value); + + return new OkResponse(new DownstreamRoute(templatePlaceholderNameAndValues.Data, reRoute)); + } } } \ No newline at end of file diff --git a/src/Ocelot/Values/DownstreamPathTemplate.cs b/src/Ocelot/Values/PathTemplate.cs similarity index 100% rename from src/Ocelot/Values/DownstreamPathTemplate.cs rename to src/Ocelot/Values/PathTemplate.cs diff --git a/src/Ocelot/Values/UpstreamPathTemplate.cs b/src/Ocelot/Values/UpstreamPathTemplate.cs new file mode 100644 index 00000000..00b70b2f --- /dev/null +++ b/src/Ocelot/Values/UpstreamPathTemplate.cs @@ -0,0 +1,14 @@ +namespace Ocelot.Values +{ + public class UpstreamPathTemplate + { + public UpstreamPathTemplate(string template, int priority) + { + Template = template; + Priority = priority; + } + + public string Template {get;} + public int Priority {get;} + } +} \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 44d0b8b6..21c21c06 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -98,6 +98,43 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_return_response_200_favouring_forward_slash() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/{url}", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51880, + UpstreamPathTemplate = "/{url}", + UpstreamHttpMethod = new List { "Get" }, + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51879, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879/", "/", 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_favouring_forward_slash_route_because_it_is_first() { diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 1182a56f..d39fa394 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -18,6 +18,7 @@ namespace Ocelot.UnitTests.Configuration using Ocelot.DependencyInjection; using Ocelot.Errors; using Ocelot.UnitTests.TestData; + using Ocelot.Values; public class FileConfigurationCreatorTests { @@ -367,7 +368,7 @@ namespace Ocelot.UnitTests.Configuration .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern("(?i)/api/products/.*/$") + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("(?i)/api/products/.*/$", 1)) .Build() })) .BDDfy(); @@ -580,7 +581,7 @@ namespace Ocelot.UnitTests.Configuration result.DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate.Value); result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod); result.UpstreamPathTemplate.Value.ShouldBe(expected.UpstreamPathTemplate.Value); - result.UpstreamTemplatePattern.ShouldBe(expected.UpstreamTemplatePattern); + result.UpstreamTemplatePattern?.Template.ShouldBe(expected.UpstreamTemplatePattern?.Template); result.ClaimsToClaims.Count.ShouldBe(expected.ClaimsToClaims.Count); result.ClaimsToHeaders.Count.ShouldBe(expected.ClaimsToHeaders.Count); result.ClaimsToQueries.Count.ShouldBe(expected.ClaimsToQueries.Count); @@ -623,7 +624,7 @@ namespace Ocelot.UnitTests.Configuration { _upstreamTemplatePatternCreator .Setup(x => x.Create(It.IsAny())) - .Returns(pattern); + .Returns(new UpstreamPathTemplate(pattern, 1)); } private void ThenTheRequestIdKeyCreatorIsCalledCorrectly() diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs index d5c9774b..87e2f2d6 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs @@ -1,5 +1,7 @@ +using System; using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; +using Ocelot.Values; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -10,7 +12,7 @@ namespace Ocelot.UnitTests.Configuration { private FileReRoute _fileReRoute; private UpstreamTemplatePatternCreator _creator; - private string _result; + private UpstreamPathTemplate _result; public UpstreamTemplatePatternCreatorTests() { @@ -29,6 +31,7 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) .When(x => x.WhenICreateTheTemplatePattern()) .Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS/[0-9a-zA-Z].*$")) + .And(x => ThenThePriorityIs(1)) .BDDfy(); } @@ -45,6 +48,7 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) .When(x => x.WhenICreateTheTemplatePattern()) .Then(x => x.ThenTheFollowingIsReturned("^(?i)/PRODUCTS(/|)$")) + .And(x => ThenThePriorityIs(1)) .BDDfy(); } @@ -59,6 +63,7 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) .When(x => x.WhenICreateTheTemplatePattern()) .Then(x => x.ThenTheFollowingIsReturned("^/PRODUCTS/[0-9a-zA-Z].*$")) + .And(x => ThenThePriorityIs(1)) .BDDfy(); } @@ -74,6 +79,7 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) .When(x => x.WhenICreateTheTemplatePattern()) .Then(x => x.ThenTheFollowingIsReturned("^/api/products/[0-9a-zA-Z].*$")) + .And(x => ThenThePriorityIs(1)) .BDDfy(); } @@ -89,6 +95,7 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) .When(x => x.WhenICreateTheTemplatePattern()) .Then(x => x.ThenTheFollowingIsReturned("^/api/products/[0-9a-zA-Z].*/variants/[0-9a-zA-Z].*$")) + .And(x => ThenThePriorityIs(1)) .BDDfy(); } @@ -104,6 +111,7 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) .When(x => x.WhenICreateTheTemplatePattern()) .Then(x => x.ThenTheFollowingIsReturned("^/api/products/[0-9a-zA-Z].*/variants/[0-9a-zA-Z].*(/|)$")) + .And(x => ThenThePriorityIs(1)) .BDDfy(); } @@ -118,6 +126,7 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) .When(x => x.WhenICreateTheTemplatePattern()) .Then(x => x.ThenTheFollowingIsReturned("^/$")) + .And(x => ThenThePriorityIs(1)) .BDDfy(); } @@ -132,6 +141,7 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) .When(x => x.WhenICreateTheTemplatePattern()) .Then(x => x.ThenTheFollowingIsReturned("^/.*")) + .And(x => ThenThePriorityIs(0)) .BDDfy(); } @@ -147,6 +157,7 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) .When(x => x.WhenICreateTheTemplatePattern()) .Then(x => x.ThenTheFollowingIsReturned("^/[0-9a-zA-Z].*/products/variants/[0-9a-zA-Z].*(/|)$")) + .And(x => ThenThePriorityIs(1)) .BDDfy(); } @@ -162,7 +173,12 @@ namespace Ocelot.UnitTests.Configuration private void ThenTheFollowingIsReturned(string expected) { - _result.ShouldBe(expected); + _result.Template.ShouldBe(expected); + } + + private void ThenThePriorityIs(int v) + { + _result.Priority.ShouldBe(v); } } } \ No newline at end of file diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 3b935bb4..1593b3f3 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -2,11 +2,13 @@ using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; using Ocelot.Configuration.Provider; using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Responses; +using Ocelot.Values; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -32,6 +34,80 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _downstreamRouteFinder = new Ocelot.DownstreamRouteFinder.Finder.DownstreamRouteFinder(_mockMatcher.Object, _finder.Object); } + + [Fact] + public void should_return_highest_priority_when_first() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .Build(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) + .Build() + }, string.Empty, serviceProviderConfig)) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) + .When(x => x.WhenICallTheFinder()) + .Then(x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .Build() + ))) + .BDDfy(); + } + + [Fact] + public void should_return_highest_priority_when_lowest() + { + var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); + + this.Given(x => x.GivenThereIsAnUpstreamUrlPath("someUpstreamPath")) + .And(x => x.GivenTheTemplateVariableAndNameFinderReturns( + new OkResponse>(new List()))) + .And(x => x.GivenTheConfigurationIs(new List + { + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 0)) + .Build(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamPathTemplate("someUpstreamPath") + .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .Build() + }, string.Empty, serviceProviderConfig)) + .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) + .And(x => x.GivenTheUpstreamHttpMethodIs("Post")) + .When(x => x.WhenICallTheFinder()) + .Then(x => x.ThenTheFollowingIsReturned(new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamPathTemplate("someDownstreamPath") + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("test", 1)) + .WithUpstreamHttpMethod(new List { "Post" }) + .Build() + ))) + .BDDfy(); + } + [Fact] public void should_return_route() { @@ -47,7 +123,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern("someUpstreamPath") + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) .Build() }, string.Empty, serviceProviderConfig )) @@ -60,6 +136,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly()) @@ -82,7 +159,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern("someUpstreamPath") + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) .Build() }, string.Empty, serviceProviderConfig )) @@ -95,6 +172,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) .Build() ))) .And(x => x.ThenTheUrlMatcherIsCalledCorrectly("matchInUrlMatcher")) @@ -117,7 +195,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern("someUpstreamPath") + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) .Build() }, string.Empty, serviceProviderConfig )) @@ -129,6 +207,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("someUpstreamPath", 1)) .Build() ))) .And(x => x.ThenTheUrlMatcherIsNotCalled()) @@ -151,13 +230,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern("") + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) .Build(), new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPathForAPost") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) - .WithUpstreamTemplatePattern("") + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) .Build() }, string.Empty, serviceProviderConfig )) @@ -169,6 +248,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPathForAPost") .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) .Build() ))) .BDDfy(); @@ -186,7 +266,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("somPath") .WithUpstreamPathTemplate("somePath") .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamTemplatePattern("somePath") + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("somePath", 1)) .Build(), }, string.Empty, serviceProviderConfig )) @@ -215,7 +295,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Post" }) - .WithUpstreamTemplatePattern("") + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) .Build() }, string.Empty, serviceProviderConfig )) @@ -227,6 +307,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) .Build() ))) .BDDfy(); @@ -248,7 +329,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List()) - .WithUpstreamTemplatePattern("") + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) .Build() }, string.Empty, serviceProviderConfig )) @@ -260,6 +341,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder new ReRouteBuilder() .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamHttpMethod(new List { "Post" }) + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) .Build() ))) .BDDfy(); @@ -281,7 +363,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithDownstreamPathTemplate("someDownstreamPath") .WithUpstreamPathTemplate("someUpstreamPath") .WithUpstreamHttpMethod(new List { "Get", "Patch", "Delete" }) - .WithUpstreamTemplatePattern("") + .WithUpstreamTemplatePattern(new UpstreamPathTemplate("", 1)) .Build() }, string.Empty, serviceProviderConfig )) @@ -356,14 +438,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void ThenTheFollowingIsReturned(DownstreamRoute expected) { _result.Data.ReRoute.DownstreamPathTemplate.Value.ShouldBe(expected.ReRoute.DownstreamPathTemplate.Value); + _result.Data.ReRoute.UpstreamTemplatePattern.Priority.ShouldBe(expected.ReRoute.UpstreamTemplatePattern.Priority); for (int i = 0; i < _result.Data.TemplatePlaceholderNameAndValues.Count; i++) { - _result.Data.TemplatePlaceholderNameAndValues[i].Name.ShouldBe( - expected.TemplatePlaceholderNameAndValues[i].Name); - - _result.Data.TemplatePlaceholderNameAndValues[i].Value.ShouldBe( - expected.TemplatePlaceholderNameAndValues[i].Value); + _result.Data.TemplatePlaceholderNameAndValues[i].Name.ShouldBe(expected.TemplatePlaceholderNameAndValues[i].Name); + _result.Data.TemplatePlaceholderNameAndValues[i].Value.ShouldBe(expected.TemplatePlaceholderNameAndValues[i].Value); } _result.IsError.ShouldBeFalse(); From fd2c5364fcaaac4547fb5b10f9936be1bea932fe Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Tue, 13 Mar 2018 20:31:22 +0000 Subject: [PATCH 04/50] #270 exposed ReRoute priority (#272) --- docs/features/routing.rst | 40 ++++++++++++++- .../Creator/UpstreamTemplatePatternCreator.cs | 4 +- .../File/FileAggregateReRoute.cs | 2 + src/Ocelot/Configuration/File/FileReRoute.cs | 2 + src/Ocelot/Configuration/File/IReRoute.cs | 1 + test/Ocelot.AcceptanceTests/RoutingTests.cs | 50 +++++++++++++++++++ .../UpstreamTemplatePatternCreatorTests.cs | 32 ++++++++++++ 7 files changed, 128 insertions(+), 3 deletions(-) diff --git a/docs/features/routing.rst b/docs/features/routing.rst index 4215aa44..b414d6c5 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -139,4 +139,42 @@ The ReRoute above will only be matched when the host header value is somedomain. If you do not set UpstreamHost on a ReRoue then any host header can match it. This is basically a catch all and preservers existing functionality at the time of building the feature. This means that if you have two ReRoutes that are the same apart from the UpstreamHost where one is null and the other set. Ocelot will favour the one that has been set. -This feature was requested as part of `Issue 216 `_ . \ No newline at end of file +This feature was requested as part of `Issue 216 `_ . + +Priority +^^^^^^^^ + +In `Issue 270 `_ I finally decided to expose the ReRoute priority in +configuration.json. This means you can decide in what order you want your ReRoutes to match the Upstream HttpRequest. + +In order to get this working add the following to a ReRoute in configuration.json, 0 is just an example value here but will explain below. + +.. code-block:: json + + { + "Priority": 0 + } + +0 is the lowest priority, Ocelot will always use 0 for /{catchAll} ReRoutes and this is still hardcoded. After that you are free +to set any priority you wish. + +e.g. you could have + +.. code-block:: json + + { + "UpstreamPathTemplate": "/goods/{catchAll}" + "Priority": 0 + } + +and + +.. code-block:: json + + { + "UpstreamPathTemplate": "/goods/delete" + "Priority": 1 + } + +In the example above if you make a request into Ocelot on /goods/delete Ocelot will match /goods/delete ReRoute. Previously it would have +matched /goods/{catchAll} (because this is the first ReRoute in the list!). \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs index 833c61db..dbf6a2d1 100644 --- a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs +++ b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs @@ -42,7 +42,7 @@ namespace Ocelot.Configuration.Creator if (upstreamTemplate == "/") { - return new UpstreamPathTemplate(RegExForwardSlashOnly, 1); + return new UpstreamPathTemplate(RegExForwardSlashOnly, reRoute.Priority); } if(upstreamTemplate.EndsWith("/")) @@ -54,7 +54,7 @@ namespace Ocelot.Configuration.Creator ? $"^{upstreamTemplate}{RegExMatchEndString}" : $"^{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}"; - return new UpstreamPathTemplate(route, 1); + return new UpstreamPathTemplate(route, reRoute.Priority); } private bool ForwardSlashAndOnePlaceHolder(string upstreamTemplate, List placeholders, int postitionOfPlaceHolderClosingBracket) diff --git a/src/Ocelot/Configuration/File/FileAggregateReRoute.cs b/src/Ocelot/Configuration/File/FileAggregateReRoute.cs index 8c9eabba..ca5013b2 100644 --- a/src/Ocelot/Configuration/File/FileAggregateReRoute.cs +++ b/src/Ocelot/Configuration/File/FileAggregateReRoute.cs @@ -14,5 +14,7 @@ namespace Ocelot.Configuration.File { get { return new List {"Get"}; } } + + public int Priority {get;set;} = 1; } } diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index adc747f9..0b2a06f9 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -20,6 +20,7 @@ namespace Ocelot.Configuration.File UpstreamHeaderTransform = new Dictionary(); DownstreamHostAndPorts = new List(); DelegatingHandlers = new List(); + Priority = 1; } public string DownstreamPathTemplate { get; set; } @@ -46,5 +47,6 @@ namespace Ocelot.Configuration.File public string UpstreamHost { get; set; } public string Key { get;set; } public List DelegatingHandlers {get;set;} + public int Priority { get;set; } } } diff --git a/src/Ocelot/Configuration/File/IReRoute.cs b/src/Ocelot/Configuration/File/IReRoute.cs index 69128d3a..fb7e9313 100644 --- a/src/Ocelot/Configuration/File/IReRoute.cs +++ b/src/Ocelot/Configuration/File/IReRoute.cs @@ -4,5 +4,6 @@ { string UpstreamPathTemplate { get; set; } bool ReRouteIsCaseSensitive { get; set; } + int Priority {get;set;} } } diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 0a16e565..9110af12 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -872,6 +872,56 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_use_priority() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/goods/{url}", + DownstreamScheme = "http", + UpstreamPathTemplate = "/goods/{url}", + UpstreamHttpMethod = new List { "Get" }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 53879, + } + }, + Priority = 0 + }, + new FileReRoute + { + DownstreamPathTemplate = "/goods/delete", + DownstreamScheme = "http", + UpstreamPathTemplate = "/goods/delete", + UpstreamHttpMethod = new List { "Get" }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 52879, + } + }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:52879/", "/goods/delete", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/goods/delete")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) { _builder = new WebHostBuilder() diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs index 9dce0e50..c70bd440 100644 --- a/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs @@ -19,6 +19,38 @@ namespace Ocelot.UnitTests.Configuration _creator = new UpstreamTemplatePatternCreator(); } + [Fact] + public void should_use_re_route_priority() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/orders/{catchAll}", + Priority = 0 + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^(?i)/orders/[0-9a-zA-Z].*$")) + .And(x => ThenThePriorityIs(0)) + .BDDfy(); + } + + [Fact] + public void should_use_zero_priority() + { + var fileReRoute = new FileReRoute + { + UpstreamPathTemplate = "/{catchAll}", + Priority = 1 + }; + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .When(x => x.WhenICreateTheTemplatePattern()) + .Then(x => x.ThenTheFollowingIsReturned("^/.*")) + .And(x => ThenThePriorityIs(0)) + .BDDfy(); + } + [Fact] public void should_set_upstream_template_pattern_to_ignore_case_sensitivity() { From d24df3642033c1edb4e79899dc5e62b667b68d67 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Wed, 14 Mar 2018 18:49:41 +0000 Subject: [PATCH 05/50] #271 Added some extra logging (#276) --- .../Requester/HttpClientHttpRequester.cs | 3 +- src/Ocelot/Requester/RequestTimedOutError.cs | 2 +- .../Requester/UnableToCompleteRequestError.cs | 2 +- .../Middleware/ResponderMiddleware.cs | 6 +++ test/Ocelot.AcceptanceTests/RoutingTests.cs | 49 +++++++++++++++++++ 5 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index c60e3b78..a3b81fbc 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -39,8 +39,7 @@ namespace Ocelot.Requester } catch (TimeoutRejectedException exception) { - return - new ErrorResponse(new RequestTimedOutError(exception)); + return new ErrorResponse(new RequestTimedOutError(exception)); } catch (BrokenCircuitException exception) { diff --git a/src/Ocelot/Requester/RequestTimedOutError.cs b/src/Ocelot/Requester/RequestTimedOutError.cs index 86eab0c6..f99308b3 100644 --- a/src/Ocelot/Requester/RequestTimedOutError.cs +++ b/src/Ocelot/Requester/RequestTimedOutError.cs @@ -6,7 +6,7 @@ namespace Ocelot.Requester public class RequestTimedOutError : Error { public RequestTimedOutError(Exception exception) - : base($"Timeout making http request, exception: {exception.Message}", OcelotErrorCode.RequestTimedOutError) + : base($"Timeout making http request, exception: {exception}", OcelotErrorCode.RequestTimedOutError) { } } diff --git a/src/Ocelot/Requester/UnableToCompleteRequestError.cs b/src/Ocelot/Requester/UnableToCompleteRequestError.cs index 033ca53f..c5eb7b31 100644 --- a/src/Ocelot/Requester/UnableToCompleteRequestError.cs +++ b/src/Ocelot/Requester/UnableToCompleteRequestError.cs @@ -6,7 +6,7 @@ namespace Ocelot.Requester public class UnableToCompleteRequestError : Error { public UnableToCompleteRequestError(Exception exception) - : base($"Error making http request, exception: {exception.Message}", OcelotErrorCode.UnableToCompleteRequestError) + : base($"Error making http request, exception: {exception}", OcelotErrorCode.UnableToCompleteRequestError) { } } diff --git a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs index 4bfbad7a..125280b0 100644 --- a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs +++ b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs @@ -39,6 +39,12 @@ namespace Ocelot.Responder.Middleware { var errors = context.Errors; _logger.LogError($"{errors.Count} pipeline errors found in {MiddlewareName}. Setting error response status code"); + + foreach(var error in errors) + { + _logger.LogError(error.Message); + } + SetErrorResponse(context.HttpContext, errors); } else diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 9110af12..6ede8524 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -922,6 +922,55 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_fix_issue_271() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/v1/{everything}", + DownstreamScheme = "http", + UpstreamPathTemplate = "/api/v1/{everything}", + UpstreamHttpMethod = new List { "Get", "Put", "Post" }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 54879, + } + }, + }, + new FileReRoute + { + DownstreamPathTemplate = "/connect/token", + DownstreamScheme = "http", + UpstreamPathTemplate = "/connect/token", + UpstreamHttpMethod = new List { "Post" }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 5001, + } + }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:54879/", "/api/v1/modules/Test", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/v1/modules/Test")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) { _builder = new WebHostBuilder() From 4e25f72a97eea95483cfe90c98d83ba44210f588 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 14 Mar 2018 19:50:46 +0000 Subject: [PATCH 06/50] #271 Added some extra logging --- src/Ocelot/Request/Mapper/UnmappableRequestError.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ocelot/Request/Mapper/UnmappableRequestError.cs b/src/Ocelot/Request/Mapper/UnmappableRequestError.cs index 79379b6b..81668eef 100644 --- a/src/Ocelot/Request/Mapper/UnmappableRequestError.cs +++ b/src/Ocelot/Request/Mapper/UnmappableRequestError.cs @@ -5,7 +5,7 @@ public class UnmappableRequestError : Error { - public UnmappableRequestError(Exception ex) : base($"Error when parsing incoming request, exception: {ex.Message}", OcelotErrorCode.UnmappableRequestError) + public UnmappableRequestError(Exception exception) : base($"Error when parsing incoming request, exception: {exception}", OcelotErrorCode.UnmappableRequestError) { } } From f7c23d3384ae0b53a1235b224b8bff0cf4b23836 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 14 Mar 2018 19:55:24 +0000 Subject: [PATCH 07/50] #271 formatting --- src/Ocelot/Requester/HttpClientHttpRequester.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index a3b81fbc..29311ead 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -43,8 +43,7 @@ namespace Ocelot.Requester } catch (BrokenCircuitException exception) { - return - new ErrorResponse(new RequestTimedOutError(exception)); + return new ErrorResponse(new RequestTimedOutError(exception)); } catch (Exception exception) { From c1b315173f5be626a8eba98c7a71eebd9e7a10d5 Mon Sep 17 00:00:00 2001 From: Simon Jefford Date: Fri, 16 Mar 2018 17:48:11 +0000 Subject: [PATCH 08/50] Squash some warnings (#278) * squash SA1649 warnings (file/type name mismatch) * squash SA1127 warnings (generic constraint on own line) * squash SA1507 warnings (multiple blank lines) * squash package analysis warnings re: summary text It's not actually possible to provide a summary right now as per https://github.com/NuGet/Home/issues/4587 * squash missing castle.core reference warning * squash obsolete method warnings re: AddOcelotBaseUrl --- src/Ocelot/Ocelot.csproj | 3 ++- src/Ocelot/Requester/HttpClientHttpRequester.cs | 3 ++- ...actory.cs => IDelegatingHandlerHandlerFactory.cs} | 0 .../Ocelot.AcceptanceTests.csproj | 5 ----- test/Ocelot.IntegrationTests/RaftTests.cs | 2 ++ .../ConfigurationBuilderExtensionsTests.cs | 3 ++- .../DependencyInjection/OcelotBuilderTests.cs | 12 ++++++++---- .../Request/Mapper/RequestMapperTests.cs | 1 - 8 files changed, 16 insertions(+), 13 deletions(-) rename src/Ocelot/Requester/{IDelegatingHandlerHandlerProviderFactory.cs => IDelegatingHandlerHandlerFactory.cs} (100%) diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index 89ada54d..3d9b65c7 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -3,6 +3,7 @@ netcoreapp2.0 2.0.0 2.0.0 + true This project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. We have been unable to find this in my current workplace without having to write our own Javascript middlewares to handle the IdentityServer reference tokens. We would rather use the IdentityServer code that already exists to do this. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features. Ocelot 0.0.0-dev @@ -48,4 +49,4 @@ - \ No newline at end of file + diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index 29311ead..c6598903 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -75,7 +75,8 @@ namespace Ocelot.Requester } } - public class ReRouteDelegatingHandler where T : DelegatingHandler + public class ReRouteDelegatingHandler + where T : DelegatingHandler { public T DelegatingHandler { get; private set; } } diff --git a/src/Ocelot/Requester/IDelegatingHandlerHandlerProviderFactory.cs b/src/Ocelot/Requester/IDelegatingHandlerHandlerFactory.cs similarity index 100% rename from src/Ocelot/Requester/IDelegatingHandlerHandlerProviderFactory.cs rename to src/Ocelot/Requester/IDelegatingHandlerHandlerFactory.cs diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index ed09ed96..90d5bc64 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -50,9 +50,4 @@ - - - ..\..\..\..\Users\TGP\.nuget\packages\castle.core\4.2.1\lib\netstandard1.3\Castle.Core.dll - - diff --git a/test/Ocelot.IntegrationTests/RaftTests.cs b/test/Ocelot.IntegrationTests/RaftTests.cs index b8c24d5a..3dc01bb9 100644 --- a/test/Ocelot.IntegrationTests/RaftTests.cs +++ b/test/Ocelot.IntegrationTests/RaftTests.cs @@ -342,7 +342,9 @@ namespace Ocelot.IntegrationTests .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); config.AddJsonFile("configuration.json"); config.AddJsonFile("peers.json", optional: true, reloadOnChange: true); + #pragma warning disable CS0618 config.AddOcelotBaseUrl(url); + #pragma warning restore CS0618 config.AddEnvironmentVariables(); }) .ConfigureServices(x => diff --git a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs index 0fcbf66e..5d32b1ed 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs @@ -22,9 +22,10 @@ namespace Ocelot.UnitTests.DependencyInjection private void GivenTheBaseUrl(string baseUrl) { + #pragma warning disable CS0618 var builder = new ConfigurationBuilder() .AddOcelotBaseUrl(baseUrl); - + #pragma warning restore CS0618 _configuration = builder.Build(); } diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index df812f70..1cd22388 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -223,12 +223,14 @@ namespace Ocelot.UnitTests.DependencyInjection _ocelotBuilder.AddAdministration("/administration", options); } - private void AddTransientGlobalDelegatingHandler() where T : DelegatingHandler + private void AddTransientGlobalDelegatingHandler() + where T : DelegatingHandler { _ocelotBuilder.AddTransientDelegatingHandler(true); } - private void AddSpecificTransientDelegatingHandler() where T : DelegatingHandler + private void AddSpecificTransientDelegatingHandler() + where T : DelegatingHandler { _ocelotBuilder.AddTransientDelegatingHandler(); } @@ -298,12 +300,14 @@ namespace Ocelot.UnitTests.DependencyInjection } } - private void AddGlobalDelegatingHandler() where T : DelegatingHandler + private void AddGlobalDelegatingHandler() + where T : DelegatingHandler { _ocelotBuilder.AddSingletonDelegatingHandler(true); } - private void AddSpecificDelegatingHandler() where T : DelegatingHandler + private void AddSpecificDelegatingHandler() + where T : DelegatingHandler { _ocelotBuilder.AddSingletonDelegatingHandler(); } diff --git a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs index ba719a45..22ff9276 100644 --- a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs +++ b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs @@ -163,7 +163,6 @@ .BDDfy(); } - [Fact] public void should_not_add_content_headers() { From ed11f3024c3d520a12cb23aa6e75a42bd36471ff Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 17 Mar 2018 11:35:16 +0000 Subject: [PATCH 09/50] Feature/#274 (#281) * #274 added acceptance tests, need to work out failing unit tests but probably going to be a redesign where we hold a reference to the cookie container and empty it if needed * #274 updated code coverage value * #274 offloaded cache logic to builder in preparation for adding state * #274 hacked something together but this is not right approach * #274 updated defaults and docs * #274 updated code coverage --- build.cake | 2 +- docs/features/configuration.rst | 17 +- .../File/FileHttpHandlerOptions.cs | 4 +- .../Requester/GlobalDelegatingHandler.cs | 14 ++ src/Ocelot/Requester/HttpClientBuilder.cs | 61 ++++++-- .../Requester/HttpClientHttpRequester.cs | 47 +----- src/Ocelot/Requester/HttpClientWrapper.cs | 2 +- src/Ocelot/Requester/IHttpClientBuilder.cs | 10 +- src/Ocelot/Requester/IHttpRequester.cs | 2 +- .../Requester/ReRouteDelegatingHandler.cs | 10 ++ test/Ocelot.AcceptanceTests/HeaderTests.cs | 121 +++++++++++++++ test/Ocelot.AcceptanceTests/Steps.cs | 5 + .../HttpHandlerOptionsCreatorTests.cs | 12 +- .../Requester/HttpClientBuilderTests.cs | 145 ++++++++++++++++-- .../Requester/HttpClientHttpRequesterTest.cs | 5 +- 15 files changed, 372 insertions(+), 85 deletions(-) create mode 100644 src/Ocelot/Requester/GlobalDelegatingHandler.cs create mode 100644 src/Ocelot/Requester/ReRouteDelegatingHandler.cs diff --git a/build.cake b/build.cake index af2f9364..c732864b 100644 --- a/build.cake +++ b/build.cake @@ -17,7 +17,7 @@ var artifactsDir = Directory("artifacts"); // unit testing var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests"); var unitTestAssemblies = @"./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj"; -var minCodeCoverage = 76.4d; +var minCodeCoverage = 82d; var coverallsRepoToken = "coveralls-repo-token-ocelot"; var coverallsRepo = "https://coveralls.io/github/TomPallister/Ocelot"; diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 93cae138..8a1e61a1 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -73,10 +73,17 @@ Follow Redirects / Use CookieContainer ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior: -- _AllowAutoRedirect_ is a value that indicates whether the request should follow redirection responses. -Set it true if the request should automatically follow redirection responses from the Downstream resource; otherwise false. The default value is true. -- _UseCookieContainer_ is a value that indicates whether the handler uses the CookieContainer property to store server cookies and uses these cookies when sending requests. -The default value is true. + +1. AllowAutoRedirect is a value that indicates whether the request should follow redirection responses. Set it true if the request should automatically +follow redirection responses from the Downstream resource; otherwise false. The default value is false. +2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer +property to store server cookies and uses these cookies when sending requests. The default value is false. Please note +that if you are using the CookieContainer Ocelot caches the HttpClient for each downstream service. This means that all requests +to that DownstreamService will share the same cookies. `Issue 274 `_ was created because a user +noticed that the cookies were being shared. I tried to think of a nice way to handle this but I think it is impossible. If you don't cache the clients +that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight +requests. This would also mean that subsequent requests dont use the cookies from the previous response! All in all not a great situation. I would avoid setting +UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request! Multiple environments ^^^^^^^^^^^^^^^^^^^^^ @@ -127,4 +134,4 @@ finds your Consul agent and interacts to load and store the configuration from C I decided to create this feature after working on the raft consensus algorithm and finding out its super hard. Why not take advantage of the fact Consul already gives you this! I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now. -This feature has a 3 second ttl cache before making a new request to your local consul agent. \ No newline at end of file +This feature has a 3 second ttl cache before making a new request to your local consul agent. diff --git a/src/Ocelot/Configuration/File/FileHttpHandlerOptions.cs b/src/Ocelot/Configuration/File/FileHttpHandlerOptions.cs index 7f24b572..2934254c 100644 --- a/src/Ocelot/Configuration/File/FileHttpHandlerOptions.cs +++ b/src/Ocelot/Configuration/File/FileHttpHandlerOptions.cs @@ -4,8 +4,8 @@ { public FileHttpHandlerOptions() { - AllowAutoRedirect = true; - UseCookieContainer = true; + AllowAutoRedirect = false; + UseCookieContainer = false; } public bool AllowAutoRedirect { get; set; } diff --git a/src/Ocelot/Requester/GlobalDelegatingHandler.cs b/src/Ocelot/Requester/GlobalDelegatingHandler.cs new file mode 100644 index 00000000..ba5e1c5f --- /dev/null +++ b/src/Ocelot/Requester/GlobalDelegatingHandler.cs @@ -0,0 +1,14 @@ +using System.Net.Http; + +namespace Ocelot.Requester +{ + public class GlobalDelegatingHandler + { + public GlobalDelegatingHandler(DelegatingHandler delegatingHandler) + { + DelegatingHandler = delegatingHandler; + } + + public DelegatingHandler DelegatingHandler { get; private set; } + } +} diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index b5604081..6cbb3aec 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -1,25 +1,61 @@ -using System.Linq; +using System; +using System.Linq; +using System.Net; using System.Net.Http; using Ocelot.Configuration; +using Ocelot.Logging; +using Ocelot.Middleware; namespace Ocelot.Requester { public class HttpClientBuilder : IHttpClientBuilder { private readonly IDelegatingHandlerHandlerFactory _factory; + private readonly IHttpClientCache _cacheHandlers; + private readonly IOcelotLogger _logger; + private string _cacheKey; + private HttpClient _httpClient; + private IHttpClient _client; + private HttpClientHandler _httpclientHandler; - public HttpClientBuilder(IDelegatingHandlerHandlerFactory house) + public HttpClientBuilder( + IDelegatingHandlerHandlerFactory factory, + IHttpClientCache cacheHandlers, + IOcelotLogger logger) { - _factory = house; + _factory = factory; + _cacheHandlers = cacheHandlers; + _logger = logger; } - public IHttpClient Create(DownstreamReRoute request) + public IHttpClient Create(DownstreamContext request) { - var httpclientHandler = new HttpClientHandler { AllowAutoRedirect = request.HttpHandlerOptions.AllowAutoRedirect, UseCookies = request.HttpHandlerOptions.UseCookieContainer}; - - var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler, request)); - - return new HttpClientWrapper(client); + _cacheKey = GetCacheKey(request); + + var httpClient = _cacheHandlers.Get(_cacheKey); + + if (httpClient != null) + { + return httpClient; + } + + _httpclientHandler = new HttpClientHandler + { + AllowAutoRedirect = request.DownstreamReRoute.HttpHandlerOptions.AllowAutoRedirect, + UseCookies = request.DownstreamReRoute.HttpHandlerOptions.UseCookieContainer, + CookieContainer = new CookieContainer() + }; + + _httpClient = new HttpClient(CreateHttpMessageHandler(_httpclientHandler, request.DownstreamReRoute)); + + _client = new HttpClientWrapper(_httpClient); + + return _client; + } + + public void Save() + { + _cacheHandlers.Set(_cacheKey, _client, TimeSpan.FromHours(24)); } private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler, DownstreamReRoute request) @@ -39,5 +75,12 @@ namespace Ocelot.Requester }); return httpMessageHandler; } + + private string GetCacheKey(DownstreamContext request) + { + var baseUrl = $"{request.DownstreamRequest.RequestUri.Scheme}://{request.DownstreamRequest.RequestUri.Authority}{request.DownstreamRequest.RequestUri.AbsolutePath}"; + + return baseUrl; + } } } diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index c6598903..4202f611 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -24,17 +24,15 @@ namespace Ocelot.Requester _factory = house; } - public async Task> GetResponse(DownstreamContext request) + public async Task> GetResponse(DownstreamContext context) { - var builder = new HttpClientBuilder(_factory); + var builder = new HttpClientBuilder(_factory, _cacheHandlers, _logger); - var cacheKey = GetCacheKey(request); - - var httpClient = GetHttpClient(cacheKey, builder, request); + var httpClient = builder.Create(context); try { - var response = await httpClient.SendAsync(request.DownstreamRequest); + var response = await httpClient.SendAsync(context.DownstreamRequest); return new OkResponse(response); } catch (TimeoutRejectedException exception) @@ -51,43 +49,8 @@ namespace Ocelot.Requester } finally { - _cacheHandlers.Set(cacheKey, httpClient, TimeSpan.FromHours(24)); + builder.Save(); } } - - private IHttpClient GetHttpClient(string cacheKey, IHttpClientBuilder builder, DownstreamContext request) - { - var httpClient = _cacheHandlers.Get(cacheKey); - - if (httpClient == null) - { - httpClient = builder.Create(request.DownstreamReRoute); - } - - return httpClient; - } - - private string GetCacheKey(DownstreamContext request) - { - var baseUrl = $"{request.DownstreamRequest.RequestUri.Scheme}://{request.DownstreamRequest.RequestUri.Authority}"; - - return baseUrl; - } - } - - public class ReRouteDelegatingHandler - where T : DelegatingHandler - { - public T DelegatingHandler { get; private set; } - } - - public class GlobalDelegatingHandler - { - public GlobalDelegatingHandler(DelegatingHandler delegatingHandler) - { - DelegatingHandler = delegatingHandler; - } - - public DelegatingHandler DelegatingHandler { get; private set; } } } diff --git a/src/Ocelot/Requester/HttpClientWrapper.cs b/src/Ocelot/Requester/HttpClientWrapper.cs index 21e74e48..b1f0345a 100644 --- a/src/Ocelot/Requester/HttpClientWrapper.cs +++ b/src/Ocelot/Requester/HttpClientWrapper.cs @@ -6,7 +6,7 @@ namespace Ocelot.Requester /// /// This class was made to make unit testing easier when HttpClient is used. /// - internal class HttpClientWrapper : IHttpClient + public class HttpClientWrapper : IHttpClient { public HttpClient Client { get; } diff --git a/src/Ocelot/Requester/IHttpClientBuilder.cs b/src/Ocelot/Requester/IHttpClientBuilder.cs index f10e55f5..cc8c6160 100644 --- a/src/Ocelot/Requester/IHttpClientBuilder.cs +++ b/src/Ocelot/Requester/IHttpClientBuilder.cs @@ -1,14 +1,10 @@ -using System.Net.Http; -using Ocelot.Configuration; +using Ocelot.Middleware; namespace Ocelot.Requester { public interface IHttpClientBuilder { - /// - /// Creates the - /// - /// - IHttpClient Create(DownstreamReRoute request); + IHttpClient Create(DownstreamContext request); + void Save(); } } diff --git a/src/Ocelot/Requester/IHttpRequester.cs b/src/Ocelot/Requester/IHttpRequester.cs index 5d9aa5dc..86f6209a 100644 --- a/src/Ocelot/Requester/IHttpRequester.cs +++ b/src/Ocelot/Requester/IHttpRequester.cs @@ -7,6 +7,6 @@ namespace Ocelot.Requester { public interface IHttpRequester { - Task> GetResponse(DownstreamContext request); + Task> GetResponse(DownstreamContext context); } } diff --git a/src/Ocelot/Requester/ReRouteDelegatingHandler.cs b/src/Ocelot/Requester/ReRouteDelegatingHandler.cs new file mode 100644 index 00000000..0a5c5472 --- /dev/null +++ b/src/Ocelot/Requester/ReRouteDelegatingHandler.cs @@ -0,0 +1,10 @@ +using System.Net.Http; + +namespace Ocelot.Requester +{ + public class ReRouteDelegatingHandler + where T : DelegatingHandler + { + public T DelegatingHandler { get; private set; } + } +} diff --git a/test/Ocelot.AcceptanceTests/HeaderTests.cs b/test/Ocelot.AcceptanceTests/HeaderTests.cs index 350e1e52..2bd3c2ab 100644 --- a/test/Ocelot.AcceptanceTests/HeaderTests.cs +++ b/test/Ocelot.AcceptanceTests/HeaderTests.cs @@ -16,6 +16,8 @@ namespace Ocelot.AcceptanceTests public class HeaderTests : IDisposable { private IWebHost _builder; + private string _cookieValue; + private int _count; private readonly Steps _steps; public HeaderTests() @@ -184,6 +186,125 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void request_should_reuse_cookies_with_cookie_container() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/sso/{everything}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 6774, + } + }, + UpstreamPathTemplate = "/sso/{everything}", + UpstreamHttpMethod = new List { "Get", "Post", "Options" }, + HttpHandlerOptions = new FileHttpHandlerOptions + { + UseCookieContainer = true + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6774", "/sso/test", 200)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseHeaderIs("Set-Cookie", "test=0; path=/")) + .And(x => _steps.GivenIAddCookieToMyRequest("test=1; path=/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void request_should_have_own_cookies_no_cookie_container() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/sso/{everything}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 6775, + } + }, + UpstreamPathTemplate = "/sso/{everything}", + UpstreamHttpMethod = new List { "Get", "Post", "Options" }, + HttpHandlerOptions = new FileHttpHandlerOptions + { + UseCookieContainer = false + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6775", "/sso/test", 200)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test")) + .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseHeaderIs("Set-Cookie", "test=0; path=/")) + .And(x => _steps.GivenIAddCookieToMyRequest("test=1; path=/")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + if(_count == 0) + { + context.Response.Cookies.Append("test", "0"); + _count++; + context.Response.StatusCode = statusCode; + return; + } + + if(context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue)) + { + if(cookieValue == "0" || headerValue == "test=1; path=/") + { + context.Response.StatusCode = statusCode; + return; + } + } + + context.Response.StatusCode = 500; + }); + }) + .Build(); + + _builder.Start(); + } + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey) { _builder = new WebHostBuilder() diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 0de7113d..2cb0f830 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -274,6 +274,11 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } + internal void GivenIAddCookieToMyRequest(string cookie) + { + _ocelotClient.DefaultRequestHeaders.Add("Set-Cookie", cookie); + } + /// /// 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. /// diff --git a/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs index b0b11bfc..ca22956c 100644 --- a/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs @@ -19,10 +19,10 @@ namespace Ocelot.UnitTests.Configuration } [Fact] - public void should_create_options_with_useCookie_and_allowAutoRedirect_true_as_default() + public void should_create_options_with_useCookie_false_and_allowAutoRedirect_true_as_default() { var fileReRoute = new FileReRoute(); - var expectedOptions = new HttpHandlerOptions(true, true, false); + var expectedOptions = new HttpHandlerOptions(false, false, false); this.Given(x => GivenTheFollowing(fileReRoute)) .When(x => WhenICreateHttpHandlerOptions()) @@ -61,12 +61,12 @@ namespace Ocelot.UnitTests.Configuration _httpHandlerOptions = _httpHandlerOptionsCreator.Create(_fileReRoute); } - private void ThenTheFollowingOptionsReturned(HttpHandlerOptions options) + private void ThenTheFollowingOptionsReturned(HttpHandlerOptions expected) { _httpHandlerOptions.ShouldNotBeNull(); - _httpHandlerOptions.AllowAutoRedirect.ShouldBe(options.AllowAutoRedirect); - _httpHandlerOptions.UseCookieContainer.ShouldBe(options.UseCookieContainer); - _httpHandlerOptions.UseTracing.ShouldBe(options.UseTracing); + _httpHandlerOptions.AllowAutoRedirect.ShouldBe(expected.AllowAutoRedirect); + _httpHandlerOptions.UseCookieContainer.ShouldBe(expected.UseCookieContainer); + _httpHandlerOptions.UseTracing.ShouldBe(expected.UseTracing); } } } diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index c4ab1341..7368fac8 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -1,9 +1,19 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; +using Ocelot.Logging; +using Ocelot.Middleware; using Ocelot.Requester; using Ocelot.Responses; using Shouldly; @@ -12,25 +22,37 @@ using Xunit; namespace Ocelot.UnitTests.Requester { - public class HttpClientBuilderTests + public class HttpClientBuilderTests : IDisposable { private readonly HttpClientBuilder _builder; private readonly Mock _factory; private IHttpClient _httpClient; private HttpResponseMessage _response; - private DownstreamReRoute _request; + private DownstreamContext _context; + private readonly Mock _cacheHandlers; + private Mock _logger; + private int _count; + private IWebHost _host; public HttpClientBuilderTests() { + _cacheHandlers = new Mock(); + _logger = new Mock(); _factory = new Mock(); - _builder = new HttpClientBuilder(_factory.Object); + _builder = new HttpClientBuilder(_factory.Object, _cacheHandlers.Object, _logger.Object); } [Fact] public void should_build_http_client() { + var reRoute = new DownstreamReRouteBuilder() + .WithIsQos(false) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)) + .WithReRouteKey("") + .Build(); + this.Given(x => GivenTheFactoryReturns()) - .And(x => GivenARequest()) + .And(x => GivenARequest(reRoute)) .When(x => WhenIBuild()) .Then(x => ThenTheHttpClientShouldNotBeNull()) .BDDfy(); @@ -39,6 +61,12 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_call_delegating_handlers_in_order() { + var reRoute = new DownstreamReRouteBuilder() + .WithIsQos(false) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)) + .WithReRouteKey("") + .Build(); + var fakeOne = new FakeDelegatingHandler(); var fakeTwo = new FakeDelegatingHandler(); @@ -49,7 +77,7 @@ namespace Ocelot.UnitTests.Requester }; this.Given(x => GivenTheFactoryReturns(handlers)) - .And(x => GivenARequest()) + .And(x => GivenARequest(reRoute)) .And(x => WhenIBuild()) .When(x => WhenICallTheClient()) .Then(x => ThenTheFakeAreHandledInOrder(fakeOne, fakeTwo)) @@ -57,12 +85,95 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } - private void GivenARequest() + [Fact] + public void should_re_use_cookies_from_container() { - var reRoute = new DownstreamReRouteBuilder().WithIsQos(false) - .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)).WithReRouteKey("").Build(); + var reRoute = new DownstreamReRouteBuilder() + .WithIsQos(false) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, true, false)) + .WithReRouteKey("") + .Build(); - _request = reRoute; + this.Given(_ => GivenADownstreamService()) + .And(_ => GivenARequest(reRoute)) + .And(_ => GivenTheFactoryReturnsNothing()) + .And(_ => WhenIBuild()) + .And(_ => WhenICallTheClient("http://localhost:5003")) + .And(_ => ThenTheCookieIsSet()) + .And(_ => GivenTheClientIsCached()) + .And(_ => WhenIBuild()) + .When(_ => WhenICallTheClient("http://localhost:5003")) + .Then(_ => ThenTheResponseIsOk()) + .BDDfy(); + } + + private void GivenTheClientIsCached() + { + _cacheHandlers.Setup(x => x.Get(It.IsAny())).Returns(_httpClient); + } + + private void ThenTheCookieIsSet() + { + _response.Headers.TryGetValues("Set-Cookie", out var test).ShouldBeTrue(); + } + + private void WhenICallTheClient(string url) + { + _response = _httpClient + .SendAsync(new HttpRequestMessage(HttpMethod.Get, url)) + .GetAwaiter() + .GetResult(); + } + + private void ThenTheResponseIsOk() + { + _response.StatusCode.ShouldBe(HttpStatusCode.OK); + } + + private void GivenADownstreamService() + { + _host = new WebHostBuilder() + .UseUrls("http://localhost:5003") + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.Run(async context => + { + if (_count == 0) + { + context.Response.Cookies.Append("test", "0"); + context.Response.StatusCode = 200; + _count++; + return; + } + if (_count == 1) + { + if (context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue)) + { + context.Response.StatusCode = 200; + return; + } + + context.Response.StatusCode = 500; + } + }); + }) + .Build(); + + _host.Start(); + } + + private void GivenARequest(DownstreamReRoute downstream) + { + var context = new DownstreamContext(new DefaultHttpContext()) + { + DownstreamReRoute = downstream, + DownstreamRequest = new HttpRequestMessage() { RequestUri = new Uri("http://localhost:5003") }, + }; + + _context = context; } private void ThenSomethingIsReturned() @@ -88,6 +199,14 @@ namespace Ocelot.UnitTests.Requester .Setup(x => x.Get(It.IsAny())) .Returns(new OkResponse>>(handlers)); } + private void GivenTheFactoryReturnsNothing() + { + var handlers = new List>(); + + _factory + .Setup(x => x.Get(It.IsAny())) + .Returns(new OkResponse>>(handlers)); + } private void GivenTheFactoryReturns(List> handlers) { @@ -98,12 +217,18 @@ namespace Ocelot.UnitTests.Requester private void WhenIBuild() { - _httpClient = _builder.Create(_request); + _httpClient = _builder.Create(_context); } private void ThenTheHttpClientShouldNotBeNull() { _httpClient.ShouldNotBeNull(); } + + public void Dispose() + { + _response?.Dispose(); + _host?.Dispose(); + } } } diff --git a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs index f2c1f3a0..bbf59692 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs @@ -35,7 +35,10 @@ namespace Ocelot.UnitTests.Requester .Setup(x => x.CreateLogger()) .Returns(_logger.Object); _cacheHandlers = new Mock(); - _httpClientRequester = new HttpClientHttpRequester(_loggerFactory.Object, _cacheHandlers.Object, _house.Object); + _httpClientRequester = new HttpClientHttpRequester( + _loggerFactory.Object, + _cacheHandlers.Object, + _house.Object); } [Fact] From 8a2f76d0c5c42d79a3d39045b574245f278877ec Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 17 Mar 2018 12:54:17 +0000 Subject: [PATCH 10/50] #268 fix flakey acceptance test (#282) * #268 added waiter to test, altho i wasn't able to replicate flakeyness with wait anyway! Hopefully this will be solid now! * #268 fixed a warning * #268 more code coverage --- .../ConsulFileConfigurationPoller.cs | 15 ++- .../Repository/IConsulPollerConfiguration.cs | 8 ++ .../InMemoryConsulPollerConfiguration.cs | 7 ++ .../DependencyInjection/OcelotBuilder.cs | 1 + .../Ocelot/Infrastructure}/Wait.cs | 4 +- .../Ocelot/Infrastructure}/Waiter.cs | 94 +++++++++---------- .../ConfigurationInConsulTests.cs | 30 +++--- .../ConsulFileConfigurationPollerTests.cs | 84 +++++++++++++++-- .../ServerHostedMiddlewareTest.cs | 68 -------------- 9 files changed, 168 insertions(+), 143 deletions(-) create mode 100644 src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs create mode 100644 src/Ocelot/Configuration/Repository/InMemoryConsulPollerConfiguration.cs rename {test/Ocelot.UnitTests => src/Ocelot/Infrastructure}/Wait.cs (82%) rename {test/Ocelot.UnitTests => src/Ocelot/Infrastructure}/Waiter.cs (93%) delete mode 100644 test/Ocelot.UnitTests/ServerHostedMiddlewareTest.cs diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs index a2780062..79efddca 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs +++ b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs @@ -17,10 +17,16 @@ namespace Ocelot.Configuration.Repository private string _previousAsJson; private readonly Timer _timer; private bool _polling; + private readonly IConsulPollerConfiguration _config; - public ConsulFileConfigurationPoller(IOcelotLoggerFactory factory, IFileConfigurationRepository repo, IFileConfigurationSetter setter) + public ConsulFileConfigurationPoller( + IOcelotLoggerFactory factory, + IFileConfigurationRepository repo, + IFileConfigurationSetter setter, + IConsulPollerConfiguration config) { _setter = setter; + _config = config; _logger = factory.CreateLogger(); _repo = repo; _previousAsJson = ""; @@ -30,11 +36,11 @@ namespace Ocelot.Configuration.Repository { return; } - + _polling = true; await Poll(); _polling = false; - }, null, 0, 1000); + }, null, 0, _config.Delay); } private async Task Poll() @@ -63,8 +69,7 @@ namespace Ocelot.Configuration.Repository /// /// We could do object comparison here but performance isnt really a problem. This might be an issue one day! /// - /// - /// + /// hash of the config private string ToJson(FileConfiguration config) { var currentHash = JsonConvert.SerializeObject(config); diff --git a/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs b/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs new file mode 100644 index 00000000..93003087 --- /dev/null +++ b/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs @@ -0,0 +1,8 @@ +namespace Ocelot.Configuration.Repository +{ + public interface IConsulPollerConfiguration + { + int Delay { get; } + } + + } diff --git a/src/Ocelot/Configuration/Repository/InMemoryConsulPollerConfiguration.cs b/src/Ocelot/Configuration/Repository/InMemoryConsulPollerConfiguration.cs new file mode 100644 index 00000000..9e411f76 --- /dev/null +++ b/src/Ocelot/Configuration/Repository/InMemoryConsulPollerConfiguration.cs @@ -0,0 +1,7 @@ +namespace Ocelot.Configuration.Repository +{ + public class InMemoryConsulPollerConfiguration : IConsulPollerConfiguration + { + public int Delay => 1000; + } +} diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 9e2e763b..eccab083 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -148,6 +148,7 @@ namespace Ocelot.DependencyInjection // We add this here so that we can always inject something into the factory for IoC.. _services.AddSingleton(); + _services.TryAddSingleton(); } public IOcelotAdministrationBuilder AddAdministration(string path, string secret) diff --git a/test/Ocelot.UnitTests/Wait.cs b/src/Ocelot/Infrastructure/Wait.cs similarity index 82% rename from test/Ocelot.UnitTests/Wait.cs rename to src/Ocelot/Infrastructure/Wait.cs index d8fd3a88..934e31e5 100644 --- a/test/Ocelot.UnitTests/Wait.cs +++ b/src/Ocelot/Infrastructure/Wait.cs @@ -1,4 +1,4 @@ -namespace Ocelot.UnitTests +namespace Ocelot.Infrastructure { public class Wait { @@ -7,4 +7,4 @@ namespace Ocelot.UnitTests return new Waiter(milliSeconds); } } -} \ No newline at end of file +} diff --git a/test/Ocelot.UnitTests/Waiter.cs b/src/Ocelot/Infrastructure/Waiter.cs similarity index 93% rename from test/Ocelot.UnitTests/Waiter.cs rename to src/Ocelot/Infrastructure/Waiter.cs index 8847c5b9..730bdbff 100644 --- a/test/Ocelot.UnitTests/Waiter.cs +++ b/src/Ocelot/Infrastructure/Waiter.cs @@ -1,47 +1,47 @@ -using System; -using System.Diagnostics; - -namespace Ocelot.UnitTests -{ - public class Waiter - { - private readonly int _milliSeconds; - - public Waiter(int milliSeconds) - { - _milliSeconds = milliSeconds; - } - - public bool Until(Func condition) - { - var stopwatch = Stopwatch.StartNew(); - var passed = false; - while (stopwatch.ElapsedMilliseconds < _milliSeconds) - { - if (condition.Invoke()) - { - passed = true; - break; - } - } - - return passed; - } - - public bool Until(Func condition) - { - var stopwatch = Stopwatch.StartNew(); - var passed = false; - while (stopwatch.ElapsedMilliseconds < _milliSeconds) - { - if (condition.Invoke()) - { - passed = true; - break; - } - } - - return passed; - } - } -} +using System; +using System.Diagnostics; + +namespace Ocelot.Infrastructure +{ + public class Waiter + { + private readonly int _milliSeconds; + + public Waiter(int milliSeconds) + { + _milliSeconds = milliSeconds; + } + + public bool Until(Func condition) + { + var stopwatch = Stopwatch.StartNew(); + var passed = false; + while (stopwatch.ElapsedMilliseconds < _milliSeconds) + { + if (condition.Invoke()) + { + passed = true; + break; + } + } + + return passed; + } + + public bool Until(Func condition) + { + var stopwatch = Stopwatch.StartNew(); + var passed = false; + while (stopwatch.ElapsedMilliseconds < _milliSeconds) + { + if (condition.Invoke()) + { + passed = true; + break; + } + } + + return passed; + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index 7c9ac239..797f340c 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -9,8 +9,10 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using Ocelot.Configuration.File; +using Shouldly; using TestStack.BDDfy; using Xunit; +using static Ocelot.Infrastructure.Wait; namespace Ocelot.AcceptanceTests { @@ -261,21 +263,27 @@ namespace Ocelot.AcceptanceTests .And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status")) .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .And(x => GivenTheConsulConfigurationIs(secondConsulConfig)) - .And(x => GivenIWaitForTheConfigToReplicateToOcelot()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .When(x => GivenTheConsulConfigurationIs(secondConsulConfig)) + .Then(x => ThenTheConfigIsUpdatedInOcelot()) .BDDfy(); } - private void GivenIWaitForTheConfigToReplicateToOcelot() + private void ThenTheConfigIsUpdatedInOcelot() { - var stopWatch = Stopwatch.StartNew(); - while (stopWatch.ElapsedMilliseconds < 10000) - { - //do nothing! - } + var result = WaitFor(20000).Until(() => { + try + { + _steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome"); + _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK); + _steps.ThenTheResponseBodyShouldBe("Hello from Laura"); + return true; + } + catch (Exception) + { + return false; + } + }); + result.ShouldBeTrue(); } private void GivenTheConsulConfigurationIs(FileConfiguration config) diff --git a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs b/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs index 4a6faba2..6499c4e0 100644 --- a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs @@ -1,16 +1,17 @@ using System; using System.Collections.Generic; -using System.Diagnostics; +using System.Threading; using Moq; using Ocelot.Configuration.File; using Ocelot.Configuration.Repository; using Ocelot.Configuration.Setter; using Ocelot.Logging; using Ocelot.Responses; +using Ocelot.UnitTests.Responder; using TestStack.BDDfy; using Xunit; using Shouldly; -using static Ocelot.UnitTests.Wait; +using static Ocelot.Infrastructure.Wait; namespace Ocelot.UnitTests.Configuration { @@ -21,6 +22,7 @@ namespace Ocelot.UnitTests.Configuration private Mock _repo; private Mock _setter; private FileConfiguration _fileConfig; + private Mock _config; public ConsulFileConfigurationPollerTests() { @@ -30,8 +32,10 @@ namespace Ocelot.UnitTests.Configuration _repo = new Mock(); _setter = new Mock(); _fileConfig = new FileConfiguration(); + _config = new Mock(); _repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse(_fileConfig)); - _poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object); + _config.Setup(x => x.Delay).Returns(10); + _poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object, _config.Object); } public void Dispose() @@ -42,7 +46,7 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void should_start() { - this.Given(x => ThenTheSetterIsCalled(_fileConfig)) + this.Given(x => ThenTheSetterIsCalled(_fileConfig, 1)) .BDDfy(); } @@ -65,22 +69,82 @@ namespace Ocelot.UnitTests.Configuration } }; - this.Given(x => WhenTheConfigIsChangedInConsul(newConfig)) - .Then(x => ThenTheSetterIsCalled(newConfig)) + this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 0)) + .Then(x => ThenTheSetterIsCalled(newConfig, 1)) .BDDfy(); } - private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig) + [Fact] + public void should_not_poll_if_already_polling() { - _repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse(newConfig)); + var newConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "test" + } + }, + } + } + }; + + this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 10)) + .Then(x => ThenTheSetterIsCalled(newConfig, 1)) + .BDDfy(); } - private void ThenTheSetterIsCalled(FileConfiguration fileConfig) + [Fact] + public void should_do_nothing_if_call_to_consul_fails() + { + var newConfig = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "test" + } + }, + } + } + }; + + this.Given(x => WhenConsulErrors()) + .Then(x => ThenTheSetterIsCalled(newConfig, 0)) + .BDDfy(); + } + + private void WhenConsulErrors() + { + _repo + .Setup(x => x.Get()) + .ReturnsAsync(new ErrorResponse(new AnyError())); + } + + private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig, int delay) + { + _repo + .Setup(x => x.Get()) + .Callback(() => Thread.Sleep(delay)) + .ReturnsAsync(new OkResponse(newConfig)); + } + + private void ThenTheSetterIsCalled(FileConfiguration fileConfig, int times) { var result = WaitFor(2000).Until(() => { try { - _setter.Verify(x => x.Set(fileConfig), Times.Once); + _setter.Verify(x => x.Set(fileConfig), Times.Exactly(times)); return true; } catch(Exception) diff --git a/test/Ocelot.UnitTests/ServerHostedMiddlewareTest.cs b/test/Ocelot.UnitTests/ServerHostedMiddlewareTest.cs deleted file mode 100644 index 15a2d673..00000000 --- a/test/Ocelot.UnitTests/ServerHostedMiddlewareTest.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Ocelot.UnitTests -{ - using System; - using System.IO; - using System.Net.Http; - using Microsoft.AspNetCore.TestHost; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.AspNetCore.Builder; - using Moq; - using Ocelot.Infrastructure.RequestData; - - public abstract class ServerHostedMiddlewareTest : IDisposable - { - protected TestServer Server { get; private set; } - protected HttpClient Client { get; private set; } - protected string Url { get; private set; } - protected HttpResponseMessage ResponseMessage { get; private set; } - protected Mock ScopedRepository { get; private set; } - - public ServerHostedMiddlewareTest() - { - Url = "http://localhost:51879"; - ScopedRepository = new Mock(); - } - - protected virtual void GivenTheTestServerIsConfigured() - { - var builder = new WebHostBuilder() - .ConfigureServices(x => GivenTheTestServerServicesAreConfigured(x)) - .UseUrls(Url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => GivenTheTestServerPipelineIsConfigured(app)); - - Server = new TestServer(builder); - Client = Server.CreateClient(); - } - - protected virtual void GivenTheTestServerServicesAreConfigured(IServiceCollection services) - { - // override this in your test fixture to set up service dependencies - } - - protected virtual void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app) - { - // override this in your test fixture to set up the test server pipeline - } - - protected void WhenICallTheMiddleware() - { - ResponseMessage = Client.GetAsync(Url).Result; - } - - protected void WhenICallTheMiddlewareWithTheRequestIdKey(string requestIdKey, string value) - { - Client.DefaultRequestHeaders.Add(requestIdKey, value); - ResponseMessage = Client.GetAsync(Url).Result; - } - - public void Dispose() - { - Client.Dispose(); - Server.Dispose(); - } - } -} From 0ab670a14327aa2d2ee1930173186d786d760f0f Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 17 Mar 2018 18:07:27 +0000 Subject: [PATCH 11/50] Feature/#52 (#283) * #52 test circle ci * #52 nicked some lads cake script * #52 put the mac build script back * #52 trying another lads circle CI thing doesnt use cake * #52 added test steps * #52 ports for linux build * #52 try travis mac build * #52 dont use build script * #52 dont use build script * #52 acceptance and int tests dont really work on mac...v strange? * #52 unique port for linux tests * #52 increase code coverage * #52 try using cake on linux for travis * #52 try using cake for mac and linux on travis * #52 dont run the acceptance and int tests on mac * #52 build.sh has lf line endings * #52 turns out crlf is OK for cake file..sigh * #52 not sure what return does in cake so wrapped in if just to see * #52 try use travis to work not run on mac * #52 dont need these references * #52 wrong property * #52 remove circle ci for linux and just use travis for all --- .circleci/config.yml | 17 ++++++ .travis.yml | 31 +++++++++++ README.md | 3 +- build.cake | 36 +++++++++++++ build.sh | 2 +- .../LoadBalancerTests.cs | 4 +- test/Ocelot.AcceptanceTests/QoSTests.cs | 4 +- .../OcelotPipelineExtensionsTests.cs | 53 +++++++++++++++++++ 8 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 .circleci/config.yml create mode 100644 .travis.yml create mode 100644 test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..8abd6ffd --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,17 @@ +version: 2 +jobs: + build: + working_directory: /temp + docker: + - image: microsoft/dotnet:sdk + environment: + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + steps: + - checkout + - run: dotnet restore + - run: dotnet build + - run: dotnet test ./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj + - run: dotnet test ./test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj + - run: dotnet test ./test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..ef0701aa --- /dev/null +++ b/.travis.yml @@ -0,0 +1,31 @@ +language: csharp +os: + - osx + - linux + +# Ubuntu 14.04 +sudo: required +dist: trusty + +# OS X 10.12 +osx_image: xcode9.2 + +mono: + - 4.4.2 + +dotnet: 2.1.4 + +before_install: + - git fetch --unshallow # Travis always does a shallow clone, but GitVersion needs the full history including branches and tags + - git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" + - git fetch origin + +script: + - ./build.sh + +cache: + directories: + - .packages + - tools/Addins + - tools/gitreleasemanager + - tools/GitVersion.CommandLine \ No newline at end of file diff --git a/README.md b/README.md index 6bbc77e0..ab3d89a5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ [](http://threemammals.com/ocelot) -[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb) +[![Build status](https://ci.appveyor.com/api/projects/status/r6sv51qx36sis1je?branch=develop&svg=true)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb) Windows (AppVeyor) +[![Build Status](https://travis-ci.org/ThreeMammals/Ocelot.svg?branch=develop)](https://travis-ci.org/ThreeMammals/Ocelot) Linux & OSX (Travis) [![Windows Build history](https://buildstats.info/appveyor/chart/TomPallister/ocelot-fcfpb?branch=develop&includeBuildsFromPullRequest=false)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb/history?branch=develop) diff --git a/build.cake b/build.cake index c732864b..f757228c 100644 --- a/build.cake +++ b/build.cake @@ -189,6 +189,24 @@ Task("RunAcceptanceTests") .IsDependentOn("Compile") .Does(() => { + if(TravisCI.IsRunningOnTravisCI) + { + Information( + @"Job: + JobId: {0} + JobNumber: {1} + OSName: {2}", + BuildSystem.TravisCI.Environment.Job.JobId, + BuildSystem.TravisCI.Environment.Job.JobNumber, + BuildSystem.TravisCI.Environment.Job.OSName + ); + + if(TravisCI.Environment.Job.OSName.ToLower() == "osx") + { + return; + } + } + var settings = new DotNetCoreTestSettings { Configuration = compileConfig, @@ -205,6 +223,24 @@ Task("RunIntegrationTests") .IsDependentOn("Compile") .Does(() => { + if(TravisCI.IsRunningOnTravisCI) + { + Information( + @"Job: + JobId: {0} + JobNumber: {1} + OSName: {2}", + BuildSystem.TravisCI.Environment.Job.JobId, + BuildSystem.TravisCI.Environment.Job.JobNumber, + BuildSystem.TravisCI.Environment.Job.OSName + ); + + if(TravisCI.Environment.Job.OSName.ToLower() == "osx") + { + return; + } + } + var settings = new DotNetCoreTestSettings { Configuration = compileConfig, diff --git a/build.sh b/build.sh index 04731adf..5b3d6020 100755 --- a/build.sh +++ b/build.sh @@ -98,4 +98,4 @@ 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 +fi diff --git a/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs b/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs index d67e7e41..f60008bd 100644 --- a/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs +++ b/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs @@ -31,7 +31,7 @@ namespace Ocelot.AcceptanceTests public void should_use_service_discovery_and_load_balance_request() { var downstreamServiceOneUrl = "http://localhost:50881"; - var downstreamServiceTwoUrl = "http://localhost:50882"; + var downstreamServiceTwoUrl = "http://localhost:50892"; var configuration = new FileConfiguration { @@ -54,7 +54,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 50882 + Port = 50892 } } } diff --git a/test/Ocelot.AcceptanceTests/QoSTests.cs b/test/Ocelot.AcceptanceTests/QoSTests.cs index d11721b8..5e82ba3d 100644 --- a/test/Ocelot.AcceptanceTests/QoSTests.cs +++ b/test/Ocelot.AcceptanceTests/QoSTests.cs @@ -41,7 +41,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51872, + Port = 51892, } }, UpstreamPathTemplate = "/", @@ -56,7 +56,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51872", "Hello from Laura")) + this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51892", "Hello from Laura")) .Given(x => _steps.GivenThereIsAConfiguration(configuration)) .Given(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) diff --git a/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs new file mode 100644 index 00000000..d61124ba --- /dev/null +++ b/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs @@ -0,0 +1,53 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; +using Ocelot.Middleware.Pipeline; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Middleware +{ + public class OcelotPipelineExtensionsTests + { + private OcelotPipelineBuilder _builder; + private OcelotRequestDelegate _handlers; + + [Fact] + public void should_set_up_pipeline() + { + this.Given(_ => GivenTheDepedenciesAreSetUp()) + .When(_ => WhenIBuild()) + .Then(_ => ThenThePipelineIsBuilt()) + .BDDfy(); + } + + private void ThenThePipelineIsBuilt() + { + _handlers.ShouldNotBeNull(); + } + + private void WhenIBuild() + { + _handlers = _builder.BuildOcelotPipeline(new OcelotPipelineConfiguration()); + } + + private void GivenTheDepedenciesAreSetUp() + { + IConfigurationBuilder test = new ConfigurationBuilder(); + var root = test.Build(); + var services = new ServiceCollection(); + services.AddSingleton(root); + services.AddOcelot(); + var provider = services.BuildServiceProvider(); + _builder = new OcelotPipelineBuilder(provider); + } + } +} \ No newline at end of file From 978b0a43a08d717f4982c89312a591dd971ac0a8 Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sat, 17 Mar 2018 18:09:47 +0000 Subject: [PATCH 12/50] #52 remove circle ci script --- .circleci/config.yml | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 8abd6ffd..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: 2 -jobs: - build: - working_directory: /temp - docker: - - image: microsoft/dotnet:sdk - environment: - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - steps: - - checkout - - run: dotnet restore - - run: dotnet build - - run: dotnet test ./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj - - run: dotnet test ./test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj - - run: dotnet test ./test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj - From b51df71d7bd2c532323fc4fc1d4fbba6afbb76bf Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 18 Mar 2018 14:58:39 +0000 Subject: [PATCH 13/50] #280 Add headers to response (#286) * #280 can now add response headers inc trace id, now need to consolidate the header place holder stuff * #280 changed port for linux tests * #280 lots of hacking around to handle errors and consolidate placeholders into one class --- docs/features/headerstransformation.rst | 29 +++- .../Builder/DownstreamReRouteBuilder.cs | 12 +- .../Creator/FileOcelotConfigurationCreator.cs | 1 + .../Creator/HeaderFindAndReplaceCreator.cs | 57 +++++-- .../Creator/HeaderTransformations.cs | 23 ++- src/Ocelot/Configuration/DownstreamReRoute.cs | 6 +- .../DependencyInjection/OcelotBuilder.cs | 3 + src/Ocelot/Errors/OcelotErrorCode.cs | 3 +- src/Ocelot/Headers/AddHeadersToRequest.cs | 1 + src/Ocelot/Headers/AddHeadersToResponse.cs | 44 ++++++ .../Headers/HttpResponseHeaderReplacer.cs | 27 ++-- src/Ocelot/Headers/IAddHeadersToRequest.cs | 2 + src/Ocelot/Headers/IAddHeadersToResponse.cs | 11 ++ .../HttpHeadersTransformationMiddleware.cs | 7 +- .../CouldNotFindPlaceholderError.cs | 12 ++ src/Ocelot/Infrastructure/IPlaceholders.cs | 11 ++ src/Ocelot/Infrastructure/Placeholders.cs | 70 +++++++++ .../Requester/OcelotHttpTracingHandler.cs | 19 ++- src/Ocelot/Requester/TracingHandlerFactory.cs | 9 +- .../ButterflyTracingTests.cs | 58 ++++++- test/Ocelot.AcceptanceTests/Steps.cs | 6 + .../ConsulFileConfigurationPollerTests.cs | 4 +- .../FileConfigurationCreatorTests.cs | 5 +- .../HeaderFindAndReplaceCreatorTests.cs | 83 +++++++++- .../Headers/AddHeadersToResponseTests.cs | 148 ++++++++++++++++++ ...ttpHeadersTransformationMiddlewareTests.cs | 11 +- .../HttpResponseHeaderReplacerTests.cs | 12 +- .../IScopedRequestDataRepository.cs | 0 .../Infrastructure/PlaceholdersTests.cs | 79 ++++++++++ .../Requester/TracingHandlerFactoryTests.cs | 5 +- .../ErrorsToHttpStatusCodeMapperTests.cs | 2 +- 31 files changed, 700 insertions(+), 60 deletions(-) create mode 100644 src/Ocelot/Headers/AddHeadersToResponse.cs create mode 100644 src/Ocelot/Headers/IAddHeadersToResponse.cs create mode 100644 src/Ocelot/Infrastructure/CouldNotFindPlaceholderError.cs create mode 100644 src/Ocelot/Infrastructure/IPlaceholders.cs create mode 100644 src/Ocelot/Infrastructure/Placeholders.cs create mode 100644 test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs create mode 100644 test/Ocelot.UnitTests/Infrastructure/IScopedRequestDataRepository.cs create mode 100644 test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs diff --git a/docs/features/headerstransformation.rst b/docs/features/headerstransformation.rst index 5063c1b7..6a333141 100644 --- a/docs/features/headerstransformation.rst +++ b/docs/features/headerstransformation.rst @@ -3,8 +3,32 @@ Headers Transformation Ocelot allows the user to transform headers pre and post downstream request. At the moment Ocelot only supports find and replace. This feature was requested `GitHub #190 `_ and I decided that it was going to be useful in various ways. -Syntax -^^^^^^ +Add to Response +^^^^^^^^^^^^^^^ + +This feature was requested in `GitHub #280 `_. I have only implemented +for responses but could add for requests in the future. + +If you want to add a header to your downstream response please add the following to a ReRoute in configuration.json.. + +.. code-block:: json + + "DownstreamHeaderTransform": { + "Uncle": "Bob" + }, + +In the example above a header with the key Uncle and value Bob would be returned by Ocelot when requesting the specific ReRoute. + +If you want to return the Butterfly APM trace id then do something like the following.. + +.. code-block:: json + + "DownstreamHeaderTransform": { + "AnyKey": "{TraceId}" + }, + +Find and Replace +^^^^^^^^^^^^^^^^ In order to transform a header first we specify the header key and then the type of transform we want e.g. @@ -43,6 +67,7 @@ Ocelot allows placeholders that can be used in header transformation. {BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value. {DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment. +{TraceId} - This will use the Butterfly APM Trace Id. This only works for DownstreamHeaderTransform at the moment. Handling 302 Redirects ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs index 0e28aaa7..d6dc0f34 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Net.Http; using Ocelot.Values; using System.Linq; +using Ocelot.Configuration.Creator; namespace Ocelot.Configuration.Builder { @@ -37,11 +38,13 @@ namespace Ocelot.Configuration.Builder private string _upstreamHost; private string _key; private List _delegatingHandlers; + private List _addHeadersToDownstream; public DownstreamReRouteBuilder() { _downstreamAddresses = new List(); _delegatingHandlers = new List(); + _addHeadersToDownstream = new List(); } public DownstreamReRouteBuilder WithDownstreamAddresses(List downstreamAddresses) @@ -224,6 +227,12 @@ namespace Ocelot.Configuration.Builder return this; } + public DownstreamReRouteBuilder WithAddHeadersToDownstream(List addHeadersToDownstream) + { + _addHeadersToDownstream = addHeadersToDownstream; + return this; + } + public DownstreamReRoute Build() { return new DownstreamReRoute( @@ -253,7 +262,8 @@ namespace Ocelot.Configuration.Builder _authenticationOptions, new PathTemplate(_downstreamPathTemplate), _reRouteKey, - _delegatingHandlers); + _delegatingHandlers, + _addHeadersToDownstream); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 94cde717..1036f8df 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -213,6 +213,7 @@ namespace Ocelot.Configuration.Creator .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) .WithUpstreamHost(fileReRoute.UpstreamHost) .WithDelegatingHandlers(fileReRoute.DelegatingHandlers) + .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) .Build(); return reRoute; diff --git a/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs b/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs index 7ab33e90..4ff87d2f 100644 --- a/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs +++ b/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs @@ -1,20 +1,22 @@ using System; using System.Collections.Generic; using Ocelot.Configuration.File; +using Ocelot.Infrastructure; +using Ocelot.Logging; using Ocelot.Middleware; +using Ocelot.Responses; namespace Ocelot.Configuration.Creator { public class HeaderFindAndReplaceCreator : IHeaderFindAndReplaceCreator { - private IBaseUrlFinder _finder; - private readonly Dictionary> _placeholders; + private IPlaceholders _placeholders; + private IOcelotLogger _logger; - public HeaderFindAndReplaceCreator(IBaseUrlFinder finder) + public HeaderFindAndReplaceCreator(IPlaceholders placeholders, IOcelotLoggerFactory factory) { - _finder = finder; - _placeholders = new Dictionary>(); - _placeholders.Add("{BaseUrl}", () => _finder.Find()); + _logger = factory.CreateLogger();; + _placeholders = placeholders; } public HeaderTransformations Create(FileReRoute fileReRoute) @@ -24,21 +26,43 @@ namespace Ocelot.Configuration.Creator foreach(var input in fileReRoute.UpstreamHeaderTransform) { var hAndr = Map(input); - upstream.Add(hAndr); + if(!hAndr.IsError) + { + upstream.Add(hAndr.Data); + } + else + { + _logger.LogError($"Unable to add UpstreamHeaderTransform {input.Key}: {input.Value}"); + } } var downstream = new List(); + var addHeadersToDownstream = new List(); foreach(var input in fileReRoute.DownstreamHeaderTransform) { - var hAndr = Map(input); - downstream.Add(hAndr); + if(input.Value.Contains(",")) + { + var hAndr = Map(input); + if(!hAndr.IsError) + { + downstream.Add(hAndr.Data); + } + else + { + _logger.LogError($"Unable to add DownstreamHeaderTransform {input.Key}: {input.Value}"); + } + } + else + { + addHeadersToDownstream.Add(new AddHeader(input.Key, input.Value)); + } } - return new HeaderTransformations(upstream, downstream); + return new HeaderTransformations(upstream, downstream, addHeadersToDownstream); } - private HeaderFindAndReplace Map(KeyValuePair input) + private Response Map(KeyValuePair input) { var findAndReplace = input.Value.Split(","); @@ -51,16 +75,19 @@ namespace Ocelot.Configuration.Creator var placeholder = replace.Substring(startOfPlaceholder, startOfPlaceholder + (endOfPlaceholder + 1)); - if(_placeholders.ContainsKey(placeholder)) + var value = _placeholders.Get(placeholder); + + if(value.IsError) { - var value = _placeholders[placeholder].Invoke(); - replace = replace.Replace(placeholder, value); + return new ErrorResponse(value.Errors); } + + replace = replace.Replace(placeholder, value.Data); } var hAndr = new HeaderFindAndReplace(input.Key, findAndReplace[0], replace, 0); - return hAndr; + return new OkResponse(hAndr); } } } diff --git a/src/Ocelot/Configuration/Creator/HeaderTransformations.cs b/src/Ocelot/Configuration/Creator/HeaderTransformations.cs index 55e1e3b9..72d307e5 100644 --- a/src/Ocelot/Configuration/Creator/HeaderTransformations.cs +++ b/src/Ocelot/Configuration/Creator/HeaderTransformations.cs @@ -4,14 +4,31 @@ namespace Ocelot.Configuration.Creator { public class HeaderTransformations { - public HeaderTransformations(List upstream, List downstream) + public HeaderTransformations( + List upstream, + List downstream, + List addHeader) { + AddHeadersToDownstream = addHeader; Upstream = upstream; Downstream = downstream; } - public List Upstream {get;private set;} + public List Upstream { get; private set; } - public List Downstream {get;private set;} + public List Downstream { get; private set; } + public List AddHeadersToDownstream {get;private set;} + } + + public class AddHeader + { + public AddHeader(string key, string value) + { + this.Key = key; + this.Value = value; + + } + public string Key { get; private set; } + public string Value { get; private set; } } } diff --git a/src/Ocelot/Configuration/DownstreamReRoute.cs b/src/Ocelot/Configuration/DownstreamReRoute.cs index 6c9aa1dd..00accc07 100644 --- a/src/Ocelot/Configuration/DownstreamReRoute.cs +++ b/src/Ocelot/Configuration/DownstreamReRoute.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Ocelot.Configuration.Creator; using Ocelot.Values; namespace Ocelot.Configuration @@ -32,8 +33,10 @@ namespace Ocelot.Configuration AuthenticationOptions authenticationOptions, PathTemplate downstreamPathTemplate, string reRouteKey, - List delegatingHandlers) + List delegatingHandlers, + List addHeadersToDownstream) { + AddHeadersToDownstream = addHeadersToDownstream; DelegatingHandlers = delegatingHandlers; Key = key; UpstreamPathTemplate = upstreamPathTemplate; @@ -90,5 +93,6 @@ namespace Ocelot.Configuration public PathTemplate DownstreamPathTemplate { get; private set; } public string ReRouteKey { get; private set; } public List DelegatingHandlers {get;private set;} + public List AddHeadersToDownstream {get;private set;} } } diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index eccab083..8ccb1459 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -52,6 +52,7 @@ namespace Ocelot.DependencyInjection using System.Linq; using System.Net.Http; using Butterfly.Client.AspNetCore; + using Ocelot.Infrastructure; public class OcelotBuilder : IOcelotBuilder { @@ -149,6 +150,8 @@ namespace Ocelot.DependencyInjection // We add this here so that we can always inject something into the factory for IoC.. _services.AddSingleton(); _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); } public IOcelotAdministrationBuilder AddAdministration(string path, string secret) diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index b976e29c..ec44ae23 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -34,6 +34,7 @@ RateLimitOptionsError, PathTemplateDoesntStartWithForwardSlash, FileValidationFailedError, - UnableToFindDelegatingHandlerProviderError + UnableToFindDelegatingHandlerProviderError, + CouldNotFindPlaceholderError } } diff --git a/src/Ocelot/Headers/AddHeadersToRequest.cs b/src/Ocelot/Headers/AddHeadersToRequest.cs index c8ec8cf3..f61b1ac3 100644 --- a/src/Ocelot/Headers/AddHeadersToRequest.cs +++ b/src/Ocelot/Headers/AddHeadersToRequest.cs @@ -4,6 +4,7 @@ using Ocelot.Configuration; using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Responses; using System.Net.Http; +using Ocelot.Configuration.Creator; namespace Ocelot.Headers { diff --git a/src/Ocelot/Headers/AddHeadersToResponse.cs b/src/Ocelot/Headers/AddHeadersToResponse.cs new file mode 100644 index 00000000..4c6f48ce --- /dev/null +++ b/src/Ocelot/Headers/AddHeadersToResponse.cs @@ -0,0 +1,44 @@ +namespace Ocelot.Headers +{ + using System; + using System.Collections.Generic; + using System.Net.Http; + using Ocelot.Configuration.Creator; + using Ocelot.Infrastructure; + using Ocelot.Infrastructure.RequestData; + using Ocelot.Logging; + + public class AddHeadersToResponse : IAddHeadersToResponse + { + private IPlaceholders _placeholders; + private IOcelotLogger _logger; + + public AddHeadersToResponse(IPlaceholders placeholders, IOcelotLoggerFactory factory) + { + _logger = factory.CreateLogger(); + _placeholders = placeholders; + } + public void Add(List addHeaders, HttpResponseMessage response) + { + foreach(var add in addHeaders) + { + if(add.Value.StartsWith('{') && add.Value.EndsWith('}')) + { + var value = _placeholders.Get(add.Value); + + if(value.IsError) + { + _logger.LogError($"Unable to add header to response {add.Key}: {add.Value}"); + continue; + } + + response.Headers.TryAddWithoutValidation(add.Key, value.Data); + } + else + { + response.Headers.TryAddWithoutValidation(add.Key, add.Value); + } + } + } + } +} diff --git a/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs b/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs index cb45ffc3..fc3e8e4c 100644 --- a/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs +++ b/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using Ocelot.Configuration; +using Ocelot.Infrastructure; using Ocelot.Infrastructure.Extensions; using Ocelot.Responses; @@ -10,24 +11,14 @@ namespace Ocelot.Headers { public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer { - private Dictionary> _placeholders; + private IPlaceholders _placeholders; - public HttpResponseHeaderReplacer() + public HttpResponseHeaderReplacer(IPlaceholders placeholders) { - _placeholders = new Dictionary>(); - _placeholders.Add("{DownstreamBaseUrl}", x => { - var downstreamUrl = $"{x.RequestUri.Scheme}://{x.RequestUri.Host}"; - - if(x.RequestUri.Port != 80 && x.RequestUri.Port != 443) - { - downstreamUrl = $"{downstreamUrl}:{x.RequestUri.Port}"; - } - - return $"{downstreamUrl}/"; - }); + _placeholders = placeholders; } - public Response Replace(HttpResponseMessage response, List fAndRs, HttpRequestMessage httpRequestMessage) + public Response Replace(HttpResponseMessage response, List fAndRs, HttpRequestMessage request) { foreach (var f in fAndRs) { @@ -35,11 +26,13 @@ namespace Ocelot.Headers if(response.Headers.TryGetValues(f.Key, out var values)) { //check to see if it is a placeholder in the find... - if(_placeholders.TryGetValue(f.Find, out var replacePlaceholder)) + var placeholderValue = _placeholders.Get(f.Find, request); + + if(!placeholderValue.IsError) { //if it is we need to get the value of the placeholder - var find = replacePlaceholder(httpRequestMessage); - var replaced = values.ToList()[f.Index].Replace(find, f.Replace.LastCharAsForwardSlash()); + //var find = replacePlaceholder(httpRequestMessage); + var replaced = values.ToList()[f.Index].Replace(placeholderValue.Data, f.Replace.LastCharAsForwardSlash()); response.Headers.Remove(f.Key); response.Headers.Add(f.Key, replaced); } diff --git a/src/Ocelot/Headers/IAddHeadersToRequest.cs b/src/Ocelot/Headers/IAddHeadersToRequest.cs index 387b3f01..fed2407c 100644 --- a/src/Ocelot/Headers/IAddHeadersToRequest.cs +++ b/src/Ocelot/Headers/IAddHeadersToRequest.cs @@ -4,6 +4,8 @@ using System.Net.Http; using Ocelot.Configuration; + using Ocelot.Configuration.Creator; + using Ocelot.Infrastructure.RequestData; using Ocelot.Responses; public interface IAddHeadersToRequest diff --git a/src/Ocelot/Headers/IAddHeadersToResponse.cs b/src/Ocelot/Headers/IAddHeadersToResponse.cs new file mode 100644 index 00000000..51f23758 --- /dev/null +++ b/src/Ocelot/Headers/IAddHeadersToResponse.cs @@ -0,0 +1,11 @@ +namespace Ocelot.Headers +{ + using System.Collections.Generic; + using System.Net.Http; + using Ocelot.Configuration.Creator; + + public interface IAddHeadersToResponse + { + void Add(List addHeaders, HttpResponseMessage response); + } +} diff --git a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs index f4281a23..b09b40f8 100644 --- a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs @@ -13,12 +13,15 @@ namespace Ocelot.Headers.Middleware private readonly IOcelotLogger _logger; private readonly IHttpContextRequestHeaderReplacer _preReplacer; private readonly IHttpResponseHeaderReplacer _postReplacer; + private readonly IAddHeadersToResponse _addHeaders; public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, IHttpContextRequestHeaderReplacer preReplacer, - IHttpResponseHeaderReplacer postReplacer) + IHttpResponseHeaderReplacer postReplacer, + IAddHeadersToResponse addHeaders) { + _addHeaders = addHeaders; _next = next; _postReplacer = postReplacer; _preReplacer = preReplacer; @@ -37,6 +40,8 @@ namespace Ocelot.Headers.Middleware var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace; _postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest); + + _addHeaders.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse); } } } diff --git a/src/Ocelot/Infrastructure/CouldNotFindPlaceholderError.cs b/src/Ocelot/Infrastructure/CouldNotFindPlaceholderError.cs new file mode 100644 index 00000000..c6214e83 --- /dev/null +++ b/src/Ocelot/Infrastructure/CouldNotFindPlaceholderError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.Infrastructure +{ + public class CouldNotFindPlaceholderError : Error + { + public CouldNotFindPlaceholderError(string placeholder) + : base($"Unable to find placeholder called {placeholder}", OcelotErrorCode.CouldNotFindPlaceholderError) + { + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Infrastructure/IPlaceholders.cs b/src/Ocelot/Infrastructure/IPlaceholders.cs new file mode 100644 index 00000000..f95fb8b8 --- /dev/null +++ b/src/Ocelot/Infrastructure/IPlaceholders.cs @@ -0,0 +1,11 @@ +using System.Net.Http; +using Ocelot.Responses; + +namespace Ocelot.Infrastructure +{ + public interface IPlaceholders + { + Response Get(string key); + Response Get(string key, HttpRequestMessage request); + } +} \ No newline at end of file diff --git a/src/Ocelot/Infrastructure/Placeholders.cs b/src/Ocelot/Infrastructure/Placeholders.cs new file mode 100644 index 00000000..b00f54fa --- /dev/null +++ b/src/Ocelot/Infrastructure/Placeholders.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Middleware; +using Ocelot.Responses; + +namespace Ocelot.Infrastructure +{ + public class Placeholders : IPlaceholders + { + private Dictionary>> _placeholders; + private Dictionary> _requestPlaceholders; + private readonly IBaseUrlFinder _finder; + private readonly IRequestScopedDataRepository _repo; + + public Placeholders(IBaseUrlFinder finder, IRequestScopedDataRepository repo) + { + _repo = repo; + _finder = finder; + _placeholders = new Dictionary>>(); + _placeholders.Add("{BaseUrl}", () => new OkResponse(_finder.Find())); + _placeholders.Add("{TraceId}", () => { + var traceId = _repo.Get("TraceId"); + if(traceId.IsError) + { + return new ErrorResponse(traceId.Errors); + } + + return new OkResponse(traceId.Data); + }); + + _requestPlaceholders = new Dictionary>(); + _requestPlaceholders.Add("{DownstreamBaseUrl}", x => { + var downstreamUrl = $"{x.RequestUri.Scheme}://{x.RequestUri.Host}"; + + if(x.RequestUri.Port != 80 && x.RequestUri.Port != 443) + { + downstreamUrl = $"{downstreamUrl}:{x.RequestUri.Port}"; + } + + return $"{downstreamUrl}/"; + }); + } + + public Response Get(string key) + { + if(_placeholders.ContainsKey(key)) + { + var response = _placeholders[key].Invoke(); + if(!response.IsError) + { + return new OkResponse(response.Data); + } + } + + return new ErrorResponse(new CouldNotFindPlaceholderError(key)); + } + + public Response Get(string key, HttpRequestMessage request) + { + if(_requestPlaceholders.ContainsKey(key)) + { + return new OkResponse(_requestPlaceholders[key].Invoke(request)); + } + + return new ErrorResponse(new CouldNotFindPlaceholderError(key)); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Requester/OcelotHttpTracingHandler.cs b/src/Ocelot/Requester/OcelotHttpTracingHandler.cs index 33bd4caf..9de364ac 100644 --- a/src/Ocelot/Requester/OcelotHttpTracingHandler.cs +++ b/src/Ocelot/Requester/OcelotHttpTracingHandler.cs @@ -5,26 +5,37 @@ using System.Threading; using System.Threading.Tasks; using Butterfly.Client.Tracing; using Butterfly.OpenTracing; +using Ocelot.Infrastructure.RequestData; namespace Ocelot.Requester { public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler { private readonly IServiceTracer _tracer; + private readonly IRequestScopedDataRepository _repo; private const string prefix_spanId = "ot-spanId"; - public OcelotHttpTracingHandler(IServiceTracer tracer, HttpMessageHandler httpMessageHandler = null) + public OcelotHttpTracingHandler( + IServiceTracer tracer, + IRequestScopedDataRepository repo, + HttpMessageHandler httpMessageHandler = null) { + _repo = repo; _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer)); InnerHandler = httpMessageHandler ?? new HttpClientHandler(); } - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + protected override Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) { return _tracer.ChildTraceAsync($"httpclient {request.Method}", DateTimeOffset.UtcNow, span => TracingSendAsync(span, request, cancellationToken)); } - protected virtual async Task TracingSendAsync(ISpan span, HttpRequestMessage request, CancellationToken cancellationToken) + protected virtual async Task TracingSendAsync( + ISpan span, + HttpRequestMessage request, + CancellationToken cancellationToken) { IEnumerable traceIdVals = null; if (request.Headers.TryGetValues(prefix_spanId, out traceIdVals)) @@ -33,6 +44,8 @@ namespace Ocelot.Requester request.Headers.TryAddWithoutValidation(prefix_spanId, span.SpanContext.SpanId); } + _repo.Add("TraceId", span.SpanContext.TraceId); + span.Tags.Client().Component("HttpClient") .HttpMethod(request.Method.Method) .HttpUrl(request.RequestUri.OriginalString) diff --git a/src/Ocelot/Requester/TracingHandlerFactory.cs b/src/Ocelot/Requester/TracingHandlerFactory.cs index 5cb72a79..b514ca18 100644 --- a/src/Ocelot/Requester/TracingHandlerFactory.cs +++ b/src/Ocelot/Requester/TracingHandlerFactory.cs @@ -1,20 +1,25 @@ using Butterfly.Client.Tracing; using Butterfly.OpenTracing; +using Ocelot.Infrastructure.RequestData; namespace Ocelot.Requester { public class TracingHandlerFactory : ITracingHandlerFactory { private readonly IServiceTracer _tracer; + private readonly IRequestScopedDataRepository _repo; - public TracingHandlerFactory(IServiceTracer tracer) + public TracingHandlerFactory( + IServiceTracer tracer, + IRequestScopedDataRepository repo) { + _repo = repo; _tracer = tracer; } public ITracingHandler Get() { - return new OcelotHttpTracingHandler(_tracer); + return new OcelotHttpTracingHandler(_tracer, _repo); } } diff --git a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs index a7411ebe..d634e617 100644 --- a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs +++ b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs @@ -70,7 +70,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51888, + Port = 51388, } }, UpstreamPathTemplate = "/api002/values", @@ -92,7 +92,7 @@ namespace Ocelot.AcceptanceTests var butterflyUrl = "http://localhost:9618"; this.Given(x => GivenServiceOneIsRunning("http://localhost:51887", "/api/values", 200, "Hello from Laura", butterflyUrl)) - .And(x => GivenServiceTwoIsRunning("http://localhost:51888", "/api/values", 200, "Hello from Tom", butterflyUrl)) + .And(x => GivenServiceTwoIsRunning("http://localhost:51388", "/api/values", 200, "Hello from Tom", butterflyUrl)) .And(x => GivenFakeButterfly(butterflyUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl)) @@ -109,6 +109,60 @@ namespace Ocelot.AcceptanceTests commandOnAllStateMachines.ShouldBeTrue(); } + [Fact] + public void should_return_tracing_header() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/values", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51387, + } + }, + UpstreamPathTemplate = "/api001/values", + UpstreamHttpMethod = new List { "Get" }, + HttpHandlerOptions = new FileHttpHandlerOptions + { + UseTracing = true + }, + QoSOptions = new FileQoSOptions + { + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak = 10, + TimeoutValue = 5000 + }, + DownstreamHeaderTransform = new Dictionary() + { + {"Trace-Id", "{TraceId}"}, + {"Tom", "Laura"} + } + } + } + }; + + var butterflyUrl = "http://localhost:9618"; + + this.Given(x => GivenServiceOneIsRunning("http://localhost:51387", "/api/values", 200, "Hello from Laura", butterflyUrl)) + .And(x => GivenFakeButterfly(butterflyUrl)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(x => _steps.ThenTheTraceHeaderIsSet("Trace-Id")) + .And(x => _steps.ThenTheResponseHeaderIs("Tom", "Laura")) + .BDDfy(); + } + private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody, string butterflyUrl) { _serviceOneBuilder = new WebHostBuilder() diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 2cb0f830..1ee6f9bc 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -318,6 +318,12 @@ namespace Ocelot.AcceptanceTests header.First().ShouldBe(value); } + public void ThenTheTraceHeaderIsSet(string key) + { + var header = _response.Headers.GetValues(key); + header.First().ShouldNotBeNullOrEmpty(); + } + public void GivenOcelotIsRunningUsingJsonSerializedCache() { _webHostBuilder = new WebHostBuilder(); diff --git a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs b/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs index 6499c4e0..7393b631 100644 --- a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs @@ -34,10 +34,10 @@ namespace Ocelot.UnitTests.Configuration _fileConfig = new FileConfiguration(); _config = new Mock(); _repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse(_fileConfig)); - _config.Setup(x => x.Delay).Returns(10); + _config.Setup(x => x.Delay).Returns(100); _poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object, _config.Object); } - + public void Dispose() { _poller.Dispose(); diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 79aff9ed..ce1d6989 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -826,7 +826,8 @@ namespace Ocelot.UnitTests.Configuration result.DownstreamReRoute[0].ClaimsToHeaders.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToHeaders.Count); result.DownstreamReRoute[0].ClaimsToQueries.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToQueries.Count); result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey); - result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers); + result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers); + result.DownstreamReRoute[0].AddHeadersToDownstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToDownstream); } } @@ -909,7 +910,7 @@ namespace Ocelot.UnitTests.Configuration private void GivenTheHeaderFindAndReplaceCreatorReturns() { - _headerFindAndReplaceCreator.Setup(x => x.Create(It.IsAny())).Returns(new HeaderTransformations(new List(), new List())); + _headerFindAndReplaceCreator.Setup(x => x.Create(It.IsAny())).Returns(new HeaderTransformations(new List(), new List(), new List())); } private void GivenTheFollowingIsReturned(ServiceProviderConfiguration serviceProviderConfiguration) diff --git a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs index 37ee8957..aa285e8d 100644 --- a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs @@ -5,7 +5,11 @@ using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; +using Ocelot.Infrastructure; +using Ocelot.Logging; using Ocelot.Middleware; +using Ocelot.Responses; +using Ocelot.UnitTests.Responder; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -17,12 +21,17 @@ namespace Ocelot.UnitTests.Configuration private HeaderFindAndReplaceCreator _creator; private FileReRoute _reRoute; private HeaderTransformations _result; - private Mock _finder; + private Mock _placeholders; + private Mock _factory; + private Mock _logger; public HeaderFindAndReplaceCreatorTests() { - _finder = new Mock(); - _creator = new HeaderFindAndReplaceCreator(_finder.Object); + _logger = new Mock(); + _factory = new Mock(); + _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _placeholders = new Mock(); + _creator = new HeaderFindAndReplaceCreator(_placeholders.Object, _factory.Object); } [Fact] @@ -84,6 +93,40 @@ namespace Ocelot.UnitTests.Configuration .BDDfy(); } + [Fact] + public void should_log_errors_and_not_add_headers() + { + var reRoute = new FileReRoute + { + DownstreamHeaderTransform = new Dictionary + { + {"Location", "http://www.bbc.co.uk/, {BaseUrl}"}, + }, + UpstreamHeaderTransform = new Dictionary + { + {"Location", "http://www.bbc.co.uk/, {BaseUrl}"}, + } + }; + + var expected = new List + { + }; + + this.Given(x => GivenTheReRoute(reRoute)) + .And(x => GivenTheBaseUrlErrors()) + .When(x => WhenICreate()) + .Then(x => ThenTheFollowingDownstreamIsReturned(expected)) + .And(x => ThenTheFollowingUpstreamIsReturned(expected)) + .And(x => ThenTheLoggerIsCalledCorrectly("Unable to add DownstreamHeaderTransform Location: http://www.bbc.co.uk/, {BaseUrl}")) + .And(x => ThenTheLoggerIsCalledCorrectly("Unable to add UpstreamHeaderTransform Location: http://www.bbc.co.uk/, {BaseUrl}")) + .BDDfy(); + } + + private void ThenTheLoggerIsCalledCorrectly(string message) + { + _logger.Verify(x => x.LogError(message), Times.Once); + } + [Fact] public void should_use_base_url_partial_placeholder() { @@ -107,9 +150,41 @@ namespace Ocelot.UnitTests.Configuration .BDDfy(); } + + [Fact] + public void should_add_trace_id_header() + { + var reRoute = new FileReRoute + { + DownstreamHeaderTransform = new Dictionary + { + {"Trace-Id", "{TraceId}"}, + } + }; + + var expected = new AddHeader("Trace-Id", "{TraceId}"); + + this.Given(x => GivenTheReRoute(reRoute)) + .And(x => GivenTheBaseUrlIs("http://ocelot.com/")) + .When(x => WhenICreate()) + .Then(x => ThenTheFollowingAddHeaderIsReturned(expected)) + .BDDfy(); + } + private void GivenTheBaseUrlIs(string baseUrl) { - _finder.Setup(x => x.Find()).Returns(baseUrl); + _placeholders.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse(baseUrl)); + } + + private void GivenTheBaseUrlErrors() + { + _placeholders.Setup(x => x.Get(It.IsAny())).Returns(new ErrorResponse(new AnyError())); + } + + private void ThenTheFollowingAddHeaderIsReturned(AddHeader addHeader) + { + _result.AddHeadersToDownstream[0].Key.ShouldBe(addHeader.Key); + _result.AddHeadersToDownstream[0].Value.ShouldBe(addHeader.Value); } private void ThenTheFollowingDownstreamIsReturned(List downstream) diff --git a/test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs b/test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs new file mode 100644 index 00000000..a4a19eec --- /dev/null +++ b/test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs @@ -0,0 +1,148 @@ +using Xunit; +using Shouldly; +using TestStack.BDDfy; +using Ocelot.Headers; +using System.Net.Http; +using System.Collections.Generic; +using Ocelot.Configuration.Creator; +using System.Linq; +using Moq; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Responses; +using Ocelot.Infrastructure; +using Ocelot.UnitTests.Responder; +using System; +using Ocelot.Logging; + +namespace Ocelot.UnitTests.Headers +{ + public class AddHeadersToResponseTests + { + private IAddHeadersToResponse _adder; + private Mock _placeholders; + private HttpResponseMessage _response; + private List _addHeaders; + private Mock _factory; + private Mock _logger; + + public AddHeadersToResponseTests() + { + _factory = new Mock(); + _logger = new Mock(); + _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _placeholders = new Mock(); + _adder = new AddHeadersToResponse(_placeholders.Object, _factory.Object); + } + + [Fact] + public void should_add_header() + { + var addHeaders = new List + { + new AddHeader("Laura", "Tom") + }; + + this.Given(_ => GivenAResponseMessage()) + .And(_ => GivenTheAddHeaders(addHeaders)) + .When(_ => WhenIAdd()) + .And(_ => ThenTheHeaderIsReturned("Laura", "Tom")) + .BDDfy(); + } + + [Fact] + public void should_add_trace_id_placeholder() + { + var addHeaders = new List + { + new AddHeader("Trace-Id", "{TraceId}") + }; + + var traceId = "123"; + + this.Given(_ => GivenAResponseMessage()) + .And(_ => GivenTheTraceIdIs(traceId)) + .And(_ => GivenTheAddHeaders(addHeaders)) + .When(_ => WhenIAdd()) + .Then(_ => ThenTheHeaderIsReturned("Trace-Id", traceId)) + .BDDfy(); + } + + [Fact] + public void should_add_trace_id_placeholder_and_normal() + { + var addHeaders = new List + { + new AddHeader("Trace-Id", "{TraceId}"), + new AddHeader("Tom", "Laura") + }; + + var traceId = "123"; + + this.Given(_ => GivenAResponseMessage()) + .And(_ => GivenTheTraceIdIs(traceId)) + .And(_ => GivenTheAddHeaders(addHeaders)) + .When(_ => WhenIAdd()) + .Then(_ => ThenTheHeaderIsReturned("Trace-Id", traceId)) + .Then(_ => ThenTheHeaderIsReturned("Tom", "Laura")) + .BDDfy(); + } + + [Fact] + public void should_do_nothing_and_log_error() + { + var addHeaders = new List + { + new AddHeader("Trace-Id", "{TraceId}") + }; + + this.Given(_ => GivenAResponseMessage()) + .And(_ => GivenTheTraceIdErrors()) + .And(_ => GivenTheAddHeaders(addHeaders)) + .When(_ => WhenIAdd()) + .Then(_ => ThenTheHeaderIsNotAdded("Trace-Id")) + .And(_ => ThenTheErrorIsLogged()) + .BDDfy(); + } + + private void ThenTheErrorIsLogged() + { + _logger.Verify(x => x.LogError("Unable to add header to response Trace-Id: {TraceId}"), Times.Once); + } + + private void ThenTheHeaderIsNotAdded(string key) + { + _response.Headers.TryGetValues(key, out var values).ShouldBeFalse(); + } + + private void GivenTheTraceIdIs(string traceId) + { + _placeholders.Setup(x => x.Get("{TraceId}")).Returns(new OkResponse(traceId)); + } + + private void GivenTheTraceIdErrors() + { + _placeholders.Setup(x => x.Get("{TraceId}")).Returns(new ErrorResponse(new AnyError())); + } + + private void ThenTheHeaderIsReturned(string key, string value) + { + var values = _response.Headers.GetValues(key); + values.First().ShouldBe(value); + } + + private void WhenIAdd() + { + _adder.Add(_addHeaders, _response); + } + + private void GivenAResponseMessage() + { + _response = new HttpResponseMessage(); + } + + private void GivenTheAddHeaders(List addHeaders) + { + _addHeaders = addHeaders; + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs index 3085a64d..c49bf172 100644 --- a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs @@ -26,6 +26,7 @@ namespace Ocelot.UnitTests.Headers private HttpHeadersTransformationMiddleware _middleware; private DownstreamContext _downstreamContext; private OcelotRequestDelegate _next; + private Mock _addHeaders; public HttpHeadersTransformationMiddlewareTests() { @@ -36,7 +37,8 @@ namespace Ocelot.UnitTests.Headers _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; - _middleware = new HttpHeadersTransformationMiddleware(_next, _loggerFactory.Object, _preReplacer.Object, _postReplacer.Object); + _addHeaders = new Mock(); + _middleware = new HttpHeadersTransformationMiddleware(_next, _loggerFactory.Object, _preReplacer.Object, _postReplacer.Object, _addHeaders.Object); } [Fact] @@ -49,9 +51,16 @@ namespace Ocelot.UnitTests.Headers .When(x => WhenICallTheMiddleware()) .Then(x => ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly()) .And(x => ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly()) + .And(x => ThenAddHeadersIsCalledCorrectly()) .BDDfy(); } + private void ThenAddHeadersIsCalledCorrectly() + { + _addHeaders + .Verify(x => x.Add(_downstreamContext.DownstreamReRoute.AddHeadersToDownstream, _downstreamContext.DownstreamResponse), Times.Once); + } + private void WhenICallTheMiddleware() { _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); diff --git a/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs b/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs index 95ae6e6d..ca97de4f 100644 --- a/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs @@ -7,20 +7,30 @@ using Ocelot.Configuration; using System.Collections.Generic; using Ocelot.Responses; using System.Linq; +using Moq; +using Ocelot.Infrastructure; +using Ocelot.Middleware; +using Ocelot.Infrastructure.RequestData; namespace Ocelot.UnitTests.Headers { public class HttpResponseHeaderReplacerTests { private HttpResponseMessage _response; + private Placeholders _placeholders; private HttpResponseHeaderReplacer _replacer; private List _headerFindAndReplaces; private Response _result; private HttpRequestMessage _request; + private Mock _finder; + private Mock _repo; public HttpResponseHeaderReplacerTests() { - _replacer = new HttpResponseHeaderReplacer(); + _repo = new Mock(); + _finder = new Mock(); + _placeholders = new Placeholders(_finder.Object, _repo.Object); + _replacer = new HttpResponseHeaderReplacer(_placeholders); } [Fact] diff --git a/test/Ocelot.UnitTests/Infrastructure/IScopedRequestDataRepository.cs b/test/Ocelot.UnitTests/Infrastructure/IScopedRequestDataRepository.cs new file mode 100644 index 00000000..e69de29b diff --git a/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs b/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs new file mode 100644 index 00000000..6acd3655 --- /dev/null +++ b/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs @@ -0,0 +1,79 @@ +using System; +using System.Net.Http; +using Moq; +using Ocelot.Infrastructure; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Middleware; +using Ocelot.Responses; +using Shouldly; +using Xunit; + +namespace Ocelot.UnitTests.Infrastructure +{ + public class PlaceholdersTests + { + private IPlaceholders _placeholders; + private Mock _finder; + private Mock _repo; + + public PlaceholdersTests() + { + _repo = new Mock(); + _finder = new Mock(); + _placeholders = new Placeholders(_finder.Object, _repo.Object); + } + + [Fact] + public void should_return_base_url() + { + var baseUrl = "http://www.bbc.co.uk"; + _finder.Setup(x => x.Find()).Returns(baseUrl); + var result = _placeholders.Get("{BaseUrl}"); + result.Data.ShouldBe(baseUrl); + } + + [Fact] + public void should_return_key_does_not_exist() + { + var result = _placeholders.Get("{Test}"); + result.IsError.ShouldBeTrue(); + result.Errors[0].Message.ShouldBe("Unable to find placeholder called {Test}"); + } + + [Fact] + public void should_return_downstream_base_url_when_port_is_not_80_or_443() + { + var request = new HttpRequestMessage(); + request.RequestUri = new Uri("http://www.bbc.co.uk"); + var result = _placeholders.Get("{DownstreamBaseUrl}", request); + result.Data.ShouldBe("http://www.bbc.co.uk/"); + } + + + [Fact] + public void should_return_downstream_base_url_when_port_is_80_or_443() + { + var request = new HttpRequestMessage(); + request.RequestUri = new Uri("http://www.bbc.co.uk:123"); + var result = _placeholders.Get("{DownstreamBaseUrl}", request); + result.Data.ShouldBe("http://www.bbc.co.uk:123/"); + } + + [Fact] + public void should_return_key_does_not_exist_for_http_request_message() + { + var result = _placeholders.Get("{Test}", new System.Net.Http.HttpRequestMessage()); + result.IsError.ShouldBeTrue(); + result.Errors[0].Message.ShouldBe("Unable to find placeholder called {Test}"); + } + + [Fact] + public void should_return_trace_id() + { + var traceId = "123"; + _repo.Setup(x => x.Get("TraceId")).Returns(new OkResponse(traceId)); + var result = _placeholders.Get("{TraceId}"); + result.Data.ShouldBe(traceId); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs b/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs index e8196966..bd633cb3 100644 --- a/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs +++ b/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs @@ -1,5 +1,6 @@ using Butterfly.Client.Tracing; using Moq; +using Ocelot.Infrastructure.RequestData; using Ocelot.Requester; using Shouldly; using Xunit; @@ -10,11 +11,13 @@ namespace Ocelot.UnitTests.Requester { private TracingHandlerFactory _factory; private Mock _tracer; + private Mock _repo; public TracingHandlerFactoryTests() { _tracer = new Mock(); - _factory = new TracingHandlerFactory(_tracer.Object); + _repo = new Mock(); + _factory = new TracingHandlerFactory(_tracer.Object, _repo.Object); } [Fact] diff --git a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs index 9800ffef..8ee2be1f 100644 --- a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs +++ b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs @@ -120,7 +120,7 @@ namespace Ocelot.UnitTests.Responder // If this test fails then it's because the number of error codes has changed. // You should make the appropriate changes to the test cases here to ensure // they cover all the error codes, and then modify this assertion. - Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(33, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); + Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(34, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); } private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode) From 7e43af01263f27cc44c46b3899c93935bc75fb9c Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Tue, 20 Mar 2018 20:48:30 +0000 Subject: [PATCH 14/50] #289 fix for issue where I was not preserving original query string when more than one query with same name (#290) --- .../QueryStrings/AddQueriesToRequest.cs | 42 +++++++++-- .../ClaimsToQueryStringForwardingTests.cs | 69 +++++++++++++++++++ .../QueryStrings/AddQueriesToRequestTests.cs | 35 +++++++++- 3 files changed, 139 insertions(+), 7 deletions(-) diff --git a/src/Ocelot/QueryStrings/AddQueriesToRequest.cs b/src/Ocelot/QueryStrings/AddQueriesToRequest.cs index 25e9772a..74cc7696 100644 --- a/src/Ocelot/QueryStrings/AddQueriesToRequest.cs +++ b/src/Ocelot/QueryStrings/AddQueriesToRequest.cs @@ -7,6 +7,8 @@ using Ocelot.Responses; using System.Security.Claims; using System.Net.Http; using System; +using Microsoft.Extensions.Primitives; +using System.Text; namespace Ocelot.QueryStrings { @@ -45,6 +47,7 @@ namespace Ocelot.QueryStrings } var uriBuilder = new UriBuilder(downstreamRequest.RequestUri); + uriBuilder.Query = ConvertDictionaryToQueryString(queryDictionary); downstreamRequest.RequestUri = uriBuilder.Uri; @@ -52,16 +55,43 @@ namespace Ocelot.QueryStrings return new OkResponse(); } - private Dictionary ConvertQueryStringToDictionary(string queryString) + private Dictionary ConvertQueryStringToDictionary(string queryString) { - return Microsoft.AspNetCore.WebUtilities.QueryHelpers - .ParseQuery(queryString) - .ToDictionary(q => q.Key, q => q.Value.FirstOrDefault() ?? string.Empty); + var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers + .ParseQuery(queryString); + + return query; } - private string ConvertDictionaryToQueryString(Dictionary queryDictionary) + private string ConvertDictionaryToQueryString(Dictionary queryDictionary) { - return Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString("", queryDictionary); + var builder = new StringBuilder(); + + builder.Append("?"); + + int outerCount = 0; + + foreach (var query in queryDictionary) + { + for (int innerCount = 0; innerCount < query.Value.Count; innerCount++) + { + builder.Append($"{query.Key}={query.Value[innerCount]}"); + + if(innerCount < (query.Value.Count - 1)) + { + builder.Append("&"); + } + } + + if(outerCount < (queryDictionary.Count - 1)) + { + builder.Append("&"); + } + + outerCount++; + } + + return builder.ToString(); } } } \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs index f73a360e..1b9469e1 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToQueryStringForwardingTests.cs @@ -19,6 +19,7 @@ namespace Ocelot.AcceptanceTests { using IdentityServer4; using IdentityServer4.Test; + using Shouldly; public class ClaimsToQueryStringForwardingTests : IDisposable { @@ -27,6 +28,7 @@ namespace Ocelot.AcceptanceTests private readonly Steps _steps; private Action _options; private string _identityServerRootUrl = "http://localhost:57888"; + private string _downstreamQueryString; public ClaimsToQueryStringForwardingTests() { @@ -105,6 +107,71 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_return_response_200_and_foward_claim_as_query_string_and_preserve_original_string() + { + var user = new TestUser() + { + Username = "test", + Password = "test", + SubjectId = "registered|1231231", + Claims = new List + { + new Claim("CustomerId", "123"), + new Claim("LocationId", "1") + } + }; + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 57876, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + AuthenticationOptions = new FileAuthenticationOptions + { + AuthenticationProviderKey = "Test", + AllowedScopes = new List + { + "openid", "offline_access", "api" + }, + }, + AddQueriesToRequest = + { + {"CustomerId", "Claims[CustomerId] > value"}, + {"LocationId", "Claims[LocationId] > value"}, + {"UserType", "Claims[sub] > value[0] > |"}, + {"UserId", "Claims[sub] > value[1] > |"} + } + } + } + }; + + this.Given(x => x.GivenThereIsAnIdentityServerOn("http://localhost:57888", "api", AccessTokenType.Jwt, user)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:57876", 200)) + .And(x => _steps.GivenIHaveAToken("http://localhost:57888")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) + .And(x => _steps.GivenIHaveAddedATokenToMyRequest()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/?test=1&test=2")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("CustomerId: 123 LocationId: 1 UserType: registered UserId: 1231231")) + .And(_ => _downstreamQueryString.ShouldBe("?test=1&test=2&CustomerId=123&LocationId=1&UserId=1231231&UserType=registered")) + .BDDfy(); + } + private void GivenThereIsAServiceRunningOn(string url, int statusCode) { _servicebuilder = new WebHostBuilder() @@ -117,6 +184,8 @@ namespace Ocelot.AcceptanceTests { app.Run(async context => { + _downstreamQueryString = context.Request.QueryString.Value; + StringValues customerId; context.Request.Query.TryGetValue("CustomerId", out customerId); diff --git a/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs b/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs index d12d4294..bebe4307 100644 --- a/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs @@ -18,7 +18,7 @@ namespace Ocelot.UnitTests.QueryStrings public class AddQueriesToRequestTests { private readonly AddQueriesToRequest _addQueriesToRequest; - private readonly HttpRequestMessage _downstreamRequest; + private HttpRequestMessage _downstreamRequest; private readonly Mock _parser; private List _configuration; private List _claims; @@ -53,6 +53,34 @@ namespace Ocelot.UnitTests.QueryStrings .BDDfy(); } + [Fact] + public void should_add_new_queries_to_downstream_request_and_preserve_other_queries() + { + var claims = new List + { + new Claim("test", "data") + }; + + this.Given( + x => x.GivenAClaimToThing(new List + { + new ClaimToThing("query-key", "", "", 0) + })) + .Given(x => x.GivenClaims(claims)) + .And(x => GivenTheDownstreamRequestHasQueryString("?test=1&test=2")) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .When(x => x.WhenIAddQueriesToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .And(x => x.ThenTheQueryIsAdded()) + .And(x => TheTheQueryStringIs("?test=1&test=2&query-key=value")) + .BDDfy(); + } + + private void TheTheQueryStringIs(string expected) + { + _downstreamRequest.RequestUri.Query.ShouldBe(expected); + } + [Fact] public void should_replace_existing_queries_on_downstream_request() { @@ -110,6 +138,11 @@ namespace Ocelot.UnitTests.QueryStrings _claims = claims; } + private void GivenTheDownstreamRequestHasQueryString(string queryString) + { + _downstreamRequest = new HttpRequestMessage(HttpMethod.Post, $"http://my.url/abc{queryString}"); + } + private void GivenTheDownstreamRequestHasQueryString(string key, string value) { var newUri = Microsoft.AspNetCore.WebUtilities.QueryHelpers From 23c2a07e3c59fd9f14c6d1eb8099d1b23e0b9ca3 Mon Sep 17 00:00:00 2001 From: jstallm Date: Tue, 20 Mar 2018 23:29:12 -0700 Subject: [PATCH 15/50] Fix typo projct (#291) --- docs/introduction/gettingstarted.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/introduction/gettingstarted.rst b/docs/introduction/gettingstarted.rst index 8f635fdc..7f81cb51 100644 --- a/docs/introduction/gettingstarted.rst +++ b/docs/introduction/gettingstarted.rst @@ -9,7 +9,7 @@ built to netcoreapp2.0 `this Date: Wed, 21 Mar 2018 14:29:45 +0800 Subject: [PATCH 16/50] Fix typo (#292) --- docs/introduction/bigpicture.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/introduction/bigpicture.rst b/docs/introduction/bigpicture.rst index 989c0f5c..b047abb4 100644 --- a/docs/introduction/bigpicture.rst +++ b/docs/introduction/bigpicture.rst @@ -1,7 +1,7 @@ Big Picture =========== -Ocleot is aimed at people using .NET running +Ocelot is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. From c79f1ecbf8d62bc67e736421ecb52c025ff916a6 Mon Sep 17 00:00:00 2001 From: tangdf Date: Wed, 21 Mar 2018 14:33:22 +0800 Subject: [PATCH 17/50] Middleware Invoke multi parameters (#288) --- .../OcelotPipelineBuilderExtensions.cs | 5 ++- .../Middleware/OcelotPiplineBuilderTests.cs | 31 ++++++++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs index 422097fc..2469bf7c 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs @@ -94,7 +94,7 @@ namespace Ocelot.Middleware.Pipeline private static Func Compile(MethodInfo methodinfo, ParameterInfo[] parameters) { var middleware = typeof(T); - var httpContextArg = Expression.Parameter(typeof(HttpContext), "httpContext"); + var httpContextArg = Expression.Parameter(typeof(DownstreamContext), "downstreamContext"); var providerArg = Expression.Parameter(typeof(IServiceProvider), "serviceProvider"); var instanceArg = Expression.Parameter(middleware, "middleware"); @@ -111,8 +111,7 @@ namespace Ocelot.Middleware.Pipeline var parameterTypeExpression = new Expression[] { providerArg, - Expression.Constant(parameterType, typeof(Type)), - Expression.Constant(methodinfo.DeclaringType, typeof(Type)) + Expression.Constant(parameterType, typeof(Type)) }; var getServiceCall = Expression.Call(GetServiceInfo, parameterTypeExpression); diff --git a/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs index 988d0b21..a6862bec 100644 --- a/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs +++ b/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs @@ -1,4 +1,7 @@ -namespace Ocelot.UnitTests.Middleware +using System; +using System.Threading.Tasks; + +namespace Ocelot.UnitTests.Middleware { using System.Collections.Generic; using Microsoft.AspNetCore.Hosting; @@ -80,5 +83,31 @@ _counter.ShouldBe(1); _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(404); } + + [Fact] + public void Middleware_Multi_Parameters_Invoke() + { + var provider = _services.BuildServiceProvider(); + IOcelotPipelineBuilder builder = new OcelotPipelineBuilder(provider); + builder = builder.UseMiddleware(); + var del = builder.Build(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + del.Invoke(_downstreamContext); + } + + private class MultiParametersInvokeMiddleware : OcelotMiddleware + { + private readonly OcelotRequestDelegate _next; + + public MultiParametersInvokeMiddleware(OcelotRequestDelegate next) + { + _next = next; + } + + public Task Invoke(DownstreamContext context, IServiceProvider serviceProvider) + { + return Task.CompletedTask; + } + } } } From 4493b22d0d325f08e15ac0ea223d8fbcbd986334 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Thu, 22 Mar 2018 08:30:26 +0000 Subject: [PATCH 18/50] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ab3d89a5..348127f6 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Windows Build history](https://buildstats.info/appveyor/chart/TomPallister/ocelot-fcfpb?branch=develop&includeBuildsFromPullRequest=false)](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb/history?branch=develop) -[![Coverage Status](https://coveralls.io/repos/github/TomPallister/Ocelot/badge.svg?branch=develop)](https://coveralls.io/github/TomPallister/Ocelot?branch=develop) +[![Coverage Status](https://coveralls.io/repos/github/ThreeMammals/Ocelot/badge.svg?branch=develop)](https://coveralls.io/github/ThreeMammals/Ocelot?branch=develop) # Ocelot From 463a7bdab4652762d14779e7e3f62a207c3d421c Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Fri, 23 Mar 2018 18:01:02 +0000 Subject: [PATCH 19/50] Feature/websockets (#273) * #212 - hacked websockets proxy together * faffing around * #212 hacking away :( * #212 websockets proxy middleware working * #212 map when for webockets working * #212 some test refactor * #212 temp commit * #212 websockets proxy working, tests passing...need to do some tidying and write docs * #212 more code coverage * #212 docs for websockets * #212 updated readme * #212 tidying up after websockets refactoring * #212 tidying up after websockets refactoring * #212 tidying up after websockets refactoring * stuck a warning in about logging levels into docs! --- README.md | 1 + docs/features/logging.rst | 8 +- docs/features/websockets.rst | 68 +++ docs/index.rst | 1 + .../Cache/Middleware/OutputCacheMiddleware.cs | 2 +- src/Ocelot/Configuration/Creator/AddHeader.cs | 14 + .../Creator/HeaderFindAndReplaceCreator.cs | 6 +- .../Creator/HeaderTransformations.cs | 17 +- .../ConsulFileConfigurationRepository.cs | 1 + .../Repository/IConsulPollerConfiguration.cs | 3 +- .../DownstreamHostNullOrEmptyError.cs | 12 - .../DownstreamPathNullOrEmptyError.cs | 12 - .../DownstreamSchemeNullOrEmptyError.cs | 12 - .../DownstreamUrlCreatorMiddleware.cs | 37 +- src/Ocelot/Headers/AddHeadersToRequest.cs | 3 +- src/Ocelot/Headers/AddHeadersToResponse.cs | 7 +- .../Headers/HttpResponseHeaderReplacer.cs | 3 +- src/Ocelot/Headers/IAddHeadersToRequest.cs | 3 +- .../Headers/IHttpResponseHeaderReplacer.cs | 3 +- src/Ocelot/Infrastructure/IPlaceholders.cs | 3 +- src/Ocelot/Infrastructure/Placeholders.cs | 17 +- .../Middleware/LoadBalancingMiddleware.cs | 12 +- src/Ocelot/Middleware/DownstreamContext.cs | 4 +- .../Pipeline/IOcelotPipelineBuilder.cs | 1 + .../Middleware/Pipeline/MapWhenMiddleware.cs | 44 ++ .../Middleware/Pipeline/MapWhenOptions.cs | 28 + .../Pipeline/OcelotPipelineBuilder.cs | 11 + .../OcelotPipelineBuilderExtensions.cs | 31 ++ .../Pipeline/OcelotPipelineExtensions.cs | 14 +- .../QueryStrings/AddQueriesToRequest.cs | 13 +- .../QueryStrings/IAddQueriesToRequest.cs | 3 +- .../Request/Middleware/DownstreamRequest.cs | 69 +++ .../DownstreamRequestInitialiserMiddleware.cs | 5 +- .../Middleware/ReRouteRequestIdMiddleware.cs | 3 +- src/Ocelot/Requester/HttpClientBuilder.cs | 4 +- .../Requester/HttpClientHttpRequester.cs | 2 +- .../ConsulRegistryConfiguration.cs | 32 +- .../ServiceFabricConfiguration.cs | 10 +- ...ableToFindServiceDiscoveryProviderError.cs | 24 +- .../IServiceDiscoveryProviderFactory.cs | 3 +- .../ConfigurationServiceProvider.cs | 42 +- .../ConsulServiceDiscoveryProvider.cs | 167 +++--- .../IServiceDiscoveryProvider.cs | 22 +- .../ServiceFabricServiceDiscoveryProvider.cs | 3 +- .../ServiceDiscoveryProviderFactory.cs | 2 + src/Ocelot/Values/DownstreamPath.cs | 2 +- src/Ocelot/Values/DownstreamUrl.cs | 12 - src/Ocelot/Values/PathTemplate.cs | 2 +- src/Ocelot/Values/Service.cs | 16 +- src/Ocelot/Values/ServiceHostAndPort.cs | 5 +- src/Ocelot/Values/UpstreamPathTemplate.cs | 7 +- .../Middleware/WebSocketsProxyMiddleware.cs | 103 ++++ .../WebSocketsProxyMiddlewareExtensions.cs | 12 + .../LoadBalancerTests.cs | 18 +- test/Ocelot.AcceptanceTests/Steps.cs | 38 ++ test/Ocelot.AcceptanceTests/WebSocketTests.cs | 487 ++++++++++++++++++ .../OutputCacheMiddlewareRealCacheTests.cs | 2 +- .../Cache/OutputCacheMiddlewareTests.cs | 2 +- .../DownstreamUrlCreatorMiddlewareTests.cs | 11 +- .../Headers/AddHeadersToRequestTests.cs | 5 +- ...ttpHeadersTransformationMiddlewareTests.cs | 5 +- ...ttpRequestHeadersBuilderMiddlewareTests.cs | 5 +- .../HttpResponseHeaderReplacerTests.cs | 17 +- .../Infrastructure/PlaceholdersTests.cs | 14 +- .../Infrastructure/StringExtensionsTests.cs | 29 ++ .../LoadBalancer/LoadBalancerFactoryTests.cs | 1 + .../LoadBalancerMiddlewareTests.cs | 8 +- .../SimpleJsonResponseAggregatorTests.cs | 3 +- .../QueryStrings/AddQueriesToRequestTests.cs | 20 +- .../QueryStringBuilderMiddlewareTests.cs | 5 +- .../ClientRateLimitMiddlewareTests.cs | 5 +- ...streamRequestInitialiserMiddlewareTests.cs | 2 +- .../ReRouteRequestIdMiddlewareTests.cs | 5 +- .../Requester/HttpClientBuilderTests.cs | 3 +- .../Requester/HttpClientHttpRequesterTest.cs | 34 +- .../ConfigurationServiceProviderTests.cs | 1 + .../ConsulServiceDiscoveryProviderTests.cs | 2 + ...viceFabricServiceDiscoveryProviderTests.cs | 5 +- .../ServiceProviderFactoryTests.cs | 1 + .../WebSocketsProxyMiddlewareTests.cs | 239 +++++++++ 80 files changed, 1539 insertions(+), 369 deletions(-) create mode 100644 docs/features/websockets.rst create mode 100644 src/Ocelot/Configuration/Creator/AddHeader.cs delete mode 100644 src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs delete mode 100644 src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs delete mode 100644 src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs create mode 100644 src/Ocelot/Middleware/Pipeline/MapWhenMiddleware.cs create mode 100644 src/Ocelot/Middleware/Pipeline/MapWhenOptions.cs create mode 100644 src/Ocelot/Request/Middleware/DownstreamRequest.cs rename src/Ocelot/ServiceDiscovery/{ => Configuration}/ConsulRegistryConfiguration.cs (90%) rename src/Ocelot/ServiceDiscovery/{ => Configuration}/ServiceFabricConfiguration.cs (58%) rename src/Ocelot/ServiceDiscovery/{ => Errors}/UnableToFindServiceDiscoveryProviderError.cs (86%) rename src/Ocelot/ServiceDiscovery/{ => Providers}/ConfigurationServiceProvider.cs (88%) rename src/Ocelot/ServiceDiscovery/{ => Providers}/ConsulServiceDiscoveryProvider.cs (96%) rename src/Ocelot/ServiceDiscovery/{ => Providers}/IServiceDiscoveryProvider.cs (79%) rename src/Ocelot/ServiceDiscovery/{ => Providers}/ServiceFabricServiceDiscoveryProvider.cs (90%) delete mode 100644 src/Ocelot/Values/DownstreamUrl.cs create mode 100644 src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs create mode 100644 src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddlewareExtensions.cs create mode 100644 test/Ocelot.AcceptanceTests/WebSocketTests.cs create mode 100644 test/Ocelot.UnitTests/Infrastructure/StringExtensionsTests.cs create mode 100644 test/Ocelot.UnitTests/WebSockets/WebSocketsProxyMiddlewareTests.cs diff --git a/README.md b/README.md index 348127f6..f8ad7b22 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio * Request Aggregation * Service Discovery with Consul * Service Fabric +* WebSockets * Authentication * Authorisation * Rate Limiting diff --git a/docs/features/logging.rst b/docs/features/logging.rst index 313f62ff..b09a26cf 100644 --- a/docs/features/logging.rst +++ b/docs/features/logging.rst @@ -11,4 +11,10 @@ Finally if logging is set to trace level Ocelot will log starting, finishing and 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. \ No newline at end of file +to true for logging settings. Nicely onto the next feature. + +Warning +^^^^^^^ + +If you are logging to Console you will get terrible performance. I have had so many issues about performance issues with Ocelot +and it is always logging level Debug, logging to Console :) Make sure you are logging to something proper in production :) diff --git a/docs/features/websockets.rst b/docs/features/websockets.rst new file mode 100644 index 00000000..828d1051 --- /dev/null +++ b/docs/features/websockets.rst @@ -0,0 +1,68 @@ +Websockets +========== + +Ocelot supports proxying websockets with some extra bits. This functionality was requested in `Issue 212 `_. + +In order to get websocket proxying working with Ocelot you need to do the following. + +In your Configure method you need to tell your application to use WebSockets. + +.. code-block:: csharp + + Configure(app => + { + app.UseWebSockets(); + app.UseOcelot().Wait(); + }) + +Then in your configuration.json add the following to proxy a ReRoute using websockets. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/ws", + "UpstreamPathTemplate": "/", + "DownstreamScheme": "ws", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5001 + } + ], + } + +With this configuration set Ocelot will match any websocket traffic that comes in on / and proxy it to localhost:5001/ws. To make this clearer +Ocelot will receive messages from the upstream client, proxy these to the downstream service, receive messages from the downstream service and +proxy these to the upstream client. + +Supported +^^^^^^^^^ + +1. Load Balancer +2. Routing +3. Service Discovery + +This means that you can set up your downstream services running websockets and either have multiple DownstreamHostAndPorts in your ReRoute +config or hook your ReRoute into a service discovery provider and then load balance requests...Which I think is pretty cool :) + +Not Supported +^^^^^^^^^^^^^ + +Unfortunately a lot of Ocelot's features are non websocket specific such as header and http client stuff. I've listed what won't work below. + +1. Tracing +2. RequestId +3. Request Aggregation +4. Rate Limiting +5. Quality of Service +6. Middleware Injection +7. Header Transformation +8. Delegating Handlers +9. Claims Transformation +10. Caching +11. Authentication - If anyone requests it we might be able to do something with basic authentication. +12. Authorisation + +I'm not 100% sure what will happen with this feature when it get's into the wild so please make sure you test thoroughly! + + diff --git a/docs/index.rst b/docs/index.rst index 43a6c436..78b305b2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -25,6 +25,7 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n features/servicefabric features/authentication features/authorisation + features/websockets features/administration features/ratelimiting features/caching diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 3d128b72..88bc4e9e 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -37,7 +37,7 @@ namespace Ocelot.Cache.Middleware return; } - var downstreamUrlKey = $"{context.DownstreamRequest.Method.Method}-{context.DownstreamRequest.RequestUri.OriginalString}"; + var downstreamUrlKey = $"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}"; _logger.LogDebug("started checking cache for {downstreamUrlKey}", downstreamUrlKey); diff --git a/src/Ocelot/Configuration/Creator/AddHeader.cs b/src/Ocelot/Configuration/Creator/AddHeader.cs new file mode 100644 index 00000000..72cae161 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/AddHeader.cs @@ -0,0 +1,14 @@ +namespace Ocelot.Configuration.Creator +{ + public class AddHeader + { + public AddHeader(string key, string value) + { + Key = key; + Value = value; + } + + public string Key { get; } + public string Value { get; } + } +} diff --git a/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs b/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs index 4ff87d2f..e976e7b9 100644 --- a/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs +++ b/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs @@ -10,12 +10,12 @@ namespace Ocelot.Configuration.Creator { public class HeaderFindAndReplaceCreator : IHeaderFindAndReplaceCreator { - private IPlaceholders _placeholders; - private IOcelotLogger _logger; + private readonly IPlaceholders _placeholders; + private readonly IOcelotLogger _logger; public HeaderFindAndReplaceCreator(IPlaceholders placeholders, IOcelotLoggerFactory factory) { - _logger = factory.CreateLogger();; + _logger = factory.CreateLogger(); _placeholders = placeholders; } diff --git a/src/Ocelot/Configuration/Creator/HeaderTransformations.cs b/src/Ocelot/Configuration/Creator/HeaderTransformations.cs index 72d307e5..461e5c35 100644 --- a/src/Ocelot/Configuration/Creator/HeaderTransformations.cs +++ b/src/Ocelot/Configuration/Creator/HeaderTransformations.cs @@ -14,21 +14,10 @@ namespace Ocelot.Configuration.Creator Downstream = downstream; } - public List Upstream { get; private set; } + public List Upstream { get; } - public List Downstream { get; private set; } - public List AddHeadersToDownstream {get;private set;} - } + public List Downstream { get; } - public class AddHeader - { - public AddHeader(string key, string value) - { - this.Key = key; - this.Value = value; - - } - public string Key { get; private set; } - public string Value { get; private set; } + public List AddHeadersToDownstream { get; } } } diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs index 97bf7c97..10e99c10 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs @@ -6,6 +6,7 @@ using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.Responses; using Ocelot.ServiceDiscovery; +using Ocelot.ServiceDiscovery.Configuration; namespace Ocelot.Configuration.Repository { diff --git a/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs b/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs index 93003087..d1f1430d 100644 --- a/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs +++ b/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs @@ -4,5 +4,4 @@ { int Delay { get; } } - - } +} diff --git a/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs b/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs deleted file mode 100644 index d56532a0..00000000 --- a/src/Ocelot/DownstreamUrlCreator/DownstreamHostNullOrEmptyError.cs +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 69528d43..00000000 --- a/src/Ocelot/DownstreamUrlCreator/DownstreamPathNullOrEmptyError.cs +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 9f83bfee..00000000 --- a/src/Ocelot/DownstreamUrlCreator/DownstreamSchemeNullOrEmptyError.cs +++ /dev/null @@ -1,12 +0,0 @@ -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/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 308daea5..43944050 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -40,49 +40,36 @@ namespace Ocelot.DownstreamUrlCreator.Middleware return; } - UriBuilder uriBuilder; - + context.DownstreamRequest.Scheme = context.DownstreamReRoute.DownstreamScheme; + if (ServiceFabricRequest(context)) { - uriBuilder = CreateServiceFabricUri(context, dsPath); + var pathAndQuery = CreateServiceFabricUri(context, dsPath); + context.DownstreamRequest.AbsolutePath = pathAndQuery.path; + context.DownstreamRequest.Query = pathAndQuery.query; } else { - uriBuilder = new UriBuilder(context.DownstreamRequest.RequestUri) - { - Path = dsPath.Data.Value, - Scheme = context.DownstreamReRoute.DownstreamScheme - }; + context.DownstreamRequest.AbsolutePath = dsPath.Data.Value; } - context.DownstreamRequest.RequestUri = uriBuilder.Uri; - - _logger.LogDebug("downstream url is {downstreamUrl.Data.Value}", context.DownstreamRequest.RequestUri); + _logger.LogDebug("downstream url is {context.DownstreamRequest}", context.DownstreamRequest); await _next.Invoke(context); } - private UriBuilder CreateServiceFabricUri(DownstreamContext context, Response dsPath) + private (string path, string query) CreateServiceFabricUri(DownstreamContext context, Response dsPath) { - var query = context.DownstreamRequest.RequestUri.Query; - var scheme = context.DownstreamReRoute.DownstreamScheme; - var host = context.DownstreamRequest.RequestUri.Host; - var port = context.DownstreamRequest.RequestUri.Port; + var query = context.DownstreamRequest.Query; var serviceFabricPath = $"/{context.DownstreamReRoute.ServiceName + dsPath.Data.Value}"; - Uri uri; - if (RequestForStatefullService(query)) { - uri = new Uri($"{scheme}://{host}:{port}{serviceFabricPath}{query}"); - } - else - { - var split = string.IsNullOrEmpty(query) ? "?" : "&"; - uri = new Uri($"{scheme}://{host}:{port}{serviceFabricPath}{query}{split}cmd=instance"); + return (serviceFabricPath, query); } - return new UriBuilder(uri); + var split = string.IsNullOrEmpty(query) ? "?" : "&"; + return (serviceFabricPath, $"{query}{split}cmd=instance"); } private static bool ServiceFabricRequest(DownstreamContext context) diff --git a/src/Ocelot/Headers/AddHeadersToRequest.cs b/src/Ocelot/Headers/AddHeadersToRequest.cs index f61b1ac3..1a71935a 100644 --- a/src/Ocelot/Headers/AddHeadersToRequest.cs +++ b/src/Ocelot/Headers/AddHeadersToRequest.cs @@ -5,6 +5,7 @@ using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Responses; using System.Net.Http; using Ocelot.Configuration.Creator; +using Ocelot.Request.Middleware; namespace Ocelot.Headers { @@ -17,7 +18,7 @@ namespace Ocelot.Headers _claimsParser = claimsParser; } - public Response SetHeadersOnDownstreamRequest(List claimsToThings, IEnumerable claims, HttpRequestMessage downstreamRequest) + public Response SetHeadersOnDownstreamRequest(List claimsToThings, IEnumerable claims, DownstreamRequest downstreamRequest) { foreach (var config in claimsToThings) { diff --git a/src/Ocelot/Headers/AddHeadersToResponse.cs b/src/Ocelot/Headers/AddHeadersToResponse.cs index 4c6f48ce..40d829ee 100644 --- a/src/Ocelot/Headers/AddHeadersToResponse.cs +++ b/src/Ocelot/Headers/AddHeadersToResponse.cs @@ -1,23 +1,22 @@ namespace Ocelot.Headers { - using System; using System.Collections.Generic; using System.Net.Http; using Ocelot.Configuration.Creator; using Ocelot.Infrastructure; - using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; public class AddHeadersToResponse : IAddHeadersToResponse { - private IPlaceholders _placeholders; - private IOcelotLogger _logger; + private readonly IPlaceholders _placeholders; + private readonly IOcelotLogger _logger; public AddHeadersToResponse(IPlaceholders placeholders, IOcelotLoggerFactory factory) { _logger = factory.CreateLogger(); _placeholders = placeholders; } + public void Add(List addHeaders, HttpResponseMessage response) { foreach(var add in addHeaders) diff --git a/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs b/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs index fc3e8e4c..c4763746 100644 --- a/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs +++ b/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs @@ -5,6 +5,7 @@ using System.Net.Http; using Ocelot.Configuration; using Ocelot.Infrastructure; using Ocelot.Infrastructure.Extensions; +using Ocelot.Request.Middleware; using Ocelot.Responses; namespace Ocelot.Headers @@ -18,7 +19,7 @@ namespace Ocelot.Headers _placeholders = placeholders; } - public Response Replace(HttpResponseMessage response, List fAndRs, HttpRequestMessage request) + public Response Replace(HttpResponseMessage response, List fAndRs, DownstreamRequest request) { foreach (var f in fAndRs) { diff --git a/src/Ocelot/Headers/IAddHeadersToRequest.cs b/src/Ocelot/Headers/IAddHeadersToRequest.cs index fed2407c..c8b1c967 100644 --- a/src/Ocelot/Headers/IAddHeadersToRequest.cs +++ b/src/Ocelot/Headers/IAddHeadersToRequest.cs @@ -6,10 +6,11 @@ using Ocelot.Configuration; using Ocelot.Configuration.Creator; using Ocelot.Infrastructure.RequestData; + using Ocelot.Request.Middleware; using Ocelot.Responses; public interface IAddHeadersToRequest { - Response SetHeadersOnDownstreamRequest(List claimsToThings, IEnumerable claims, HttpRequestMessage downstreamRequest); + Response SetHeadersOnDownstreamRequest(List claimsToThings, IEnumerable claims, DownstreamRequest downstreamRequest); } } diff --git a/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs b/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs index 8e74d111..6c805b86 100644 --- a/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs +++ b/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs @@ -1,12 +1,13 @@ using System.Collections.Generic; using System.Net.Http; using Ocelot.Configuration; +using Ocelot.Request.Middleware; using Ocelot.Responses; namespace Ocelot.Headers { public interface IHttpResponseHeaderReplacer { - Response Replace(HttpResponseMessage response, List fAndRs, HttpRequestMessage httpRequestMessage); + Response Replace(HttpResponseMessage response, List fAndRs, DownstreamRequest httpRequestMessage); } } \ No newline at end of file diff --git a/src/Ocelot/Infrastructure/IPlaceholders.cs b/src/Ocelot/Infrastructure/IPlaceholders.cs index f95fb8b8..1d2bbfa5 100644 --- a/src/Ocelot/Infrastructure/IPlaceholders.cs +++ b/src/Ocelot/Infrastructure/IPlaceholders.cs @@ -1,4 +1,5 @@ using System.Net.Http; +using Ocelot.Request.Middleware; using Ocelot.Responses; namespace Ocelot.Infrastructure @@ -6,6 +7,6 @@ namespace Ocelot.Infrastructure public interface IPlaceholders { Response Get(string key); - Response Get(string key, HttpRequestMessage request); + Response Get(string key, DownstreamRequest request); } } \ No newline at end of file diff --git a/src/Ocelot/Infrastructure/Placeholders.cs b/src/Ocelot/Infrastructure/Placeholders.cs index b00f54fa..c43dea14 100644 --- a/src/Ocelot/Infrastructure/Placeholders.cs +++ b/src/Ocelot/Infrastructure/Placeholders.cs @@ -3,14 +3,15 @@ using System.Collections.Generic; using System.Net.Http; using Ocelot.Infrastructure.RequestData; using Ocelot.Middleware; +using Ocelot.Request.Middleware; using Ocelot.Responses; namespace Ocelot.Infrastructure { public class Placeholders : IPlaceholders { - private Dictionary>> _placeholders; - private Dictionary> _requestPlaceholders; + private readonly Dictionary>> _placeholders; + private readonly Dictionary> _requestPlaceholders; private readonly IBaseUrlFinder _finder; private readonly IRequestScopedDataRepository _repo; @@ -30,13 +31,13 @@ namespace Ocelot.Infrastructure return new OkResponse(traceId.Data); }); - _requestPlaceholders = new Dictionary>(); + _requestPlaceholders = new Dictionary>(); _requestPlaceholders.Add("{DownstreamBaseUrl}", x => { - var downstreamUrl = $"{x.RequestUri.Scheme}://{x.RequestUri.Host}"; + var downstreamUrl = $"{x.Scheme}://{x.Host}"; - if(x.RequestUri.Port != 80 && x.RequestUri.Port != 443) + if(x.Port != 80 && x.Port != 443) { - downstreamUrl = $"{downstreamUrl}:{x.RequestUri.Port}"; + downstreamUrl = $"{downstreamUrl}:{x.Port}"; } return $"{downstreamUrl}/"; @@ -57,7 +58,7 @@ namespace Ocelot.Infrastructure return new ErrorResponse(new CouldNotFindPlaceholderError(key)); } - public Response Get(string key, HttpRequestMessage request) + public Response Get(string key, DownstreamRequest request) { if(_requestPlaceholders.ContainsKey(key)) { @@ -67,4 +68,4 @@ namespace Ocelot.Infrastructure return new ErrorResponse(new CouldNotFindPlaceholderError(key)); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index 8c2e963a..82f792f9 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -1,12 +1,8 @@ using System; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.DownstreamRouteFinder.Middleware; -using Ocelot.Infrastructure.RequestData; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Logging; using Ocelot.Middleware; -using Ocelot.QueryStrings.Middleware; namespace Ocelot.LoadBalancer.Middleware { @@ -43,17 +39,13 @@ namespace Ocelot.LoadBalancer.Middleware return; } - var uriBuilder = new UriBuilder(context.DownstreamRequest.RequestUri); - - uriBuilder.Host = hostAndPort.Data.DownstreamHost; + context.DownstreamRequest.Host = hostAndPort.Data.DownstreamHost; if (hostAndPort.Data.DownstreamPort > 0) { - uriBuilder.Port = hostAndPort.Data.DownstreamPort; + context.DownstreamRequest.Port = hostAndPort.Data.DownstreamPort; } - context.DownstreamRequest.RequestUri = uriBuilder.Uri; - try { await _next.Invoke(context); diff --git a/src/Ocelot/Middleware/DownstreamContext.cs b/src/Ocelot/Middleware/DownstreamContext.cs index 1c805deb..9e054eb5 100644 --- a/src/Ocelot/Middleware/DownstreamContext.cs +++ b/src/Ocelot/Middleware/DownstreamContext.cs @@ -1,9 +1,11 @@ +using System; using System.Collections.Generic; using System.Net.Http; using Microsoft.AspNetCore.Http; using Ocelot.Configuration; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Errors; +using Ocelot.Request.Middleware; namespace Ocelot.Middleware { @@ -19,7 +21,7 @@ namespace Ocelot.Middleware public ServiceProviderConfiguration ServiceProviderConfiguration {get; set;} public HttpContext HttpContext { get; private set; } public DownstreamReRoute DownstreamReRoute { get; set; } - public HttpRequestMessage DownstreamRequest { get; set; } + public DownstreamRequest DownstreamRequest { get; set; } public HttpResponseMessage DownstreamResponse { get; set; } public List Errors { get;set; } public bool IsError => Errors.Count > 0; diff --git a/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs b/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs index 3bc0d6b0..9cb0db56 100644 --- a/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs +++ b/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs @@ -11,5 +11,6 @@ namespace Ocelot.Middleware.Pipeline IServiceProvider ApplicationServices { get; } OcelotPipelineBuilder Use(Func middleware); OcelotRequestDelegate Build(); + IOcelotPipelineBuilder New(); } } diff --git a/src/Ocelot/Middleware/Pipeline/MapWhenMiddleware.cs b/src/Ocelot/Middleware/Pipeline/MapWhenMiddleware.cs new file mode 100644 index 00000000..f05c35e4 --- /dev/null +++ b/src/Ocelot/Middleware/Pipeline/MapWhenMiddleware.cs @@ -0,0 +1,44 @@ +using System; +using System.Threading.Tasks; + +namespace Ocelot.Middleware.Pipeline +{ + public class MapWhenMiddleware + { + private readonly OcelotRequestDelegate _next; + private readonly MapWhenOptions _options; + + public MapWhenMiddleware(OcelotRequestDelegate next, MapWhenOptions options) + { + if (next == null) + { + throw new ArgumentNullException(nameof(next)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + _next = next; + _options = options; + } + + public async Task Invoke(DownstreamContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (_options.Predicate(context)) + { + await _options.Branch(context); + } + else + { + await _next(context); + } + } + } +} diff --git a/src/Ocelot/Middleware/Pipeline/MapWhenOptions.cs b/src/Ocelot/Middleware/Pipeline/MapWhenOptions.cs new file mode 100644 index 00000000..912688c3 --- /dev/null +++ b/src/Ocelot/Middleware/Pipeline/MapWhenOptions.cs @@ -0,0 +1,28 @@ +using System; + +namespace Ocelot.Middleware.Pipeline +{ + public class MapWhenOptions + { + private Func _predicate; + + public Func Predicate + { + get + { + return _predicate; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _predicate = value; + } + } + + public OcelotRequestDelegate Branch { get; set; } + } +} diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs index 1e37514c..5877ab62 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs @@ -19,6 +19,12 @@ namespace Ocelot.Middleware.Pipeline _middlewares = new List>(); } + public OcelotPipelineBuilder(IOcelotPipelineBuilder builder) + { + ApplicationServices = builder.ApplicationServices; + _middlewares = new List>(); + } + public IServiceProvider ApplicationServices { get; } public OcelotPipelineBuilder Use(Func middleware) @@ -42,5 +48,10 @@ namespace Ocelot.Middleware.Pipeline return app; } + + public IOcelotPipelineBuilder New() + { + return new OcelotPipelineBuilder(this); + } } } diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs index 422097fc..5989b1dd 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs @@ -12,6 +12,8 @@ using Microsoft.Extensions.DependencyInjection; namespace Ocelot.Middleware.Pipeline { + using Predicate = Func; + public static class OcelotPipelineBuilderExtensions { internal const string InvokeMethodName = "Invoke"; @@ -91,6 +93,35 @@ namespace Ocelot.Middleware.Pipeline }); } + public static IOcelotPipelineBuilder MapWhen(this IOcelotPipelineBuilder app, Predicate predicate, Action configuration) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + if (predicate == null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + var branchBuilder = app.New(); + configuration(branchBuilder); + var branch = branchBuilder.Build(); + + var options = new MapWhenOptions + { + Predicate = predicate, + Branch = branch, + }; + return app.Use(next => new MapWhenMiddleware(next, options).Invoke); + } + private static Func Compile(MethodInfo methodinfo, ParameterInfo[] parameters) { var middleware = typeof(T); diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs index 374b867f..f9a74235 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs @@ -15,18 +15,30 @@ using Ocelot.Request.Middleware; using Ocelot.Requester.Middleware; using Ocelot.RequestId.Middleware; using Ocelot.Responder.Middleware; +using Ocelot.WebSockets.Middleware; namespace Ocelot.Middleware.Pipeline { public static class OcelotPipelineExtensions { public static OcelotRequestDelegate BuildOcelotPipeline(this IOcelotPipelineBuilder builder, - OcelotPipelineConfiguration pipelineConfiguration = null) + OcelotPipelineConfiguration pipelineConfiguration) { // This is registered to catch any global exceptions that are not handled // It also sets the Request Id if anything is set globally builder.UseExceptionHandlerMiddleware(); + // If the request is for websockets upgrade we fork into a different pipeline + builder.MapWhen(context => context.HttpContext.WebSockets.IsWebSocketRequest, + app => + { + app.UseDownstreamRouteFinderMiddleware(); + app.UseDownstreamRequestInitialiser(); + app.UseLoadBalancingMiddleware(); + app.UseDownstreamUrlCreatorMiddleware(); + app.UseWebSocketsProxyMiddleware(); + }); + // Allow the user to respond with absolutely anything they want. builder.UseIfNotNull(pipelineConfiguration.PreErrorResponderMiddleware); diff --git a/src/Ocelot/QueryStrings/AddQueriesToRequest.cs b/src/Ocelot/QueryStrings/AddQueriesToRequest.cs index 74cc7696..3cc2abdf 100644 --- a/src/Ocelot/QueryStrings/AddQueriesToRequest.cs +++ b/src/Ocelot/QueryStrings/AddQueriesToRequest.cs @@ -7,6 +7,7 @@ using Ocelot.Responses; using System.Security.Claims; using System.Net.Http; using System; +using Ocelot.Request.Middleware; using Microsoft.Extensions.Primitives; using System.Text; @@ -21,9 +22,9 @@ namespace Ocelot.QueryStrings _claimsParser = claimsParser; } - public Response SetQueriesOnDownstreamRequest(List claimsToThings, IEnumerable claims, HttpRequestMessage downstreamRequest) + public Response SetQueriesOnDownstreamRequest(List claimsToThings, IEnumerable claims, DownstreamRequest downstreamRequest) { - var queryDictionary = ConvertQueryStringToDictionary(downstreamRequest.RequestUri.Query); + var queryDictionary = ConvertQueryStringToDictionary(downstreamRequest.Query); foreach (var config in claimsToThings) { @@ -46,11 +47,7 @@ namespace Ocelot.QueryStrings } } - var uriBuilder = new UriBuilder(downstreamRequest.RequestUri); - - uriBuilder.Query = ConvertDictionaryToQueryString(queryDictionary); - - downstreamRequest.RequestUri = uriBuilder.Uri; + downstreamRequest.Query = ConvertDictionaryToQueryString(queryDictionary); return new OkResponse(); } @@ -94,4 +91,4 @@ namespace Ocelot.QueryStrings return builder.ToString(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/QueryStrings/IAddQueriesToRequest.cs b/src/Ocelot/QueryStrings/IAddQueriesToRequest.cs index 34a6c2f5..bc017936 100644 --- a/src/Ocelot/QueryStrings/IAddQueriesToRequest.cs +++ b/src/Ocelot/QueryStrings/IAddQueriesToRequest.cs @@ -4,11 +4,12 @@ using Ocelot.Configuration; using Ocelot.Responses; using System.Net.Http; using System.Security.Claims; +using Ocelot.Request.Middleware; namespace Ocelot.QueryStrings { public interface IAddQueriesToRequest { - Response SetQueriesOnDownstreamRequest(List claimsToThings, IEnumerable claims, HttpRequestMessage downstreamRequest); + Response SetQueriesOnDownstreamRequest(List claimsToThings, IEnumerable claims, DownstreamRequest downstreamRequest); } } diff --git a/src/Ocelot/Request/Middleware/DownstreamRequest.cs b/src/Ocelot/Request/Middleware/DownstreamRequest.cs new file mode 100644 index 00000000..449b33cc --- /dev/null +++ b/src/Ocelot/Request/Middleware/DownstreamRequest.cs @@ -0,0 +1,69 @@ +namespace Ocelot.Request.Middleware +{ + using System; + using System.Net.Http; + using System.Net.Http.Headers; + + public class DownstreamRequest + { + private readonly HttpRequestMessage _request; + + public DownstreamRequest(HttpRequestMessage request) + { + _request = request; + Method = _request.Method.Method; + OriginalString = _request.RequestUri.OriginalString; + Scheme = _request.RequestUri.Scheme; + Host = _request.RequestUri.Host; + Port = _request.RequestUri.Port; + Headers = _request.Headers; + AbsolutePath = _request.RequestUri.AbsolutePath; + Query = _request.RequestUri.Query; + } + + public HttpRequestHeaders Headers { get; } + + public string Method { get; } + + public string OriginalString { get; } + + public string Scheme { get; set; } + + public string Host { get; set; } + + public int Port { get; set; } + + public string AbsolutePath { get; set; } + + public string Query { get; set; } + + public HttpRequestMessage ToHttpRequestMessage() + { + var uriBuilder = new UriBuilder + { + Port = Port, + Host = Host, + Path = AbsolutePath, + Query = Query, + Scheme = Scheme + }; + + _request.RequestUri = uriBuilder.Uri; + return _request; + } + + public string ToUri() + { + var uriBuilder = new UriBuilder + { + Port = Port, + Host = Host, + Path = AbsolutePath, + Query = Query, + Scheme = Scheme + }; + + return uriBuilder.Uri.AbsoluteUri; + } + } +} diff --git a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs index f14c1394..ecdb134a 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs @@ -1,5 +1,6 @@ namespace Ocelot.Request.Middleware { + using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Ocelot.DownstreamRouteFinder.Middleware; @@ -31,9 +32,9 @@ namespace Ocelot.Request.Middleware return; } - context.DownstreamRequest = downstreamRequest.Data; + context.DownstreamRequest = new DownstreamRequest(downstreamRequest.Data); await _next.Invoke(context); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs b/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs index 32ee6c68..7ae416b0 100644 --- a/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs +++ b/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs @@ -9,6 +9,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Collections.Generic; using Ocelot.DownstreamRouteFinder.Middleware; +using Ocelot.Request.Middleware; namespace Ocelot.RequestId.Middleware { @@ -82,7 +83,7 @@ namespace Ocelot.RequestId.Middleware return headers.TryGetValues(requestId.RequestIdKey, out value); } - private void AddRequestIdHeader(RequestId requestId, HttpRequestMessage httpRequestMessage) + private void AddRequestIdHeader(RequestId requestId, DownstreamRequest httpRequestMessage) { httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue); } diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index 6cbb3aec..348bb207 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -78,9 +78,7 @@ namespace Ocelot.Requester private string GetCacheKey(DownstreamContext request) { - var baseUrl = $"{request.DownstreamRequest.RequestUri.Scheme}://{request.DownstreamRequest.RequestUri.Authority}{request.DownstreamRequest.RequestUri.AbsolutePath}"; - - return baseUrl; + return request.DownstreamRequest.OriginalString; } } } diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index 4202f611..ddbcf119 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -32,7 +32,7 @@ namespace Ocelot.Requester try { - var response = await httpClient.SendAsync(context.DownstreamRequest); + var response = await httpClient.SendAsync(context.DownstreamRequest.ToHttpRequestMessage()); return new OkResponse(response); } catch (TimeoutRejectedException exception) diff --git a/src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs b/src/Ocelot/ServiceDiscovery/Configuration/ConsulRegistryConfiguration.cs similarity index 90% rename from src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs rename to src/Ocelot/ServiceDiscovery/Configuration/ConsulRegistryConfiguration.cs index ba389c05..9a9e5de8 100644 --- a/src/Ocelot/ServiceDiscovery/ConsulRegistryConfiguration.cs +++ b/src/Ocelot/ServiceDiscovery/Configuration/ConsulRegistryConfiguration.cs @@ -1,16 +1,16 @@ -namespace Ocelot.ServiceDiscovery -{ - public class ConsulRegistryConfiguration - { - public ConsulRegistryConfiguration(string hostName, int port, string keyOfServiceInConsul) - { - HostName = hostName; - Port = port; - KeyOfServiceInConsul = keyOfServiceInConsul; - } - - public string KeyOfServiceInConsul { get; private set; } - public string HostName { get; private set; } - public int Port { get; private set; } - } -} \ No newline at end of file +namespace Ocelot.ServiceDiscovery.Configuration +{ + public class ConsulRegistryConfiguration + { + public ConsulRegistryConfiguration(string hostName, int port, string keyOfServiceInConsul) + { + HostName = hostName; + Port = port; + KeyOfServiceInConsul = keyOfServiceInConsul; + } + + public string KeyOfServiceInConsul { get; private set; } + public string HostName { get; private set; } + public int Port { get; private set; } + } +} diff --git a/src/Ocelot/ServiceDiscovery/ServiceFabricConfiguration.cs b/src/Ocelot/ServiceDiscovery/Configuration/ServiceFabricConfiguration.cs similarity index 58% rename from src/Ocelot/ServiceDiscovery/ServiceFabricConfiguration.cs rename to src/Ocelot/ServiceDiscovery/Configuration/ServiceFabricConfiguration.cs index 7522a1e0..73211a5b 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceFabricConfiguration.cs +++ b/src/Ocelot/ServiceDiscovery/Configuration/ServiceFabricConfiguration.cs @@ -1,4 +1,4 @@ -namespace Ocelot.ServiceDiscovery +namespace Ocelot.ServiceDiscovery.Configuration { public class ServiceFabricConfiguration { @@ -9,8 +9,10 @@ ServiceName = serviceName; } - public string ServiceName { get; private set; } - public string HostName { get; private set; } - public int Port { get; private set; } + public string ServiceName { get; } + + public string HostName { get; } + + public int Port { get; } } } diff --git a/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs b/src/Ocelot/ServiceDiscovery/Errors/UnableToFindServiceDiscoveryProviderError.cs similarity index 86% rename from src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs rename to src/Ocelot/ServiceDiscovery/Errors/UnableToFindServiceDiscoveryProviderError.cs index 639e4659..a31ed2ee 100644 --- a/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs +++ b/src/Ocelot/ServiceDiscovery/Errors/UnableToFindServiceDiscoveryProviderError.cs @@ -1,12 +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 +using Ocelot.Errors; + +namespace Ocelot.ServiceDiscovery.Errors +{ + public class UnableToFindServiceDiscoveryProviderError : Error + { + public UnableToFindServiceDiscoveryProviderError(string message) + : base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError) + { + } + } +} diff --git a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs index 9f0bc93a..91e9c700 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProviderFactory.cs @@ -1,4 +1,5 @@ using Ocelot.Configuration; +using Ocelot.ServiceDiscovery.Providers; namespace Ocelot.ServiceDiscovery { @@ -6,4 +7,4 @@ namespace Ocelot.ServiceDiscovery { IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute); } -} \ No newline at end of file +} diff --git a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs b/src/Ocelot/ServiceDiscovery/Providers/ConfigurationServiceProvider.cs similarity index 88% rename from src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs rename to src/Ocelot/ServiceDiscovery/Providers/ConfigurationServiceProvider.cs index 28a296c5..04369996 100644 --- a/src/Ocelot/ServiceDiscovery/ConfigurationServiceProvider.cs +++ b/src/Ocelot/ServiceDiscovery/Providers/ConfigurationServiceProvider.cs @@ -1,21 +1,21 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Ocelot.Values; - -namespace Ocelot.ServiceDiscovery -{ - public class ConfigurationServiceProvider : IServiceDiscoveryProvider - { - private readonly List _services; - - public ConfigurationServiceProvider(List services) - { - _services = services; - } - - public async Task> Get() - { - return await Task.FromResult(_services); - } - } -} \ No newline at end of file +using System.Collections.Generic; +using System.Threading.Tasks; +using Ocelot.Values; + +namespace Ocelot.ServiceDiscovery.Providers +{ + public class ConfigurationServiceProvider : IServiceDiscoveryProvider + { + private readonly List _services; + + public ConfigurationServiceProvider(List services) + { + _services = services; + } + + public async Task> Get() + { + return await Task.FromResult(_services); + } + } +} diff --git a/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs similarity index 96% rename from src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs rename to src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs index ef882a50..775033fd 100644 --- a/src/Ocelot/ServiceDiscovery/ConsulServiceDiscoveryProvider.cs +++ b/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs @@ -1,83 +1,84 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Consul; -using Ocelot.Infrastructure.Extensions; -using Ocelot.Logging; -using Ocelot.Values; - -namespace Ocelot.ServiceDiscovery -{ - public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider - { - private readonly ConsulRegistryConfiguration _consulConfig; - private readonly IOcelotLogger _logger; - private readonly ConsulClient _consul; - private const string VersionPrefix = "version-"; - - public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration consulRegistryConfiguration, IOcelotLoggerFactory factory) - {; - _logger = factory.CreateLogger(); - - var consulHost = string.IsNullOrEmpty(consulRegistryConfiguration?.HostName) ? "localhost" : consulRegistryConfiguration.HostName; - - var consulPort = consulRegistryConfiguration?.Port ?? 8500; - - _consulConfig = new ConsulRegistryConfiguration(consulHost, consulPort, consulRegistryConfiguration?.KeyOfServiceInConsul); - - _consul = new ConsulClient(config => - { - config.Address = new Uri($"http://{_consulConfig.HostName}:{_consulConfig.Port}"); - }); - } - - public async Task> Get() - { - var queryResult = await _consul.Health.Service(_consulConfig.KeyOfServiceInConsul, string.Empty, true); - - var services = new List(); - - foreach (var serviceEntry in queryResult.Response) - { - if (IsValid(serviceEntry)) - { - services.Add(BuildService(serviceEntry)); - } - else - { - _logger.LogError($"Unable to use service Address: {serviceEntry.Service.Address} and Port: {serviceEntry.Service.Port} as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"); - } - } - - return services.ToList(); - } - - private Service BuildService(ServiceEntry serviceEntry) - { - return new Service( - serviceEntry.Service.Service, - new ServiceHostAndPort(serviceEntry.Service.Address, serviceEntry.Service.Port), - serviceEntry.Service.ID, - GetVersionFromStrings(serviceEntry.Service.Tags), - serviceEntry.Service.Tags ?? Enumerable.Empty()); - } - - private bool IsValid(ServiceEntry serviceEntry) - { - if (serviceEntry.Service.Address.Contains("http://") || serviceEntry.Service.Address.Contains("https://") || serviceEntry.Service.Port <= 0) - { - return false; - } - - return true; - } - - private string GetVersionFromStrings(IEnumerable strings) - { - return strings - ?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal)) - .TrimStart(VersionPrefix); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Consul; +using Ocelot.Infrastructure.Extensions; +using Ocelot.Logging; +using Ocelot.ServiceDiscovery.Configuration; +using Ocelot.Values; + +namespace Ocelot.ServiceDiscovery.Providers +{ + public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider + { + private readonly ConsulRegistryConfiguration _consulConfig; + private readonly IOcelotLogger _logger; + private readonly ConsulClient _consul; + private const string VersionPrefix = "version-"; + + public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration consulRegistryConfiguration, IOcelotLoggerFactory factory) + {; + _logger = factory.CreateLogger(); + + var consulHost = string.IsNullOrEmpty(consulRegistryConfiguration?.HostName) ? "localhost" : consulRegistryConfiguration.HostName; + + var consulPort = consulRegistryConfiguration?.Port ?? 8500; + + _consulConfig = new ConsulRegistryConfiguration(consulHost, consulPort, consulRegistryConfiguration?.KeyOfServiceInConsul); + + _consul = new ConsulClient(config => + { + config.Address = new Uri($"http://{_consulConfig.HostName}:{_consulConfig.Port}"); + }); + } + + public async Task> Get() + { + var queryResult = await _consul.Health.Service(_consulConfig.KeyOfServiceInConsul, string.Empty, true); + + var services = new List(); + + foreach (var serviceEntry in queryResult.Response) + { + if (IsValid(serviceEntry)) + { + services.Add(BuildService(serviceEntry)); + } + else + { + _logger.LogError($"Unable to use service Address: {serviceEntry.Service.Address} and Port: {serviceEntry.Service.Port} as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"); + } + } + + return services.ToList(); + } + + private Service BuildService(ServiceEntry serviceEntry) + { + return new Service( + serviceEntry.Service.Service, + new ServiceHostAndPort(serviceEntry.Service.Address, serviceEntry.Service.Port), + serviceEntry.Service.ID, + GetVersionFromStrings(serviceEntry.Service.Tags), + serviceEntry.Service.Tags ?? Enumerable.Empty()); + } + + private bool IsValid(ServiceEntry serviceEntry) + { + if (serviceEntry.Service.Address.Contains("http://") || serviceEntry.Service.Address.Contains("https://") || serviceEntry.Service.Port <= 0) + { + return false; + } + + return true; + } + + private string GetVersionFromStrings(IEnumerable strings) + { + return strings + ?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal)) + .TrimStart(VersionPrefix); + } + } +} diff --git a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/Providers/IServiceDiscoveryProvider.cs similarity index 79% rename from src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs rename to src/Ocelot/ServiceDiscovery/Providers/IServiceDiscoveryProvider.cs index 3d9887f3..ef28375e 100644 --- a/src/Ocelot/ServiceDiscovery/IServiceDiscoveryProvider.cs +++ b/src/Ocelot/ServiceDiscovery/Providers/IServiceDiscoveryProvider.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Ocelot.Values; - -namespace Ocelot.ServiceDiscovery -{ - public interface IServiceDiscoveryProvider - { - Task> Get(); - } -} \ No newline at end of file +using System.Collections.Generic; +using System.Threading.Tasks; +using Ocelot.Values; + +namespace Ocelot.ServiceDiscovery.Providers +{ + public interface IServiceDiscoveryProvider + { + Task> Get(); + } +} diff --git a/src/Ocelot/ServiceDiscovery/ServiceFabricServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/Providers/ServiceFabricServiceDiscoveryProvider.cs similarity index 90% rename from src/Ocelot/ServiceDiscovery/ServiceFabricServiceDiscoveryProvider.cs rename to src/Ocelot/ServiceDiscovery/Providers/ServiceFabricServiceDiscoveryProvider.cs index 257298b7..bc7ebf6e 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceFabricServiceDiscoveryProvider.cs +++ b/src/Ocelot/ServiceDiscovery/Providers/ServiceFabricServiceDiscoveryProvider.cs @@ -1,8 +1,9 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Ocelot.ServiceDiscovery.Configuration; using Ocelot.Values; -namespace Ocelot.ServiceDiscovery +namespace Ocelot.ServiceDiscovery.Providers { public class ServiceFabricServiceDiscoveryProvider : IServiceDiscoveryProvider { diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index 86b28a72..95201dfb 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using Ocelot.Configuration; using Ocelot.Logging; +using Ocelot.ServiceDiscovery.Configuration; +using Ocelot.ServiceDiscovery.Providers; using Ocelot.Values; namespace Ocelot.ServiceDiscovery diff --git a/src/Ocelot/Values/DownstreamPath.cs b/src/Ocelot/Values/DownstreamPath.cs index b4dd346b..f0821118 100644 --- a/src/Ocelot/Values/DownstreamPath.cs +++ b/src/Ocelot/Values/DownstreamPath.cs @@ -7,6 +7,6 @@ Value = value; } - public string Value { get; private set; } + public string Value { get; } } } diff --git a/src/Ocelot/Values/DownstreamUrl.cs b/src/Ocelot/Values/DownstreamUrl.cs deleted file mode 100644 index 48644595..00000000 --- a/src/Ocelot/Values/DownstreamUrl.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ocelot.Values -{ - public class DownstreamUrl - { - public DownstreamUrl(string value) - { - Value = value; - } - - public string Value { get; private set; } - } -} \ No newline at end of file diff --git a/src/Ocelot/Values/PathTemplate.cs b/src/Ocelot/Values/PathTemplate.cs index 6d7221a6..584f80ac 100644 --- a/src/Ocelot/Values/PathTemplate.cs +++ b/src/Ocelot/Values/PathTemplate.cs @@ -7,6 +7,6 @@ Value = value; } - public string Value { get; private set; } + public string Value { get; } } } diff --git a/src/Ocelot/Values/Service.cs b/src/Ocelot/Values/Service.cs index abf5449b..234b248b 100644 --- a/src/Ocelot/Values/Service.cs +++ b/src/Ocelot/Values/Service.cs @@ -5,9 +5,9 @@ namespace Ocelot.Values public class Service { public Service(string name, - ServiceHostAndPort hostAndPort, - string id, - string version, + ServiceHostAndPort hostAndPort, + string id, + string version, IEnumerable tags) { Name = name; @@ -17,14 +17,14 @@ namespace Ocelot.Values Tags = tags; } - public string Id { get; private set; } + public string Id { get; } - public string Name { get; private set; } + public string Name { get; } - public string Version { get; private set; } + public string Version { get; } - public IEnumerable Tags { get; private set; } + public IEnumerable Tags { get; } - public ServiceHostAndPort HostAndPort { get; private set; } + public ServiceHostAndPort HostAndPort { get; } } } diff --git a/src/Ocelot/Values/ServiceHostAndPort.cs b/src/Ocelot/Values/ServiceHostAndPort.cs index 135944b1..4e4271b8 100644 --- a/src/Ocelot/Values/ServiceHostAndPort.cs +++ b/src/Ocelot/Values/ServiceHostAndPort.cs @@ -8,7 +8,8 @@ DownstreamPort = downstreamPort; } - public string DownstreamHost { get; private set; } - public int DownstreamPort { get; private set; } + public string DownstreamHost { get; } + + public int DownstreamPort { get; } } } diff --git a/src/Ocelot/Values/UpstreamPathTemplate.cs b/src/Ocelot/Values/UpstreamPathTemplate.cs index 0cd44bc5..4b33a230 100644 --- a/src/Ocelot/Values/UpstreamPathTemplate.cs +++ b/src/Ocelot/Values/UpstreamPathTemplate.cs @@ -8,7 +8,8 @@ namespace Ocelot.Values Priority = priority; } - public string Template {get;} - public int Priority {get;} + public string Template { get; } + + public int Priority { get; } } -} \ No newline at end of file +} diff --git a/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs b/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs new file mode 100644 index 00000000..7a9e0ad0 --- /dev/null +++ b/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs @@ -0,0 +1,103 @@ +using System; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Ocelot.Logging; +using Ocelot.Middleware; + +namespace Ocelot.WebSockets.Middleware +{ + public class WebSocketsProxyMiddleware : OcelotMiddleware + { + private OcelotRequestDelegate _next; + private IOcelotLogger _logger; + + public WebSocketsProxyMiddleware(OcelotRequestDelegate next, + IOcelotLoggerFactory loggerFactory) + { + _next = next; + _logger = loggerFactory.CreateLogger(); + } + + public async Task Invoke(DownstreamContext context) + { + await Proxy(context.HttpContext, context.DownstreamRequest.ToUri()); + } + + private async Task Proxy(HttpContext context, string serverEndpoint) + { + var wsToUpstreamClient = await context.WebSockets.AcceptWebSocketAsync(); + + var wsToDownstreamService = new ClientWebSocket(); + var uri = new Uri(serverEndpoint); + await wsToDownstreamService.ConnectAsync(uri, CancellationToken.None); + + var receiveFromUpstreamSendToDownstream = Task.Run(async () => + { + var buffer = new byte[1024 * 4]; + + var receiveSegment = new ArraySegment(buffer); + + while (wsToUpstreamClient.State == WebSocketState.Open || wsToUpstreamClient.State == WebSocketState.CloseSent) + { + var result = await wsToUpstreamClient.ReceiveAsync(receiveSegment, CancellationToken.None); + + var sendSegment = new ArraySegment(buffer, 0, result.Count); + + if(result.MessageType == WebSocketMessageType.Close) + { + await wsToUpstreamClient.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", + CancellationToken.None); + + await wsToDownstreamService.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", + CancellationToken.None); + + break; + } + + await wsToDownstreamService.SendAsync(sendSegment, result.MessageType, result.EndOfMessage, + CancellationToken.None); + + if (wsToUpstreamClient.State != WebSocketState.Open) + { + await wsToDownstreamService.CloseAsync(WebSocketCloseStatus.Empty, "", + CancellationToken.None); + break; + } + } + }); + + var receiveFromDownstreamAndSendToUpstream = Task.Run(async () => + { + var buffer = new byte[1024 * 4]; + + while (wsToDownstreamService.State == WebSocketState.Open || wsToDownstreamService.State == WebSocketState.CloseSent) + { + if (wsToUpstreamClient.State != WebSocketState.Open) + { + break; + } + else + { + var receiveSegment = new ArraySegment(buffer); + var result = await wsToDownstreamService.ReceiveAsync(receiveSegment, CancellationToken.None); + + if (result.MessageType == WebSocketMessageType.Close) + { + break; + } + + var sendSegment = new ArraySegment(buffer, 0, result.Count); + + //send to upstream client + await wsToUpstreamClient.SendAsync(sendSegment, result.MessageType, result.EndOfMessage, + CancellationToken.None); + } + } + }); + + await Task.WhenAll(receiveFromDownstreamAndSendToUpstream, receiveFromUpstreamSendToDownstream); + } + } +} diff --git a/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddlewareExtensions.cs b/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddlewareExtensions.cs new file mode 100644 index 00000000..e973dfc3 --- /dev/null +++ b/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddlewareExtensions.cs @@ -0,0 +1,12 @@ +using Ocelot.Middleware.Pipeline; + +namespace Ocelot.WebSockets.Middleware +{ + public static class WebSocketsProxyMiddlewareExtensions + { + public static IOcelotPipelineBuilder UseWebSocketsProxyMiddleware(this IOcelotPipelineBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs b/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs index f60008bd..14c69f9a 100644 --- a/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs +++ b/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs @@ -1,8 +1,6 @@ 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; @@ -28,7 +26,7 @@ namespace Ocelot.AcceptanceTests } [Fact] - public void should_use_service_discovery_and_load_balance_request() + public void should_load_balance_request() { var downstreamServiceOneUrl = "http://localhost:50881"; var downstreamServiceTwoUrl = "http://localhost:50892"; @@ -74,18 +72,6 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } - private void ThenOnlyOneServiceHasBeenCalled() - { - _counterOne.ShouldBe(10); - _counterTwo.ShouldBe(0); - } - - private void GivenIResetCounters() - { - _counterOne = 0; - _counterTwo = 0; - } - private void ThenBothServicesCalledRealisticAmountOfTimes(int bottom, int top) { _counterOne.ShouldBeInRange(bottom, top); @@ -121,7 +107,7 @@ namespace Ocelot.AcceptanceTests context.Response.StatusCode = statusCode; await context.Response.WriteAsync(response); } - catch (System.Exception exception) + catch (Exception exception) { await context.Response.WriteAsync(exception.StackTrace); } diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 1ee6f9bc..cca5ac18 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -40,12 +40,49 @@ namespace Ocelot.AcceptanceTests public string RequestIdKey = "OcRequestId"; private readonly Random _random; private IWebHostBuilder _webHostBuilder; + private WebHostBuilder _ocelotBuilder; + private IWebHost _ocelotHost; public Steps() { _random = new Random(); } + public async Task StartFakeOcelotWithWebSockets() + { + _ocelotBuilder = new WebHostBuilder(); + _ocelotBuilder.ConfigureServices(s => + { + s.AddSingleton(_ocelotBuilder); + s.AddOcelot(); + }); + _ocelotBuilder.UseKestrel() + .UseUrls("http://localhost:5000") + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + }) + .Configure(app => + { + app.UseWebSockets(); + app.UseOcelot().Wait(); + }) + .UseIISIntegration(); + _ocelotHost = _ocelotBuilder.Build(); + await _ocelotHost.StartAsync(); + } + public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) { var configurationPath = TestConfiguration.ConfigurationPath; @@ -698,6 +735,7 @@ namespace Ocelot.AcceptanceTests { _ocelotClient?.Dispose(); _ocelotServer?.Dispose(); + _ocelotHost?.Dispose(); } public void ThenTheRequestIdIsReturned() diff --git a/test/Ocelot.AcceptanceTests/WebSocketTests.cs b/test/Ocelot.AcceptanceTests/WebSocketTests.cs new file mode 100644 index 00000000..8137649a --- /dev/null +++ b/test/Ocelot.AcceptanceTests/WebSocketTests.cs @@ -0,0 +1,487 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Consul; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class WebSocketTests : IDisposable + { + private IWebHost _firstDownstreamHost; + private IWebHost _secondDownstreamHost; + private readonly List _secondRecieved; + private readonly List _firstRecieved; + private readonly List _serviceEntries; + private readonly Steps _steps; + private IWebHost _fakeConsulBuilder; + + public WebSocketTests() + { + _steps = new Steps(); + _firstRecieved = new List(); + _secondRecieved = new List(); + _serviceEntries = new List(); + } + + [Fact] + public async Task should_proxy_websocket_input_to_downstream_service() + { + var downstreamPort = 5001; + var downstreamHost = "localhost"; + + var config = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + UpstreamPathTemplate = "/", + DownstreamPathTemplate = "/ws", + DownstreamScheme = "ws", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = downstreamHost, + Port = downstreamPort + } + } + } + } + }; + + this.Given(_ => _steps.GivenThereIsAConfiguration(config)) + .And(_ => _steps.StartFakeOcelotWithWebSockets()) + .And(_ => StartFakeDownstreamService($"http://{downstreamHost}:{downstreamPort}", "/ws")) + .When(_ => StartClient("ws://localhost:5000/")) + .Then(_ => _firstRecieved.Count.ShouldBe(10)) + .BDDfy(); + } + + [Fact] + public async Task should_proxy_websocket_input_to_downstream_service_and_use_load_balancer() + { + var downstreamPort = 5005; + var downstreamHost = "localhost"; + var secondDownstreamPort = 5006; + var secondDownstreamHost = "localhost"; + + var config = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + UpstreamPathTemplate = "/", + DownstreamPathTemplate = "/ws", + DownstreamScheme = "ws", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = downstreamHost, + Port = downstreamPort + }, + new FileHostAndPort + { + Host = secondDownstreamHost, + Port = secondDownstreamPort + } + }, + LoadBalancer = "RoundRobin" + } + } + }; + + this.Given(_ => _steps.GivenThereIsAConfiguration(config)) + .And(_ => _steps.StartFakeOcelotWithWebSockets()) + .And(_ => StartFakeDownstreamService($"http://{downstreamHost}:{downstreamPort}", "/ws")) + .And(_ => StartSecondFakeDownstreamService($"http://{secondDownstreamHost}:{secondDownstreamPort}","/ws")) + .When(_ => WhenIStartTheClients()) + .Then(_ => ThenBothDownstreamServicesAreCalled()) + .BDDfy(); + } + + [Fact] + public async Task should_proxy_websocket_input_to_downstream_service_and_use_service_discovery_and_load_balancer() + { + var downstreamPort = 5007; + var downstreamHost = "localhost"; + + var secondDownstreamPort = 5008; + var secondDownstreamHost = "localhost"; + + var serviceName = "websockets"; + var consulPort = 8509; + var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; + var serviceEntryOne = new ServiceEntry() + { + Service = new AgentService() + { + Service = serviceName, + Address = downstreamHost, + Port = downstreamPort, + ID = Guid.NewGuid().ToString(), + Tags = new string[0] + }, + }; + var serviceEntryTwo = new ServiceEntry() + { + Service = new AgentService() + { + Service = serviceName, + Address = secondDownstreamHost, + Port = secondDownstreamPort, + ID = Guid.NewGuid().ToString(), + Tags = new string[0] + }, + }; + + var config = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + UpstreamPathTemplate = "/", + DownstreamPathTemplate = "/ws", + DownstreamScheme = "ws", + LoadBalancer = "RoundRobin", + ServiceName = serviceName, + UseServiceDiscovery = true + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "localhost", + Port = consulPort, + Type = "consul" + } + } + }; + + this.Given(_ => _steps.GivenThereIsAConfiguration(config)) + .And(_ => _steps.StartFakeOcelotWithWebSockets()) + .And(_ => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) + .And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) + .And(_ => StartFakeDownstreamService($"http://{downstreamHost}:{downstreamPort}", "/ws")) + .And(_ => StartSecondFakeDownstreamService($"http://{secondDownstreamHost}:{secondDownstreamPort}", "/ws")) + .When(_ => WhenIStartTheClients()) + .Then(_ => ThenBothDownstreamServicesAreCalled()) + .BDDfy(); + } + + private void ThenBothDownstreamServicesAreCalled() + { + _firstRecieved.Count.ShouldBe(10); + _firstRecieved.ForEach(x => + { + x.ShouldBe("test"); + }); + + _secondRecieved.Count.ShouldBe(10); + _secondRecieved.ForEach(x => + { + x.ShouldBe("chocolate"); + }); + } + + private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries) + { + foreach (var serviceEntry in serviceEntries) + { + _serviceEntries.Add(serviceEntry); + } + } + + private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName) + { + _fakeConsulBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + if (context.Request.Path.Value == $"/v1/health/service/{serviceName}") + { + await context.Response.WriteJsonAsync(_serviceEntries); + } + }); + }) + .Build(); + + _fakeConsulBuilder.Start(); + } + + private async Task WhenIStartTheClients() + { + var firstClient = StartClient("ws://localhost:5000/"); + + var secondClient = StartSecondClient("ws://localhost:5000/"); + + await Task.WhenAll(firstClient, secondClient); + } + + private async Task StartClient(string url) + { + var client = new ClientWebSocket(); + + await client.ConnectAsync(new Uri(url), CancellationToken.None); + + var sending = Task.Run(async () => + { + string line = "test"; + for (int i = 0; i < 10; i++) + { + var bytes = Encoding.UTF8.GetBytes(line); + + await client.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, + CancellationToken.None); + await Task.Delay(10); + } + + await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + }); + + var receiving = Task.Run(async () => + { + var buffer = new byte[1024 * 4]; + + while (true) + { + var result = await client.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + + if (result.MessageType == WebSocketMessageType.Text) + { + _firstRecieved.Add(Encoding.UTF8.GetString(buffer, 0, result.Count)); + } + + else if (result.MessageType == WebSocketMessageType.Close) + { + await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + break; + } + } + }); + + await Task.WhenAll(sending, receiving); + } + + private async Task StartSecondClient(string url) + { + await Task.Delay(500); + + var client = new ClientWebSocket(); + + await client.ConnectAsync(new Uri(url), CancellationToken.None); + + var sending = Task.Run(async () => + { + string line = "test"; + for (int i = 0; i < 10; i++) + { + var bytes = Encoding.UTF8.GetBytes(line); + + await client.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, + CancellationToken.None); + await Task.Delay(10); + } + + await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + }); + + var receiving = Task.Run(async () => + { + var buffer = new byte[1024 * 4]; + + while (true) + { + var result = await client.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + + if (result.MessageType == WebSocketMessageType.Text) + { + _secondRecieved.Add(Encoding.UTF8.GetString(buffer, 0, result.Count)); + } + + else if (result.MessageType == WebSocketMessageType.Close) + { + await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + break; + } + } + }); + + await Task.WhenAll(sending, receiving); + } + + + private async Task StartFakeDownstreamService(string url, string path) + { + _firstDownstreamHost = new WebHostBuilder() + .ConfigureServices(s => { }).UseKestrel() + .UseUrls(url) + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddEnvironmentVariables(); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + }) + .Configure(app => + { + app.UseWebSockets(); + app.Use(async (context, next) => + { + if (context.Request.Path == path) + { + if (context.WebSockets.IsWebSocketRequest) + { + WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(); + await Echo(webSocket); + } + else + { + context.Response.StatusCode = 400; + } + } + else + { + await next(); + } + }); + }) + .UseIISIntegration().Build(); + await _firstDownstreamHost.StartAsync(); + } + + + private async Task StartSecondFakeDownstreamService(string url, string path) + { + _secondDownstreamHost = new WebHostBuilder() + .ConfigureServices(s => { }).UseKestrel() + .UseUrls(url) + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddEnvironmentVariables(); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + }) + .Configure(app => + { + app.UseWebSockets(); + app.Use(async (context, next) => + { + if (context.Request.Path == path) + { + if (context.WebSockets.IsWebSocketRequest) + { + WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(); + await Message(webSocket); + } + else + { + context.Response.StatusCode = 400; + } + } + else + { + await next(); + } + }); + }) + .UseIISIntegration().Build(); + await _secondDownstreamHost.StartAsync(); + } + + + private async Task Echo(WebSocket webSocket) + { + try + { + var buffer = new byte[1024 * 4]; + + var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + + while (!result.CloseStatus.HasValue) + { + await webSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); + + result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + } + + await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + private async Task Message(WebSocket webSocket) + { + try + { + var buffer = new byte[1024 * 4]; + + var bytes = Encoding.UTF8.GetBytes("chocolate"); + + var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + + while (!result.CloseStatus.HasValue) + { + await webSocket.SendAsync(new ArraySegment(bytes), result.MessageType, result.EndOfMessage, CancellationToken.None); + + result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + } + + await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + public void Dispose() + { + _steps.Dispose(); + _firstDownstreamHost?.Dispose(); + _secondDownstreamHost?.Dispose(); + _fakeConsulBuilder?.Dispose(); + } + } +} diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs index 210b1cb5..c1bc9b9d 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs @@ -43,7 +43,7 @@ namespace Ocelot.UnitTests.Cache }); _cacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _downstreamContext.DownstreamRequest = new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"); + _downstreamContext.DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123")); _next = context => Task.CompletedTask; _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager, _regionCreator); } diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index 78911787..83a4744d 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -40,7 +40,7 @@ namespace Ocelot.UnitTests.Cache _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; - _downstreamContext.DownstreamRequest = new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"); + _downstreamContext.DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123")); } [Fact] diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index c226f245..57177126 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -20,6 +20,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator using Xunit; using Shouldly; using Microsoft.AspNetCore.Http; + using Ocelot.Request.Middleware; public class DownstreamUrlCreatorMiddlewareTests { @@ -30,6 +31,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator private DownstreamUrlCreatorMiddleware _middleware; private DownstreamContext _downstreamContext; private OcelotRequestDelegate _next; + private HttpRequestMessage _request; public DownstreamUrlCreatorMiddlewareTests() { @@ -38,7 +40,8 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _downstreamUrlTemplateVariableReplacer = new Mock(); - _downstreamContext.DownstreamRequest = new HttpRequestMessage(HttpMethod.Get, "https://my.url/abc/?q=123"); + _request = new HttpRequestMessage(HttpMethod.Get, "https://my.url/abc/?q=123"); + _downstreamContext.DownstreamRequest = new DownstreamRequest(_request); _next = context => Task.CompletedTask; } @@ -208,7 +211,9 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator private void GivenTheDownstreamRequestUriIs(string uri) { - _downstreamContext.DownstreamRequest.RequestUri = new Uri(uri); + _request.RequestUri = new Uri(uri); + //todo - not sure if needed + _downstreamContext.DownstreamRequest = new DownstreamRequest(_request); } private void GivenTheUrlReplacerWillReturn(string path) @@ -221,7 +226,7 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator private void ThenTheDownstreamRequestUriIs(string expectedUri) { - _downstreamContext.DownstreamRequest.RequestUri.OriginalString.ShouldBe(expectedUri); + _downstreamContext.DownstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri); } } } diff --git a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestTests.cs b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestTests.cs index 0a8290bc..8e323bb2 100644 --- a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestTests.cs +++ b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestTests.cs @@ -11,6 +11,7 @@ using Shouldly; using TestStack.BDDfy; using Xunit; using System.Net.Http; +using Ocelot.Request.Middleware; namespace Ocelot.UnitTests.Headers { @@ -18,7 +19,7 @@ namespace Ocelot.UnitTests.Headers { private readonly AddHeadersToRequest _addHeadersToRequest; private readonly Mock _parser; - private readonly HttpRequestMessage _downstreamRequest; + private readonly DownstreamRequest _downstreamRequest; private List _claims; private List _configuration; private Response _result; @@ -28,7 +29,7 @@ namespace Ocelot.UnitTests.Headers { _parser = new Mock(); _addHeadersToRequest = new AddHeadersToRequest(_parser.Object); - _downstreamRequest = new HttpRequestMessage(); + _downstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); } [Fact] diff --git a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs index c49bf172..4992479b 100644 --- a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs @@ -16,6 +16,7 @@ using Ocelot.Middleware; namespace Ocelot.UnitTests.Headers { using System.Threading.Tasks; + using Ocelot.Request.Middleware; public class HttpHeadersTransformationMiddlewareTests { @@ -68,7 +69,7 @@ namespace Ocelot.UnitTests.Headers private void GivenTheDownstreamRequestIs() { - _downstreamContext.DownstreamRequest = new HttpRequestMessage(); + _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); } private void GivenTheHttpResponseMessageIs() @@ -97,7 +98,7 @@ namespace Ocelot.UnitTests.Headers private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly() { - _postReplacer.Verify(x => x.Replace(It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once); + _postReplacer.Verify(x => x.Replace(It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once); } private void GivenTheFollowingRequest() diff --git a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs index 7f96b247..7a448118 100644 --- a/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpRequestHeadersBuilderMiddlewareTests.cs @@ -14,6 +14,7 @@ namespace Ocelot.UnitTests.Headers using Ocelot.Headers; using Ocelot.Headers.Middleware; using Ocelot.Logging; + using Ocelot.Request.Middleware; using Ocelot.Responses; using TestStack.BDDfy; using Xunit; @@ -37,7 +38,7 @@ namespace Ocelot.UnitTests.Headers _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; _middleware = new HttpRequestHeadersBuilderMiddleware(_next, _loggerFactory.Object, _addHeaders.Object); - _downstreamContext.DownstreamRequest = new HttpRequestMessage(); + _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); } [Fact] @@ -81,7 +82,7 @@ namespace Ocelot.UnitTests.Headers .Setup(x => x.SetHeadersOnDownstreamRequest( It.IsAny>(), It.IsAny>(), - It.IsAny())) + It.IsAny())) .Returns(new OkResponse()); } diff --git a/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs b/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs index ca97de4f..6eefdc4c 100644 --- a/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs @@ -11,6 +11,7 @@ using Moq; using Ocelot.Infrastructure; using Ocelot.Middleware; using Ocelot.Infrastructure.RequestData; +using Ocelot.Request.Middleware; namespace Ocelot.UnitTests.Headers { @@ -21,7 +22,7 @@ namespace Ocelot.UnitTests.Headers private HttpResponseHeaderReplacer _replacer; private List _headerFindAndReplaces; private Response _result; - private HttpRequestMessage _request; + private DownstreamRequest _request; private Mock _finder; private Mock _repo; @@ -69,7 +70,7 @@ namespace Ocelot.UnitTests.Headers { var downstreamUrl = "http://downstream.com/"; - var request = new HttpRequestMessage(); + var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); request.RequestUri = new System.Uri(downstreamUrl); var response = new HttpResponseMessage(); @@ -91,7 +92,7 @@ namespace Ocelot.UnitTests.Headers { var downstreamUrl = "http://downstream.com/"; - var request = new HttpRequestMessage(); + var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); request.RequestUri = new System.Uri(downstreamUrl); var response = new HttpResponseMessage(); @@ -113,7 +114,7 @@ namespace Ocelot.UnitTests.Headers { var downstreamUrl = "http://downstream.com/test/product"; - var request = new HttpRequestMessage(); + var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); request.RequestUri = new System.Uri(downstreamUrl); var response = new HttpResponseMessage(); @@ -135,7 +136,7 @@ namespace Ocelot.UnitTests.Headers { var downstreamUrl = "http://downstream.com/test/product"; - var request = new HttpRequestMessage(); + var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); request.RequestUri = new System.Uri(downstreamUrl); var response = new HttpResponseMessage(); @@ -157,7 +158,7 @@ namespace Ocelot.UnitTests.Headers { var downstreamUrl = "http://downstream.com:123/test/product"; - var request = new HttpRequestMessage(); + var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); request.RequestUri = new System.Uri(downstreamUrl); var response = new HttpResponseMessage(); @@ -179,7 +180,7 @@ namespace Ocelot.UnitTests.Headers { var downstreamUrl = "http://downstream.com:123/test/product"; - var request = new HttpRequestMessage(); + var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); request.RequestUri = new System.Uri(downstreamUrl); var response = new HttpResponseMessage(); @@ -198,7 +199,7 @@ namespace Ocelot.UnitTests.Headers private void GivenTheRequestIs(HttpRequestMessage request) { - _request = request; + _request = new DownstreamRequest(request); } private void ThenTheHeadersAreNotReplaced() diff --git a/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs b/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs index 6acd3655..51cbffef 100644 --- a/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs +++ b/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs @@ -4,6 +4,7 @@ using Moq; using Ocelot.Infrastructure; using Ocelot.Infrastructure.RequestData; using Ocelot.Middleware; +using Ocelot.Request.Middleware; using Ocelot.Responses; using Shouldly; using Xunit; @@ -43,8 +44,9 @@ namespace Ocelot.UnitTests.Infrastructure [Fact] public void should_return_downstream_base_url_when_port_is_not_80_or_443() { - var request = new HttpRequestMessage(); - request.RequestUri = new Uri("http://www.bbc.co.uk"); + var httpRequest = new HttpRequestMessage(); + httpRequest.RequestUri = new Uri("http://www.bbc.co.uk"); + var request = new DownstreamRequest(httpRequest); var result = _placeholders.Get("{DownstreamBaseUrl}", request); result.Data.ShouldBe("http://www.bbc.co.uk/"); } @@ -53,8 +55,9 @@ namespace Ocelot.UnitTests.Infrastructure [Fact] public void should_return_downstream_base_url_when_port_is_80_or_443() { - var request = new HttpRequestMessage(); - request.RequestUri = new Uri("http://www.bbc.co.uk:123"); + var httpRequest = new HttpRequestMessage(); + httpRequest.RequestUri = new Uri("http://www.bbc.co.uk:123"); + var request = new DownstreamRequest(httpRequest); var result = _placeholders.Get("{DownstreamBaseUrl}", request); result.Data.ShouldBe("http://www.bbc.co.uk:123/"); } @@ -62,7 +65,8 @@ namespace Ocelot.UnitTests.Infrastructure [Fact] public void should_return_key_does_not_exist_for_http_request_message() { - var result = _placeholders.Get("{Test}", new System.Net.Http.HttpRequestMessage()); + var request = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://west.com")); + var result = _placeholders.Get("{Test}", request); result.IsError.ShouldBeTrue(); result.Errors[0].Message.ShouldBe("Unable to find placeholder called {Test}"); } diff --git a/test/Ocelot.UnitTests/Infrastructure/StringExtensionsTests.cs b/test/Ocelot.UnitTests/Infrastructure/StringExtensionsTests.cs new file mode 100644 index 00000000..88632675 --- /dev/null +++ b/test/Ocelot.UnitTests/Infrastructure/StringExtensionsTests.cs @@ -0,0 +1,29 @@ +using Xunit; +using Ocelot.Infrastructure.Extensions; +using Shouldly; + +namespace Ocelot.UnitTests.Infrastructure +{ + public class StringExtensionsTests + { + [Fact] + public void should_trim_start() + { + var test = "/string"; + + test = test.TrimStart("/"); + + test.ShouldBe("string"); + } + + [Fact] + public void should_return_source() + { + var test = "string"; + + test = test.LastCharAsForwardSlash(); + + test.ShouldBe("string/"); + } + } +} diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index 5d7fa8b6..da439976 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -5,6 +5,7 @@ using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.ServiceDiscovery; using Shouldly; using System.Collections.Generic; +using Ocelot.ServiceDiscovery.Providers; using TestStack.BDDfy; using Xunit; diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index e03fe6e1..a51b5cff 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -13,6 +13,7 @@ namespace Ocelot.UnitTests.LoadBalancer using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.LoadBalancer.Middleware; using Ocelot.Logging; + using Ocelot.Request.Middleware; using Ocelot.Responses; using Ocelot.Values; using Shouldly; @@ -39,13 +40,13 @@ namespace Ocelot.UnitTests.LoadBalancer _loadBalancerHouse = new Mock(); _loadBalancer = new Mock(); _loadBalancerHouse = new Mock(); - _downstreamRequest = new HttpRequestMessage(HttpMethod.Get, ""); + _downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "http://test.com/"); _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; - _downstreamContext.DownstreamRequest = _downstreamRequest; + _downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest); } [Fact] @@ -122,6 +123,7 @@ namespace Ocelot.UnitTests.LoadBalancer private void GivenTheDownStreamUrlIs(string downstreamUrl) { _downstreamRequest.RequestUri = new System.Uri(downstreamUrl); + _downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest); } private void GivenTheLoadBalancerReturnsAnError() @@ -185,7 +187,7 @@ namespace Ocelot.UnitTests.LoadBalancer private void ThenTheDownstreamUrlIsReplacedWith(string expectedUri) { - _downstreamContext.DownstreamRequest.RequestUri.OriginalString.ShouldBe(expectedUri); + _downstreamContext.DownstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri); } } } diff --git a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs b/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs index 27d1b7e0..f0a2b224 100644 --- a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs +++ b/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs @@ -9,6 +9,7 @@ using Ocelot.Configuration.Builder; using Ocelot.Errors; using Ocelot.Middleware; using Ocelot.Middleware.Multiplexer; +using Ocelot.Request.Middleware; using Ocelot.UnitTests.Responder; using Shouldly; using TestStack.BDDfy; @@ -48,7 +49,7 @@ namespace Ocelot.UnitTests.Middleware new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Bill says hi") }, DownstreamReRoute = billDownstreamReRoute, Errors = new List { new AnyError() }, - DownstreamRequest = new HttpRequestMessage(HttpMethod.Get, new Uri("http://www.bbc.co.uk")), + DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, new Uri("http://www.bbc.co.uk"))), }; var downstreamContexts = new List { billDownstreamContext }; diff --git a/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs b/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs index bebe4307..83c486f2 100644 --- a/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs @@ -12,24 +12,27 @@ using TestStack.BDDfy; using Xunit; using System.Net.Http; using System; +using Ocelot.Request.Middleware; namespace Ocelot.UnitTests.QueryStrings { public class AddQueriesToRequestTests { private readonly AddQueriesToRequest _addQueriesToRequest; - private HttpRequestMessage _downstreamRequest; + private DownstreamRequest _downstreamRequest; private readonly Mock _parser; private List _configuration; private List _claims; private Response _result; private Response _claimValue; + private HttpRequestMessage _request; public AddQueriesToRequestTests() { + _request = new HttpRequestMessage(HttpMethod.Post, "http://my.url/abc?q=123"); _parser = new Mock(); _addQueriesToRequest = new AddQueriesToRequest(_parser.Object); - _downstreamRequest = new HttpRequestMessage(HttpMethod.Post, "http://my.url/abc?q=123"); + _downstreamRequest = new DownstreamRequest(_request); } [Fact] @@ -78,7 +81,7 @@ namespace Ocelot.UnitTests.QueryStrings private void TheTheQueryStringIs(string expected) { - _downstreamRequest.RequestUri.Query.ShouldBe(expected); + _downstreamRequest.Query.ShouldBe(expected); } [Fact] @@ -123,7 +126,7 @@ namespace Ocelot.UnitTests.QueryStrings private void ThenTheQueryIsAdded() { - var queries = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(_downstreamRequest.RequestUri.OriginalString); + var queries = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(_downstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString); var query = queries.First(x => x.Key == "query-key"); query.Value.First().ShouldBe(_claimValue.Data); } @@ -140,15 +143,18 @@ namespace Ocelot.UnitTests.QueryStrings private void GivenTheDownstreamRequestHasQueryString(string queryString) { - _downstreamRequest = new HttpRequestMessage(HttpMethod.Post, $"http://my.url/abc{queryString}"); + _request = new HttpRequestMessage(HttpMethod.Post, $"http://my.url/abc{queryString}"); + _downstreamRequest = new DownstreamRequest(_request); } private void GivenTheDownstreamRequestHasQueryString(string key, string value) { var newUri = Microsoft.AspNetCore.WebUtilities.QueryHelpers - .AddQueryString(_downstreamRequest.RequestUri.OriginalString, key, value); + .AddQueryString(_downstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString, key, value); - _downstreamRequest.RequestUri = new Uri(newUri); + _request.RequestUri = new Uri(newUri); + //todo - might not need to instanciate + _downstreamRequest = new DownstreamRequest(_request); } private void GivenTheClaimParserReturns(Response claimValue) diff --git a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs index 163a0411..e4d95028 100644 --- a/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/QueryStringBuilderMiddlewareTests.cs @@ -18,6 +18,7 @@ namespace Ocelot.UnitTests.QueryStrings using System.Security.Claims; using Microsoft.AspNetCore.Http; using System.Threading.Tasks; + using Ocelot.Request.Middleware; public class QueryStringBuilderMiddlewareTests { @@ -36,7 +37,7 @@ namespace Ocelot.UnitTests.QueryStrings _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; _addQueries = new Mock(); - _downstreamContext.DownstreamRequest = new HttpRequestMessage(); + _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); _middleware = new QueryStringBuilderMiddleware(_next, _loggerFactory.Object, _addQueries.Object); } @@ -74,7 +75,7 @@ namespace Ocelot.UnitTests.QueryStrings .Setup(x => x.SetQueriesOnDownstreamRequest( It.IsAny>(), It.IsAny>(), - It.IsAny())) + It.IsAny())) .Returns(new OkResponse()); } diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index 819e38dd..7d3cac1a 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -18,6 +18,7 @@ namespace Ocelot.UnitTests.RateLimit using Microsoft.Extensions.Caching.Memory; using System.IO; using System.Threading.Tasks; + using Ocelot.Request.Middleware; public class ClientRateLimitMiddlewareTests { @@ -100,7 +101,7 @@ namespace Ocelot.UnitTests.RateLimit { var request = new HttpRequestMessage(new HttpMethod("GET"), _url); request.Headers.Add("ClientId", clientId); - _downstreamContext.DownstreamRequest = request; + _downstreamContext.DownstreamRequest = new DownstreamRequest(request); _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); _responseStatusCode = (int)_downstreamContext.HttpContext.Response.StatusCode; @@ -115,7 +116,7 @@ namespace Ocelot.UnitTests.RateLimit { var request = new HttpRequestMessage(new HttpMethod("GET"), _url); request.Headers.Add("ClientId", clientId); - _downstreamContext.DownstreamRequest = request; + _downstreamContext.DownstreamRequest = new DownstreamRequest(request); _downstreamContext.HttpContext.Request.Headers.TryAdd("ClientId", clientId); _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); diff --git a/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs index 2bd26a4b..d37efc42 100644 --- a/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs @@ -88,7 +88,7 @@ namespace Ocelot.UnitTests.Request private void GivenTheMapperWillReturnAMappedRequest() { - _mappedRequest = new OkResponse(new HttpRequestMessage()); + _mappedRequest = new OkResponse(new HttpRequestMessage(HttpMethod.Get, "http://www.bbc.co.uk")); _requestMapper .Setup(rm => rm.Map(It.IsAny())) diff --git a/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs b/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs index e27fc50f..91c1e15c 100644 --- a/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs @@ -20,6 +20,7 @@ namespace Ocelot.UnitTests.RequestId using Shouldly; using TestStack.BDDfy; using Xunit; + using Ocelot.Request.Middleware; public class ReRouteRequestIdMiddlewareTests { @@ -35,7 +36,7 @@ namespace Ocelot.UnitTests.RequestId public ReRouteRequestIdMiddlewareTests() { - _downstreamRequest = new HttpRequestMessage(); + _downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); _repo = new Mock(); _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _loggerFactory = new Mock(); @@ -47,7 +48,7 @@ namespace Ocelot.UnitTests.RequestId return Task.CompletedTask; }; _middleware = new ReRouteRequestIdMiddleware(_next, _loggerFactory.Object, _repo.Object); - _downstreamContext.DownstreamRequest = _downstreamRequest; + _downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest); } [Fact] diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index 7368fac8..c5895c1d 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -14,6 +14,7 @@ using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Logging; using Ocelot.Middleware; +using Ocelot.Request.Middleware; using Ocelot.Requester; using Ocelot.Responses; using Shouldly; @@ -170,7 +171,7 @@ namespace Ocelot.UnitTests.Requester var context = new DownstreamContext(new DefaultHttpContext()) { DownstreamReRoute = downstream, - DownstreamRequest = new HttpRequestMessage() { RequestUri = new Uri("http://localhost:5003") }, + DownstreamRequest = new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri("http://localhost:5003") }), }; _context = context; diff --git a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs index bbf59692..c80ea391 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs @@ -12,13 +12,16 @@ using Ocelot.Middleware; using TestStack.BDDfy; using Xunit; using Shouldly; +using Ocelot.Request.Middleware; +using System.Threading.Tasks; +using System.Threading; namespace Ocelot.UnitTests.Requester { public class HttpClientHttpRequesterTest { private readonly Mock _cacheHandlers; - private Mock _house; + private Mock _factory; private Response _response; private readonly HttpClientHttpRequester _httpClientRequester; private DownstreamContext _request; @@ -27,8 +30,8 @@ namespace Ocelot.UnitTests.Requester public HttpClientHttpRequesterTest() { - _house = new Mock(); - _house.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse>>(new List>())); + _factory = new Mock(); + _factory.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse>>(new List>())); _logger = new Mock(); _loggerFactory = new Mock(); _loggerFactory @@ -38,7 +41,7 @@ namespace Ocelot.UnitTests.Requester _httpClientRequester = new HttpClientHttpRequester( _loggerFactory.Object, _cacheHandlers.Object, - _house.Object); + _factory.Object); } [Fact] @@ -50,10 +53,11 @@ namespace Ocelot.UnitTests.Requester var context = new DownstreamContext(new DefaultHttpContext()) { DownstreamReRoute = reRoute, - DownstreamRequest = new HttpRequestMessage() { RequestUri = new Uri("http://www.bbc.co.uk") }, + DownstreamRequest = new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri("http://www.bbc.co.uk") }), }; this.Given(x=>x.GivenTheRequestIs(context)) + .And(x => GivenTheHouseReturnsOkHandler()) .When(x=>x.WhenIGetResponse()) .Then(x => x.ThenTheResponseIsCalledCorrectly()) .BDDfy(); @@ -68,7 +72,7 @@ namespace Ocelot.UnitTests.Requester var context = new DownstreamContext(new DefaultHttpContext()) { DownstreamReRoute = reRoute, - DownstreamRequest = new HttpRequestMessage() { RequestUri = new Uri("http://localhost:60080") }, + DownstreamRequest = new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri("http://localhost:60080") }), }; this.Given(x => x.GivenTheRequestIs(context)) @@ -96,5 +100,23 @@ namespace Ocelot.UnitTests.Requester { _response.IsError.ShouldBeTrue(); } + + private void GivenTheHouseReturnsOkHandler() + { + var handlers = new List> + { + () => new OkDelegatingHandler() + }; + + _factory.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse>>(handlers)); + } + + class OkDelegatingHandler : DelegatingHandler + { + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return Task.FromResult(new HttpResponseMessage()); + } + } } } diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs index 08b67820..60555435 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ConfigurationServiceProviderTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Ocelot.ServiceDiscovery; +using Ocelot.ServiceDiscovery.Providers; using Ocelot.Values; using Shouldly; using TestStack.BDDfy; diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs index a272d1b4..fc67ab8e 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs @@ -9,6 +9,8 @@ using Microsoft.AspNetCore.Http; using Moq; using Ocelot.Logging; using Ocelot.ServiceDiscovery; +using Ocelot.ServiceDiscovery.Configuration; +using Ocelot.ServiceDiscovery.Providers; using Ocelot.Values; using Xunit; using TestStack.BDDfy; diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceFabricServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceFabricServiceDiscoveryProviderTests.cs index 39deb681..eabe72d7 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceFabricServiceDiscoveryProviderTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceFabricServiceDiscoveryProviderTests.cs @@ -1,4 +1,7 @@ -namespace Ocelot.UnitTests.ServiceDiscovery +using Ocelot.ServiceDiscovery.Configuration; +using Ocelot.ServiceDiscovery.Providers; + +namespace Ocelot.UnitTests.ServiceDiscovery { using System; using System.Collections.Generic; diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index 021b9efb..2eef9b78 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -5,6 +5,7 @@ using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Logging; using Ocelot.ServiceDiscovery; +using Ocelot.ServiceDiscovery.Providers; using Shouldly; using TestStack.BDDfy; using Xunit; diff --git a/test/Ocelot.UnitTests/WebSockets/WebSocketsProxyMiddlewareTests.cs b/test/Ocelot.UnitTests/WebSockets/WebSocketsProxyMiddlewareTests.cs new file mode 100644 index 00000000..c3b087d4 --- /dev/null +++ b/test/Ocelot.UnitTests/WebSockets/WebSocketsProxyMiddlewareTests.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Consul; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Websockets +{ + public class WebSocketsProxyMiddlewareTests : IDisposable + { + private IWebHost _firstDownstreamHost; + private readonly List _firstRecieved; + private WebHostBuilder _ocelotBuilder; + private IWebHost _ocelotHost; + + public WebSocketsProxyMiddlewareTests() + { + _firstRecieved = new List(); + } + + [Fact] + public async Task should_proxy_websocket_input_to_downstream_service() + { + var downstreamPort = 5001; + var downstreamHost = "localhost"; + + var config = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + UpstreamPathTemplate = "/", + DownstreamPathTemplate = "/ws", + DownstreamScheme = "ws", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = downstreamHost, + Port = downstreamPort + } + } + } + } + }; + + this.Given(_ => GivenThereIsAConfiguration(config)) + .And(_ => StartFakeOcelotWithWebSockets()) + .And(_ => StartFakeDownstreamService($"http://{downstreamHost}:{downstreamPort}", "/ws")) + .When(_ => StartClient("ws://localhost:5000/")) + .Then(_ => _firstRecieved.Count.ShouldBe(10)) + .BDDfy(); + } + + public void Dispose() + { + _firstDownstreamHost?.Dispose(); + } + + public async Task StartFakeOcelotWithWebSockets() + { + _ocelotBuilder = new WebHostBuilder(); + _ocelotBuilder.ConfigureServices(s => + { + s.AddSingleton(_ocelotBuilder); + s.AddOcelot(); + }); + _ocelotBuilder.UseKestrel() + .UseUrls("http://localhost:5000") + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + }) + .Configure(app => + { + app.UseWebSockets(); + app.UseOcelot().Wait(); + }) + .UseIISIntegration(); + _ocelotHost = _ocelotBuilder.Build(); + await _ocelotHost.StartAsync(); + } + + public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = Path.Combine(AppContext.BaseDirectory, "configuration.json"); + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + private async Task StartFakeDownstreamService(string url, string path) + { + _firstDownstreamHost = new WebHostBuilder() + .ConfigureServices(s => { }).UseKestrel() + .UseUrls(url) + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddEnvironmentVariables(); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + }) + .Configure(app => + { + app.UseWebSockets(); + app.Use(async (context, next) => + { + if (context.Request.Path == path) + { + if (context.WebSockets.IsWebSocketRequest) + { + WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(); + await Echo(webSocket); + } + else + { + context.Response.StatusCode = 400; + } + } + else + { + await next(); + } + }); + }) + .UseIISIntegration().Build(); + await _firstDownstreamHost.StartAsync(); + } + + private async Task StartClient(string url) + { + var client = new ClientWebSocket(); + + await client.ConnectAsync(new Uri(url), CancellationToken.None); + + var sending = Task.Run(async () => + { + string line = "test"; + for (int i = 0; i < 10; i++) + { + var bytes = Encoding.UTF8.GetBytes(line); + + await client.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, + CancellationToken.None); + await Task.Delay(10); + } + + await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + }); + + var receiving = Task.Run(async () => + { + var buffer = new byte[1024 * 4]; + + while (true) + { + var result = await client.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + + if (result.MessageType == WebSocketMessageType.Text) + { + _firstRecieved.Add(Encoding.UTF8.GetString(buffer, 0, result.Count)); + } + + else if (result.MessageType == WebSocketMessageType.Close) + { + await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + break; + } + } + }); + + await Task.WhenAll(sending, receiving); + } + + private async Task Echo(WebSocket webSocket) + { + try + { + var buffer = new byte[1024 * 4]; + + var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + + while (!result.CloseStatus.HasValue) + { + await webSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); + + result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + } + + await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + } +} From ec545fadae91cfcf71ca225fff7cbab579e28f74 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Mon, 2 Apr 2018 18:12:35 +0100 Subject: [PATCH 20/50] Feature/fix tracing (#297) * hacked together tracing fix by wrapping middleware delegate in another delegate * #227 have re-implemented tracing, cleaned up trace names, probably still need some refactoring and tests as this was a bit of a hack job * #227 bit of checking for when we dont want to use tracing, also removed a unit test for websockets that wasnt a unit test, i stuck it there because i wanted the code coverage and now im paying the price, will have to work out a better way to do it * #227 a bit of refactoring to make this work better, still a bit hacky...would like to revisit the whole thing one day * #227 dont need this * #227 or this * #227 small refactor --- .../Creator/HttpHandlerOptionsCreator.cs | 15 +- .../Extensions/StringValuesExtensions.cs | 17 ++ src/Ocelot/Logging/IOcelotLogger.cs | 22 ++ src/Ocelot/Logging/IOcelotLoggerFactory.cs | 22 +- .../Logging/OcelotDiagnosticListener.cs | 54 +++- .../Middleware/OcelotMiddlewareExtensions.cs | 2 + .../OcelotPipelineBuilderExtensions.cs | 32 ++- .../DelegatingHandlerHandlerFactory.cs | 1 + src/Ocelot/Requester/FakeServiceTracer.cs | 17 ++ src/Ocelot/Requester/TracingHandlerFactory.cs | 13 - test/Ocelot.ManualTest/appsettings.json | 2 +- test/Ocelot.ManualTest/configuration.json | 2 +- .../HttpHandlerOptionsCreatorTests.cs | 73 +++++- .../WebSocketsProxyMiddlewareTests.cs | 239 ------------------ 14 files changed, 228 insertions(+), 283 deletions(-) create mode 100644 src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs create mode 100644 src/Ocelot/Logging/IOcelotLogger.cs create mode 100644 src/Ocelot/Requester/FakeServiceTracer.cs delete mode 100644 test/Ocelot.UnitTests/WebSockets/WebSocketsProxyMiddlewareTests.cs diff --git a/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs b/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs index 6c66f3c0..332c25b7 100644 --- a/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs @@ -1,13 +1,24 @@ -using Ocelot.Configuration.File; +using Butterfly.Client.Tracing; +using Ocelot.Configuration.File; +using Ocelot.Requester; namespace Ocelot.Configuration.Creator { public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator { + private IServiceTracer _tracer; + + public HttpHandlerOptionsCreator(IServiceTracer tracer) + { + _tracer = tracer; + } + public HttpHandlerOptions Create(FileReRoute fileReRoute) { + var useTracing = _tracer.GetType() != typeof(FakeServiceTracer) ? fileReRoute.HttpHandlerOptions.UseTracing : false; + return new HttpHandlerOptions(fileReRoute.HttpHandlerOptions.AllowAutoRedirect, - fileReRoute.HttpHandlerOptions.UseCookieContainer, fileReRoute.HttpHandlerOptions.UseTracing); + fileReRoute.HttpHandlerOptions.UseCookieContainer, useTracing); } } } diff --git a/src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs b/src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs new file mode 100644 index 00000000..1b6d1682 --- /dev/null +++ b/src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.Primitives; +using System.Linq; + +namespace Ocelot.Infrastructure.Extensions +{ + internal static class StringValueExtensions + { + public static string GetValue(this StringValues stringValues) + { + if (stringValues.Count == 1) + { + return stringValues; + } + return stringValues.ToArray().LastOrDefault(); + } + } +} diff --git a/src/Ocelot/Logging/IOcelotLogger.cs b/src/Ocelot/Logging/IOcelotLogger.cs new file mode 100644 index 00000000..67bac731 --- /dev/null +++ b/src/Ocelot/Logging/IOcelotLogger.cs @@ -0,0 +1,22 @@ +using System; + +namespace Ocelot.Logging +{ + /// + /// Thin wrapper around the DotNet core logging framework, used to allow the scopedDataRepository to be injected giving access to the Ocelot RequestId + /// + public interface IOcelotLogger + { + void LogTrace(string message, params object[] args); + void LogDebug(string message, params object[] args); + void LogInformation(string message, params object[] args); + void LogError(string message, Exception exception); + void LogError(string message, params object[] args); + void LogCritical(string message, Exception exception); + + /// + /// The name of the type the logger has been built for. + /// + string Name { get; } + } +} diff --git a/src/Ocelot/Logging/IOcelotLoggerFactory.cs b/src/Ocelot/Logging/IOcelotLoggerFactory.cs index 3a145595..2407afcd 100644 --- a/src/Ocelot/Logging/IOcelotLoggerFactory.cs +++ b/src/Ocelot/Logging/IOcelotLoggerFactory.cs @@ -1,27 +1,7 @@ -using System; - -namespace Ocelot.Logging +namespace Ocelot.Logging { public interface IOcelotLoggerFactory { IOcelotLogger CreateLogger(); } - - /// - /// Thin wrapper around the DotNet core logging framework, used to allow the scopedDataRepository to be injected giving access to the Ocelot RequestId - /// - public interface IOcelotLogger - { - void LogTrace(string message, params object[] args); - void LogDebug(string message, params object[] args); - void LogInformation(string message, params object[] args); - void LogError(string message, Exception exception); - void LogError(string message, params object[] args); - void LogCritical(string message, Exception exception); - - /// - /// The name of the type the logger has been built for. - /// - string Name { get; } - } } diff --git a/src/Ocelot/Logging/OcelotDiagnosticListener.cs b/src/Ocelot/Logging/OcelotDiagnosticListener.cs index ee0b5b8a..d75cfd2c 100644 --- a/src/Ocelot/Logging/OcelotDiagnosticListener.cs +++ b/src/Ocelot/Logging/OcelotDiagnosticListener.cs @@ -3,18 +3,48 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DiagnosticAdapter; using Butterfly.Client.AspNetCore; using Butterfly.OpenTracing; +using Ocelot.Middleware; +using Butterfly.Client.Tracing; +using System.Linq; +using System.Collections.Generic; +using Ocelot.Infrastructure.Extensions; +using Microsoft.Extensions.Logging; +using Ocelot.Requester; namespace Ocelot.Logging { public class OcelotDiagnosticListener { + private IServiceTracer _tracer; private IOcelotLogger _logger; - public OcelotDiagnosticListener(IOcelotLoggerFactory factory) + public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceTracer tracer) { + _tracer = tracer; _logger = factory.CreateLogger(); } + [DiagnosticName("Ocelot.MiddlewareException")] + public virtual void OcelotMiddlewareException(Exception exception, DownstreamContext context, string name) + { + _logger.LogTrace($"Ocelot.MiddlewareException: {name}; {exception.Message}"); + Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}"); + } + + [DiagnosticName("Ocelot.MiddlewareStarted")] + public virtual void OcelotMiddlewareStarted(DownstreamContext context, string name) + { + _logger.LogTrace($"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}"); + Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}"); + } + + [DiagnosticName("Ocelot.MiddlewareFinished")] + public virtual void OcelotMiddlewareFinished(DownstreamContext context, string name) + { + _logger.LogTrace($"OcelotMiddlewareFinished: {name}; {context.HttpContext.Request.Path}"); + Event(context.HttpContext, $"OcelotMiddlewareFinished: {name}; {context.HttpContext.Request.Path}"); + } + [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareStarting")] public virtual void OnMiddlewareStarting(HttpContext httpContext, string name) { @@ -36,8 +66,28 @@ namespace Ocelot.Logging } private void Event(HttpContext httpContext, string @event) - { + { + // Hack - if the user isnt using tracing the code gets here and will blow up on + // _tracer.Tracer.TryExtract. We already use the fake tracer for another scenario + // so sticking it here as well..I guess we need a factory for this but no idea + // how to hook that into the diagnostic framework at the moment. + if(_tracer.GetType() == typeof(FakeServiceTracer)) + { + return; + } + var span = httpContext.GetSpan(); + if(span == null) + { + var spanBuilder = new SpanBuilder($"server {httpContext.Request.Method} {httpContext.Request.Path}"); + if (_tracer.Tracer.TryExtract(out var spanContext, httpContext.Request.Headers, (c, k) => c[k].GetValue(), + c => c.Select(x => new KeyValuePair(x.Key, x.Value.GetValue())).GetEnumerator())) + { + spanBuilder.AsChildOf(spanContext); + }; + span = _tracer.Start(spanBuilder); + httpContext.SetSpan(span); + } span?.Log(LogField.CreateNew().Event(@event)); } } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index cda82bef..61d13f52 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -65,6 +65,8 @@ rest of asp.net.. */ + builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware"; + builder.Use(async (context, task) => { var downstreamContext = new DownstreamContext(context); diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs index 5989b1dd..539fa6a1 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs @@ -3,6 +3,7 @@ // Removed code and changed RequestDelete to OcelotRequestDelete, HttpContext to DownstreamContext, removed some exception handling messages using System; +using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -75,7 +76,28 @@ namespace Ocelot.Middleware.Pipeline var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs); if (parameters.Length == 1) { - return (OcelotRequestDelegate)methodinfo.CreateDelegate(typeof(OcelotRequestDelegate), instance); + var ocelotDelegate = (OcelotRequestDelegate)methodinfo.CreateDelegate(typeof(OcelotRequestDelegate), instance); + var diagnosticListener = (DiagnosticListener)app.ApplicationServices.GetService(typeof(DiagnosticListener)); + var middlewareName = ocelotDelegate.Target.GetType().Name; + + OcelotRequestDelegate wrapped = context => { + try + { + Write(diagnosticListener, "Ocelot.MiddlewareStarted", middlewareName, context); + return ocelotDelegate(context); + } + catch(Exception ex) + { + Write(diagnosticListener, "Ocelot.MiddlewareException", middlewareName, context); + throw ex; + } + finally + { + Write(diagnosticListener, "Ocelot.MiddlewareFinished", middlewareName, context); + } + }; + + return wrapped; } var factory = Compile(methodinfo, parameters); @@ -93,6 +115,14 @@ namespace Ocelot.Middleware.Pipeline }); } + private static void Write(DiagnosticListener diagnosticListener, string message, string middlewareName, DownstreamContext context) + { + if(diagnosticListener != null) + { + diagnosticListener.Write(message, new { name = middlewareName, context = context }); + } + } + public static IOcelotPipelineBuilder MapWhen(this IOcelotPipelineBuilder app, Predicate predicate, Action configuration) { if (app == null) diff --git a/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs index e0d9da82..02969855 100644 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs +++ b/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; +using Butterfly.Client.Tracing; using Microsoft.Extensions.DependencyInjection; using Ocelot.Configuration; using Ocelot.Logging; diff --git a/src/Ocelot/Requester/FakeServiceTracer.cs b/src/Ocelot/Requester/FakeServiceTracer.cs new file mode 100644 index 00000000..95347ef6 --- /dev/null +++ b/src/Ocelot/Requester/FakeServiceTracer.cs @@ -0,0 +1,17 @@ +using Butterfly.Client.Tracing; +using Butterfly.OpenTracing; + +namespace Ocelot.Requester +{ + public class FakeServiceTracer : IServiceTracer + { + public ITracer Tracer { get; } + public string ServiceName { get; } + public string Environment { get; } + public string Identity { get; } + public ISpan Start(ISpanBuilder spanBuilder) + { + return null; + } + } +} diff --git a/src/Ocelot/Requester/TracingHandlerFactory.cs b/src/Ocelot/Requester/TracingHandlerFactory.cs index b514ca18..f0eb97b1 100644 --- a/src/Ocelot/Requester/TracingHandlerFactory.cs +++ b/src/Ocelot/Requester/TracingHandlerFactory.cs @@ -1,5 +1,4 @@ using Butterfly.Client.Tracing; -using Butterfly.OpenTracing; using Ocelot.Infrastructure.RequestData; namespace Ocelot.Requester @@ -22,16 +21,4 @@ namespace Ocelot.Requester return new OcelotHttpTracingHandler(_tracer, _repo); } } - - public class FakeServiceTracer : IServiceTracer - { - public ITracer Tracer { get; } - public string ServiceName { get; } - public string Environment { get; } - public string Identity { get; } - public ISpan Start(ISpanBuilder spanBuilder) - { - throw new System.NotImplementedException(); - } - } } diff --git a/test/Ocelot.ManualTest/appsettings.json b/test/Ocelot.ManualTest/appsettings.json index e3439bab..c10bfed6 100644 --- a/test/Ocelot.ManualTest/appsettings.json +++ b/test/Ocelot.ManualTest/appsettings.json @@ -2,7 +2,7 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Debug", + "Default": "Error", "System": "Error", "Microsoft": "Error" } diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index ba34d4e5..12f7f188 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -99,7 +99,7 @@ "HttpHandlerOptions": { "AllowAutoRedirect": true, "UseCookieContainer": true, - "UseTracing": false + "UseTracing": true }, "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, diff --git a/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs index ca22956c..7316dcb3 100644 --- a/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs @@ -1,6 +1,10 @@ -using Ocelot.Configuration; +using System; +using Butterfly.Client.Tracing; +using Butterfly.OpenTracing; +using Ocelot.Configuration; using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; +using Ocelot.Requester; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -9,13 +13,54 @@ namespace Ocelot.UnitTests.Configuration { public class HttpHandlerOptionsCreatorTests { - private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; + private IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; private FileReRoute _fileReRoute; private HttpHandlerOptions _httpHandlerOptions; + private IServiceTracer _serviceTracer; public HttpHandlerOptionsCreatorTests() { - _httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(); + _serviceTracer = new FakeServiceTracer(); + _httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(_serviceTracer); + } + + [Fact] + public void should_not_use_tracing_if_fake_tracer_registered() + { + var fileReRoute = new FileReRoute + { + HttpHandlerOptions = new FileHttpHandlerOptions + { + UseTracing = true + } + }; + + var expectedOptions = new HttpHandlerOptions(false, false, false); + + this.Given(x => GivenTheFollowing(fileReRoute)) + .When(x => WhenICreateHttpHandlerOptions()) + .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) + .BDDfy(); + } + + [Fact] + public void should_use_tracing_if_real_tracer_registered() + { + var fileReRoute = new FileReRoute + { + HttpHandlerOptions = new FileHttpHandlerOptions + { + UseTracing = true + } + }; + + var expectedOptions = new HttpHandlerOptions(false, false, true); + + this.Given(x => GivenTheFollowing(fileReRoute)) + .And(x => GivenARealTracer()) + .When(x => WhenICreateHttpHandlerOptions()) + .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) + .BDDfy(); } [Fact] @@ -68,5 +113,27 @@ namespace Ocelot.UnitTests.Configuration _httpHandlerOptions.UseCookieContainer.ShouldBe(expected.UseCookieContainer); _httpHandlerOptions.UseTracing.ShouldBe(expected.UseTracing); } + + private void GivenARealTracer() + { + var tracer = new RealTracer(); + _httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(tracer); + } + + class RealTracer : IServiceTracer + { + public ITracer Tracer => throw new NotImplementedException(); + + public string ServiceName => throw new NotImplementedException(); + + public string Environment => throw new NotImplementedException(); + + public string Identity => throw new NotImplementedException(); + + public ISpan Start(ISpanBuilder spanBuilder) + { + throw new NotImplementedException(); + } + } } } diff --git a/test/Ocelot.UnitTests/WebSockets/WebSocketsProxyMiddlewareTests.cs b/test/Ocelot.UnitTests/WebSockets/WebSocketsProxyMiddlewareTests.cs deleted file mode 100644 index c3b087d4..00000000 --- a/test/Ocelot.UnitTests/WebSockets/WebSocketsProxyMiddlewareTests.cs +++ /dev/null @@ -1,239 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Consul; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Websockets -{ - public class WebSocketsProxyMiddlewareTests : IDisposable - { - private IWebHost _firstDownstreamHost; - private readonly List _firstRecieved; - private WebHostBuilder _ocelotBuilder; - private IWebHost _ocelotHost; - - public WebSocketsProxyMiddlewareTests() - { - _firstRecieved = new List(); - } - - [Fact] - public async Task should_proxy_websocket_input_to_downstream_service() - { - var downstreamPort = 5001; - var downstreamHost = "localhost"; - - var config = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/", - DownstreamPathTemplate = "/ws", - DownstreamScheme = "ws", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = downstreamHost, - Port = downstreamPort - } - } - } - } - }; - - this.Given(_ => GivenThereIsAConfiguration(config)) - .And(_ => StartFakeOcelotWithWebSockets()) - .And(_ => StartFakeDownstreamService($"http://{downstreamHost}:{downstreamPort}", "/ws")) - .When(_ => StartClient("ws://localhost:5000/")) - .Then(_ => _firstRecieved.Count.ShouldBe(10)) - .BDDfy(); - } - - public void Dispose() - { - _firstDownstreamHost?.Dispose(); - } - - public async Task StartFakeOcelotWithWebSockets() - { - _ocelotBuilder = new WebHostBuilder(); - _ocelotBuilder.ConfigureServices(s => - { - s.AddSingleton(_ocelotBuilder); - s.AddOcelot(); - }); - _ocelotBuilder.UseKestrel() - .UseUrls("http://localhost:5000") - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureLogging((hostingContext, logging) => - { - logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - logging.AddConsole(); - }) - .Configure(app => - { - app.UseWebSockets(); - app.UseOcelot().Wait(); - }) - .UseIISIntegration(); - _ocelotHost = _ocelotBuilder.Build(); - await _ocelotHost.StartAsync(); - } - - public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = Path.Combine(AppContext.BaseDirectory, "configuration.json"); - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - } - - private async Task StartFakeDownstreamService(string url, string path) - { - _firstDownstreamHost = new WebHostBuilder() - .ConfigureServices(s => { }).UseKestrel() - .UseUrls(url) - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddEnvironmentVariables(); - }) - .ConfigureLogging((hostingContext, logging) => - { - logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - logging.AddConsole(); - }) - .Configure(app => - { - app.UseWebSockets(); - app.Use(async (context, next) => - { - if (context.Request.Path == path) - { - if (context.WebSockets.IsWebSocketRequest) - { - WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(); - await Echo(webSocket); - } - else - { - context.Response.StatusCode = 400; - } - } - else - { - await next(); - } - }); - }) - .UseIISIntegration().Build(); - await _firstDownstreamHost.StartAsync(); - } - - private async Task StartClient(string url) - { - var client = new ClientWebSocket(); - - await client.ConnectAsync(new Uri(url), CancellationToken.None); - - var sending = Task.Run(async () => - { - string line = "test"; - for (int i = 0; i < 10; i++) - { - var bytes = Encoding.UTF8.GetBytes(line); - - await client.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, - CancellationToken.None); - await Task.Delay(10); - } - - await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); - }); - - var receiving = Task.Run(async () => - { - var buffer = new byte[1024 * 4]; - - while (true) - { - var result = await client.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - - if (result.MessageType == WebSocketMessageType.Text) - { - _firstRecieved.Add(Encoding.UTF8.GetString(buffer, 0, result.Count)); - } - - else if (result.MessageType == WebSocketMessageType.Close) - { - await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); - break; - } - } - }); - - await Task.WhenAll(sending, receiving); - } - - private async Task Echo(WebSocket webSocket) - { - try - { - var buffer = new byte[1024 * 4]; - - var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - - while (!result.CloseStatus.HasValue) - { - await webSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); - - result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - } - - await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); - } - catch (Exception e) - { - Console.WriteLine(e); - } - } - } -} From ff3e7c6665c3a15365221ad296b36e6cdcdf9a9f Mon Sep 17 00:00:00 2001 From: Felix Boers Date: Thu, 5 Apr 2018 20:07:23 +0200 Subject: [PATCH 21/50] Log downstream templates in DownstreamRouteFinderMiddleware (#302) Concatenate all downstream templates separated by , (colon) in order to write them to the log. --- .../Middleware/DownstreamRouteFinderMiddleware.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index c9da50a4..0639135d 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using System.Linq; using Ocelot.Configuration; using Ocelot.Configuration.Provider; using Ocelot.DownstreamRouteFinder.Finder; @@ -57,10 +58,10 @@ namespace Ocelot.DownstreamRouteFinder.Middleware SetPipelineError(context, downstreamRoute.Errors); return; - } - - // todo - put this back in - //// _logger.LogDebug("downstream template is {downstreamRoute.Data.ReRoute.DownstreamPath}", downstreamRoute.Data.ReRoute.DownstreamReRoute.DownstreamPathTemplate); + } + + var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value)); + _logger.LogDebug($"downstream templates are {downstreamPathTemplates}"); context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues; From 0c380239a973f5f4cb7c6e9d15013425fe7d5cbe Mon Sep 17 00:00:00 2001 From: Felix Boers Date: Thu, 5 Apr 2018 20:07:45 +0200 Subject: [PATCH 22/50] Override ToString() in DownstreamRequest (#301) When executing the downstreamUrlCreatorMittleware the downstream request url shoud be written to the log. But instead of the url the type name gets written because the ToString() method wasn't overriden. Now ToString() internally calls the ToUri() method in order to provide the url instead of the type name. --- src/Ocelot/Request/Middleware/DownstreamRequest.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Ocelot/Request/Middleware/DownstreamRequest.cs b/src/Ocelot/Request/Middleware/DownstreamRequest.cs index 449b33cc..75070bfd 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequest.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequest.cs @@ -65,5 +65,10 @@ namespace Ocelot.Request.Middleware return uriBuilder.Uri.AbsoluteUri; } + + public override string ToString() + { + return ToUri(); + } } } From efbb950ea2842b448b72c055fe358dc10ecf6710 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 7 Apr 2018 12:03:24 +0100 Subject: [PATCH 23/50] Feature/another look at logging (#303) * #212 - hacked websockets proxy together * faffing around * #212 hacking away :( * #212 websockets proxy middleware working * #212 map when for webockets working * #212 some test refactor * #212 temp commit * #212 websockets proxy working, tests passing...need to do some tidying and write docs * #212 more code coverage * #212 docs for websockets * #212 updated readme * #212 tidying up after websockets refactoring * #212 tidying up after websockets refactoring * #212 tidying up after websockets refactoring * changing logging levels and logging like ms reccomends with structured data rather than strings * more faffing * more fafin * #287 ocelot logger now only takes strings as it did take params then just turned them to strings, misleading, unit tests for logger and diagnosticlogger * #287 errors now logged as they happen * #287 more detail for logs requested in issue * #287 tidy up * #287 renamed * #287 always log context id * #287 fixed me being an idiot * #287 removed crap websockets unit test that isnt a unit test * #287 removed crap websockets unit test that isnt a unit test --- .../Middleware/AuthenticationMiddleware.cs | 18 +-- src/Ocelot/Authorisation/ClaimsAuthoriser.cs | 13 +- .../Middleware/AuthorisationMiddleware.cs | 33 ++-- src/Ocelot/Authorisation/ScopesAuthoriser.cs | 8 +- .../Cache/Middleware/OutputCacheMiddleware.cs | 15 +- .../Middleware/ClaimsBuilderMiddleware.cs | 7 +- .../Creator/ClaimsToThingCreator.cs | 3 +- .../Creator/HeaderFindAndReplaceCreator.cs | 4 +- .../Configuration/IOcelotConfiguration.cs | 2 +- .../Parser/ClaimToThingConfigurationParser.cs | 18 +-- .../ConsulFileConfigurationPoller.cs | 6 +- .../ConfigurationValidationResult.cs | 4 +- .../Finder/DownstreamRouteFinder.cs | 5 +- .../UnableToFindDownstreamRouteError.cs | 3 +- .../DownstreamRouteFinderMiddleware.cs | 11 +- .../DownstreamUrlCreator/IUrlBuilder.cs | 12 -- .../DownstreamUrlCreatorMiddleware.cs | 7 +- src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs | 47 ------ src/Ocelot/Errors/Error.cs | 2 +- .../Middleware/ExceptionHandlerMiddleware.cs | 46 +++--- src/Ocelot/Headers/AddHeadersToResponse.cs | 2 +- .../HttpHeadersTransformationMiddleware.cs | 3 +- .../HttpRequestHeadersBuilderMiddleware.cs | 11 +- .../Claims/Parser/ClaimsParser.cs | 10 +- .../Extensions/StringValuesExtensions.cs | 3 +- .../RequestData/HttpDataRepository.cs | 20 +-- .../LoadBalancers/LeastConnection.cs | 4 +- .../Middleware/LoadBalancingMiddleware.cs | 9 +- src/Ocelot/Logging/AspDotNetLogger.cs | 36 ++--- src/Ocelot/Logging/AspDotNetLoggerFactory.cs | 14 +- src/Ocelot/Logging/IOcelotLogger.cs | 13 +- .../Logging/OcelotDiagnosticListener.cs | 22 +-- src/Ocelot/Middleware/DownstreamContext.cs | 11 +- src/Ocelot/Middleware/OcelotMiddleware.cs | 19 ++- .../OcelotPipelineBuilderExtensions.cs | 10 +- .../QueryStringBuilderMiddleware.cs | 7 +- src/Ocelot/Raft/RaftController.cs | 13 ++ .../Middleware/ClientRateLimitMiddleware.cs | 12 +- .../DownstreamRequestInitialiserMiddleware.cs | 3 +- .../Middleware/ReRouteRequestIdMiddleware.cs | 23 +-- .../Middleware/HttpRequesterMiddleware.cs | 7 +- .../Requester/OcelotHttpTracingHandler.cs | 10 +- .../Middleware/ResponderMiddleware.cs | 16 +- src/Ocelot/Responses/ErrorResponse.cs | 6 +- src/Ocelot/Responses/Response.cs | 12 +- .../ConsulServiceDiscoveryProvider.cs | 2 +- .../Middleware/WebSocketsProxyMiddleware.cs | 3 +- test/Ocelot.ManualTest/Program.cs | 4 +- test/Ocelot.ManualTest/appsettings.json | 2 +- .../ClaimsToThingCreatorTests.cs | 2 +- .../HeaderFindAndReplaceCreatorTests.cs | 2 +- .../Errors/ExceptionHandlerMiddlewareTests.cs | 48 +++--- .../Headers/AddHeadersToResponseTests.cs | 4 +- .../Logging/AspDotNetLoggerTests.cs | 81 ++++++++++ .../Logging/OcelotDiagnosticListenerTests.cs | 145 ++++++++++++++++++ .../Middleware/OcelotMiddlewareTests.cs | 75 +++++++++ test/Ocelot.UnitTests/Ocelot.UnitTests.csproj | 4 + .../ReRouteRequestIdMiddlewareTests.cs | 45 ++++-- .../Responder/ResponderMiddlewareTests.cs | 2 +- .../ConsulServiceDiscoveryProviderTests.cs | 8 +- 60 files changed, 600 insertions(+), 387 deletions(-) delete mode 100644 src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs delete mode 100644 src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs create mode 100644 test/Ocelot.UnitTests/Logging/AspDotNetLoggerTests.cs create mode 100644 test/Ocelot.UnitTests/Logging/OcelotDiagnosticListenerTests.cs create mode 100644 test/Ocelot.UnitTests/Middleware/OcelotMiddlewareTests.cs diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs index bd4683d0..2c70e394 100644 --- a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs +++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs @@ -12,20 +12,19 @@ namespace Ocelot.Authentication.Middleware public class AuthenticationMiddleware : OcelotMiddleware { private readonly OcelotRequestDelegate _next; - private readonly IOcelotLogger _logger; public AuthenticationMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory) + : base(loggerFactory.CreateLogger()) { _next = next; - _logger = loggerFactory.CreateLogger(); } public async Task Invoke(DownstreamContext context) { if (IsAuthenticatedRoute(context.DownstreamReRoute)) { - _logger.LogDebug($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated"); + Logger.LogInformation($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated"); var result = await context.HttpContext.AuthenticateAsync(context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey); @@ -33,25 +32,22 @@ namespace Ocelot.Authentication.Middleware if (context.HttpContext.User.Identity.IsAuthenticated) { - _logger.LogDebug($"Client has been authenticated for {context.HttpContext.Request.Path}"); + Logger.LogInformation($"Client has been authenticated for {context.HttpContext.Request.Path}"); await _next.Invoke(context); } else { - var error = new List - { - new UnauthenticatedError( - $"Request for authenticated route {context.HttpContext.Request.Path} by {context.HttpContext.User.Identity.Name} was unauthenticated") - }; + var error = new UnauthenticatedError( + $"Request for authenticated route {context.HttpContext.Request.Path} by {context.HttpContext.User.Identity.Name} was unauthenticated"); - _logger.LogError($"Client has NOT been authenticated for {context.HttpContext.Request.Path} and pipeline error set. {error.ToErrorString()}"); + Logger.LogWarning($"Client has NOT been authenticated for {context.HttpContext.Request.Path} and pipeline error set. {error}"); SetPipelineError(context, error); } } else { - _logger.LogTrace($"No authentication needed for {context.HttpContext.Request.Path}"); + Logger.LogInformation($"No authentication needed for {context.HttpContext.Request.Path}"); await _next.Invoke(context); } diff --git a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs index 3ca7809e..d67c4d5f 100644 --- a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs +++ b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Security.Claims; -using Ocelot.Errors; using Ocelot.Responses; namespace Ocelot.Authorisation @@ -32,19 +31,13 @@ namespace Ocelot.Authorisation var authorised = values.Data.Contains(required.Value); if (!authorised) { - return new ErrorResponse(new List - { - new ClaimValueNotAuthorisedError( - $"claim value: {values.Data} is not the same as required value: {required.Value} for type: {required.Key}") - }); + return new ErrorResponse(new ClaimValueNotAuthorisedError( + $"claim value: {values.Data} is not the same as required value: {required.Value} for type: {required.Key}")); } } else { - return new ErrorResponse(new List - { - new UserDoesNotHaveClaimError($"user does not have claim {required.Key}") - }); + return new ErrorResponse(new UserDoesNotHaveClaimError($"user does not have claim {required.Key}")); } } diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs index 4ace7dbc..82fc3af7 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs @@ -13,30 +13,29 @@ private readonly OcelotRequestDelegate _next; private readonly IClaimsAuthoriser _claimsAuthoriser; private readonly IScopesAuthoriser _scopesAuthoriser; - private readonly IOcelotLogger _logger; public AuthorisationMiddleware(OcelotRequestDelegate next, IClaimsAuthoriser claimsAuthoriser, IScopesAuthoriser scopesAuthoriser, IOcelotLoggerFactory loggerFactory) + :base(loggerFactory.CreateLogger()) { _next = next; _claimsAuthoriser = claimsAuthoriser; _scopesAuthoriser = scopesAuthoriser; - _logger = loggerFactory.CreateLogger(); } public async Task Invoke(DownstreamContext context) { if (IsAuthenticatedRoute(context.DownstreamReRoute)) { - _logger.LogDebug("route is authenticated scopes must be checked"); + Logger.LogInformation("route is authenticated scopes must be checked"); var authorised = _scopesAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.AuthenticationOptions.AllowedScopes); if (authorised.IsError) { - _logger.LogDebug("error authorising user scopes"); + Logger.LogWarning("error authorising user scopes"); SetPipelineError(context, authorised.Errors); return; @@ -44,29 +43,26 @@ if (IsAuthorised(authorised)) { - _logger.LogDebug("user scopes is authorised calling next authorisation checks"); + Logger.LogInformation("user scopes is authorised calling next authorisation checks"); } else { - _logger.LogDebug("user scopes is not authorised setting pipeline error"); + Logger.LogWarning("user scopes is not authorised setting pipeline error"); - SetPipelineError(context, new List - { - new UnauthorisedError( - $"{context.HttpContext.User.Identity.Name} unable to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}") - }); + SetPipelineError(context, new UnauthorisedError( + $"{context.HttpContext.User.Identity.Name} unable to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}")); } } if (IsAuthorisedRoute(context.DownstreamReRoute)) { - _logger.LogDebug("route is authorised"); + Logger.LogInformation("route is authorised"); var authorised = _claimsAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.RouteClaimsRequirement); if (authorised.IsError) { - _logger.LogDebug($"Error whilst authorising {context.HttpContext.User.Identity.Name} for {context.HttpContext.User.Identity.Name}. Setting pipeline error"); + Logger.LogWarning($"Error whilst authorising {context.HttpContext.User.Identity.Name}. Setting pipeline error"); SetPipelineError(context, authorised.Errors); return; @@ -74,22 +70,19 @@ if (IsAuthorised(authorised)) { - _logger.LogDebug($"{context.HttpContext.User.Identity.Name} has succesfully been authorised for {context.DownstreamReRoute.UpstreamPathTemplate.Value}. Calling next middleware"); + Logger.LogInformation($"{context.HttpContext.User.Identity.Name} has succesfully been authorised for {context.DownstreamReRoute.UpstreamPathTemplate.Value}."); await _next.Invoke(context); } else { - _logger.LogDebug($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}. Setting pipeline error"); + Logger.LogWarning($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}. Setting pipeline error"); - SetPipelineError(context, new List - { - new UnauthorisedError($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}") - }); + SetPipelineError(context, new UnauthorisedError($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.Value}")); } } else { - _logger.LogDebug($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised"); + Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised"); await _next.Invoke(context); } } diff --git a/src/Ocelot/Authorisation/ScopesAuthoriser.cs b/src/Ocelot/Authorisation/ScopesAuthoriser.cs index 1654e2a1..4b999c10 100644 --- a/src/Ocelot/Authorisation/ScopesAuthoriser.cs +++ b/src/Ocelot/Authorisation/ScopesAuthoriser.cs @@ -1,5 +1,4 @@ using IdentityModel; -using Ocelot.Errors; using Ocelot.Responses; using System.Collections.Generic; using System.Security.Claims; @@ -38,11 +37,8 @@ namespace Ocelot.Authorisation if (matchesScopes.Count == 0) { - return new ErrorResponse(new List - { - new ScopeNotAuthorisedError( - $"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'") - }); + return new ErrorResponse( + new ScopeNotAuthorisedError($"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'")); } return new OkResponse(true); diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 88bc4e9e..93548f3b 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -14,7 +14,6 @@ namespace Ocelot.Cache.Middleware public class OutputCacheMiddleware : OcelotMiddleware { private readonly OcelotRequestDelegate _next; - private readonly IOcelotLogger _logger; private readonly IOcelotCache _outputCache; private readonly IRegionCreator _regionCreator; @@ -22,10 +21,10 @@ namespace Ocelot.Cache.Middleware IOcelotLoggerFactory loggerFactory, IOcelotCache outputCache, IRegionCreator regionCreator) + :base(loggerFactory.CreateLogger()) { _next = next; _outputCache = outputCache; - _logger = loggerFactory.CreateLogger(); _regionCreator = regionCreator; } @@ -39,29 +38,29 @@ namespace Ocelot.Cache.Middleware var downstreamUrlKey = $"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}"; - _logger.LogDebug("started checking cache for {downstreamUrlKey}", downstreamUrlKey); + Logger.LogDebug($"Started checking cache for {downstreamUrlKey}"); var cached = _outputCache.Get(downstreamUrlKey, context.DownstreamReRoute.CacheOptions.Region); if (cached != null) { - _logger.LogDebug("cache entry exists for {downstreamUrlKey}", downstreamUrlKey); + Logger.LogDebug($"cache entry exists for {downstreamUrlKey}"); var response = CreateHttpResponseMessage(cached); SetHttpResponseMessageThisRequest(context, response); - _logger.LogDebug("finished returned cached response for {downstreamUrlKey}", downstreamUrlKey); + Logger.LogDebug($"finished returned cached response for {downstreamUrlKey}"); return; } - _logger.LogDebug("no resonse cached for {downstreamUrlKey}", downstreamUrlKey); + Logger.LogDebug($"no resonse cached for {downstreamUrlKey}"); await _next.Invoke(context); if (context.IsError) { - _logger.LogDebug("there was a pipeline error for {downstreamUrlKey}", downstreamUrlKey); + Logger.LogDebug($"there was a pipeline error for {downstreamUrlKey}"); return; } @@ -70,7 +69,7 @@ namespace Ocelot.Cache.Middleware _outputCache.Add(downstreamUrlKey, cached, TimeSpan.FromSeconds(context.DownstreamReRoute.CacheOptions.TtlSeconds), context.DownstreamReRoute.CacheOptions.Region); - _logger.LogDebug("finished response added to cache for {downstreamUrlKey}", downstreamUrlKey); + Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}"); } private void SetHttpResponseMessageThisRequest(DownstreamContext context, HttpResponseMessage response) diff --git a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs index 48f57834..2dfc3dc0 100644 --- a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs +++ b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddleware.cs @@ -12,28 +12,27 @@ namespace Ocelot.Claims.Middleware { private readonly OcelotRequestDelegate _next; private readonly IAddClaimsToRequest _addClaimsToRequest; - private readonly IOcelotLogger _logger; public ClaimsBuilderMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, IAddClaimsToRequest addClaimsToRequest) + :base(loggerFactory.CreateLogger()) { _next = next; _addClaimsToRequest = addClaimsToRequest; - _logger = loggerFactory.CreateLogger(); } public async Task Invoke(DownstreamContext context) { if (context.DownstreamReRoute.ClaimsToClaims.Any()) { - _logger.LogDebug("this route has instructions to convert claims to other claims"); + Logger.LogDebug("this route has instructions to convert claims to other claims"); var result = _addClaimsToRequest.SetClaimsOnContext(context.DownstreamReRoute.ClaimsToClaims, context.HttpContext); if (result.IsError) { - _logger.LogDebug("error converting claims to other claims, setting pipeline error"); + Logger.LogDebug("error converting claims to other claims, setting pipeline error"); SetPipelineError(context, result.Errors); return; diff --git a/src/Ocelot/Configuration/Creator/ClaimsToThingCreator.cs b/src/Ocelot/Configuration/Creator/ClaimsToThingCreator.cs index 985abae4..0be61658 100644 --- a/src/Ocelot/Configuration/Creator/ClaimsToThingCreator.cs +++ b/src/Ocelot/Configuration/Creator/ClaimsToThingCreator.cs @@ -26,8 +26,7 @@ namespace Ocelot.Configuration.Creator if (claimToThing.IsError) { - _logger.LogDebug("ClaimsToThingCreator.BuildAddThingsToRequest", - $"Unable to extract configuration for key: {input.Key} and value: {input.Value} your configuration file is incorrect"); + _logger.LogDebug($"Unable to extract configuration for key: {input.Key} and value: {input.Value} your configuration file is incorrect"); } else { diff --git a/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs b/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs index e976e7b9..7bfc6d8e 100644 --- a/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs +++ b/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs @@ -32,7 +32,7 @@ namespace Ocelot.Configuration.Creator } else { - _logger.LogError($"Unable to add UpstreamHeaderTransform {input.Key}: {input.Value}"); + _logger.LogWarning($"Unable to add UpstreamHeaderTransform {input.Key}: {input.Value}"); } } @@ -50,7 +50,7 @@ namespace Ocelot.Configuration.Creator } else { - _logger.LogError($"Unable to add DownstreamHeaderTransform {input.Key}: {input.Value}"); + _logger.LogWarning($"Unable to add DownstreamHeaderTransform {input.Key}: {input.Value}"); } } else diff --git a/src/Ocelot/Configuration/IOcelotConfiguration.cs b/src/Ocelot/Configuration/IOcelotConfiguration.cs index fd808ae4..2353cb85 100644 --- a/src/Ocelot/Configuration/IOcelotConfiguration.cs +++ b/src/Ocelot/Configuration/IOcelotConfiguration.cs @@ -9,4 +9,4 @@ namespace Ocelot.Configuration ServiceProviderConfiguration ServiceProviderConfiguration {get;} string RequestId {get;} } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/Parser/ClaimToThingConfigurationParser.cs b/src/Ocelot/Configuration/Parser/ClaimToThingConfigurationParser.cs index 4d698e96..94df168f 100644 --- a/src/Ocelot/Configuration/Parser/ClaimToThingConfigurationParser.cs +++ b/src/Ocelot/Configuration/Parser/ClaimToThingConfigurationParser.cs @@ -20,22 +20,14 @@ namespace Ocelot.Configuration.Parser if (instructions.Length <= 1) { - return new ErrorResponse( - new List - { - new NoInstructionsError(SplitToken) - }); + return new ErrorResponse(new NoInstructionsError(SplitToken)); } var claimMatch = _claimRegex.IsMatch(instructions[0]); if (!claimMatch) { - return new ErrorResponse( - new List - { - new InstructionNotForClaimsError() - }); + return new ErrorResponse(new InstructionNotForClaimsError()); } var newKey = GetIndexValue(instructions[0]); @@ -53,11 +45,7 @@ namespace Ocelot.Configuration.Parser } catch (Exception exception) { - return new ErrorResponse( - new List - { - new ParsingConfigurationHeaderError(exception) - }); + return new ErrorResponse(new ParsingConfigurationHeaderError(exception)); } } diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs index 79efddca..16108d15 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs +++ b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs @@ -45,13 +45,13 @@ namespace Ocelot.Configuration.Repository private async Task Poll() { - _logger.LogDebug("Started polling consul"); + _logger.LogInformation("Started polling consul"); var fileConfig = await _repo.Get(); if(fileConfig.IsError) { - _logger.LogDebug($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}"); + _logger.LogWarning($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}"); return; } @@ -63,7 +63,7 @@ namespace Ocelot.Configuration.Repository _previousAsJson = asJson; } - _logger.LogDebug("Finished polling consul"); + _logger.LogInformation("Finished polling consul"); } /// diff --git a/src/Ocelot/Configuration/Validator/ConfigurationValidationResult.cs b/src/Ocelot/Configuration/Validator/ConfigurationValidationResult.cs index 4fe3567d..7e06cb6b 100644 --- a/src/Ocelot/Configuration/Validator/ConfigurationValidationResult.cs +++ b/src/Ocelot/Configuration/Validator/ConfigurationValidationResult.cs @@ -17,8 +17,8 @@ namespace Ocelot.Configuration.Validator Errors = errors; } - public bool IsError { get; private set; } + public bool IsError { get; } - public List Errors { get; private set; } + public List Errors { get; } } } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index bcfde4b0..35b6d92d 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -44,10 +44,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder return notNullOption != null ? new OkResponse(notNullOption) : new OkResponse(nullOption); } - return new ErrorResponse(new List - { - new UnableToFindDownstreamRouteError() - }); + return new ErrorResponse(new UnableToFindDownstreamRouteError(path, httpMethod)); } private bool RouteIsApplicableToThisRequest(ReRoute reRoute, string httpMethod, string upstreamHost) diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/UnableToFindDownstreamRouteError.cs b/src/Ocelot/DownstreamRouteFinder/Finder/UnableToFindDownstreamRouteError.cs index 66f7172a..81988ba5 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/UnableToFindDownstreamRouteError.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/UnableToFindDownstreamRouteError.cs @@ -4,7 +4,8 @@ namespace Ocelot.DownstreamRouteFinder.Finder { public class UnableToFindDownstreamRouteError : Error { - public UnableToFindDownstreamRouteError() : base("UnableToFindDownstreamRouteError", OcelotErrorCode.UnableToFindDownstreamRouteError) + public UnableToFindDownstreamRouteError(string path, string httpVerb) + : base($"Unable to find downstream route for path: {path}, verb: {httpVerb}", OcelotErrorCode.UnableToFindDownstreamRouteError) { } } diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index 0639135d..70e4a4e2 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -14,7 +14,6 @@ namespace Ocelot.DownstreamRouteFinder.Middleware { private readonly OcelotRequestDelegate _next; private readonly IDownstreamRouteFinder _downstreamRouteFinder; - private readonly IOcelotLogger _logger; private readonly IOcelotConfigurationProvider _configProvider; private readonly IMultiplexer _multiplexer; @@ -23,12 +22,12 @@ namespace Ocelot.DownstreamRouteFinder.Middleware IDownstreamRouteFinder downstreamRouteFinder, IOcelotConfigurationProvider configProvider, IMultiplexer multiplexer) + :base(loggerFactory.CreateLogger()) { _configProvider = configProvider; _multiplexer = multiplexer; _next = next; _downstreamRouteFinder = downstreamRouteFinder; - _logger = loggerFactory.CreateLogger(); } public async Task Invoke(DownstreamContext context) @@ -41,27 +40,27 @@ namespace Ocelot.DownstreamRouteFinder.Middleware if (configuration.IsError) { - _logger.LogError($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}"); + Logger.LogWarning($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}"); SetPipelineError(context, configuration.Errors); return; } context.ServiceProviderConfiguration = configuration.Data.ServiceProviderConfiguration; - _logger.LogDebug("upstream url path is {upstreamUrlPath}", upstreamUrlPath); + Logger.LogDebug($"Upstream url path is {upstreamUrlPath}"); var downstreamRoute = _downstreamRouteFinder.FindDownstreamRoute(upstreamUrlPath, context.HttpContext.Request.Method, configuration.Data, upstreamHost); if (downstreamRoute.IsError) { - _logger.LogError($"{MiddlewareName} setting pipeline errors. IDownstreamRouteFinder returned {downstreamRoute.Errors.ToErrorString()}"); + Logger.LogWarning($"{MiddlewareName} setting pipeline errors. IDownstreamRouteFinder returned {downstreamRoute.Errors.ToErrorString()}"); SetPipelineError(context, downstreamRoute.Errors); return; } var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value)); - _logger.LogDebug($"downstream templates are {downstreamPathTemplates}"); + Logger.LogDebug($"downstream templates are {downstreamPathTemplates}"); context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues; diff --git a/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs deleted file mode 100644 index 9975cec2..00000000 --- a/src/Ocelot/DownstreamUrlCreator/IUrlBuilder.cs +++ /dev/null @@ -1,12 +0,0 @@ -/* -using Ocelot.Responses; -using Ocelot.Values; - -namespace Ocelot.DownstreamUrlCreator -{ - public interface IUrlBuilder - { - Response Build(string downstreamPath, string downstreamScheme, ServiceHostAndPort downstreamHostAndPort); - } -} -*/ diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 43944050..bc054783 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -16,15 +16,14 @@ namespace Ocelot.DownstreamUrlCreator.Middleware { private readonly OcelotRequestDelegate _next; private readonly IDownstreamPathPlaceholderReplacer _replacer; - private readonly IOcelotLogger _logger; public DownstreamUrlCreatorMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, IDownstreamPathPlaceholderReplacer replacer) + :base(loggerFactory.CreateLogger()) { _next = next; _replacer = replacer; - _logger = loggerFactory.CreateLogger(); } public async Task Invoke(DownstreamContext context) @@ -34,7 +33,7 @@ namespace Ocelot.DownstreamUrlCreator.Middleware if (dsPath.IsError) { - _logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error"); + Logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error"); SetPipelineError(context, dsPath.Errors); return; @@ -53,7 +52,7 @@ namespace Ocelot.DownstreamUrlCreator.Middleware context.DownstreamRequest.AbsolutePath = dsPath.Data.Value; } - _logger.LogDebug("downstream url is {context.DownstreamRequest}", context.DownstreamRequest); + Logger.LogDebug($"Downstream url is {context.DownstreamRequest}"); await _next.Invoke(context); } diff --git a/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs b/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs deleted file mode 100644 index 1c5f284c..00000000 --- a/src/Ocelot/DownstreamUrlCreator/UrlBuilder.cs +++ /dev/null @@ -1,47 +0,0 @@ -/* -using System; -using System.Collections.Generic; -using Ocelot.Errors; -using Ocelot.Responses; -using Ocelot.Values; - -namespace Ocelot.DownstreamUrlCreator -{ - public class UrlBuilder : IUrlBuilder - { - public Response Build(string downstreamPath, string downstreamScheme, ServiceHostAndPort 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)); - } - } -} -*/ diff --git a/src/Ocelot/Errors/Error.cs b/src/Ocelot/Errors/Error.cs index 520f3775..e063934b 100644 --- a/src/Ocelot/Errors/Error.cs +++ b/src/Ocelot/Errors/Error.cs @@ -16,4 +16,4 @@ namespace Ocelot.Errors return Message; } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs index 2b9e22e2..236e2e8d 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs @@ -18,7 +18,6 @@ namespace Ocelot.Errors.Middleware public class ExceptionHandlerMiddleware : OcelotMiddleware { private readonly OcelotRequestDelegate _next; - private readonly IOcelotLogger _logger; private readonly IOcelotConfigurationProvider _provider; private readonly IRequestScopedDataRepository _repo; @@ -26,11 +25,11 @@ namespace Ocelot.Errors.Middleware IOcelotLoggerFactory loggerFactory, IOcelotConfigurationProvider provider, IRequestScopedDataRepository repo) + : base(loggerFactory.CreateLogger()) { _provider = provider; _repo = repo; _next = next; - _logger = loggerFactory.CreateLogger(); } public async Task Invoke(DownstreamContext context) @@ -39,47 +38,44 @@ namespace Ocelot.Errors.Middleware { await TrySetGlobalRequestId(context); - _logger.LogDebug("ocelot pipeline started"); + Logger.LogDebug("ocelot pipeline started"); await _next.Invoke(context); } catch (Exception e) { - _logger.LogDebug("error calling middleware"); + Logger.LogDebug("error calling middleware"); var message = CreateMessage(context, e); - _logger.LogError(message, e); + Logger.LogError(message, e); SetInternalServerErrorOnResponse(context); } - _logger.LogDebug("ocelot pipeline finished"); + Logger.LogDebug("ocelot pipeline finished"); } private async Task TrySetGlobalRequestId(DownstreamContext context) { - //try and get the global request id and set it for logs... - //should this basically be immutable per request...i guess it should! - //first thing is get config - var configuration = await _provider.Get(); + //try and get the global request id and set it for logs... + //should this basically be immutable per request...i guess it should! + //first thing is get config + var configuration = await _provider.Get(); - //if error throw to catch below.. - if(configuration.IsError) - { - throw new Exception($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}"); - } - - //else set the request id? - var key = configuration.Data.RequestId; - - StringValues upstreamRequestIds; - if (!string.IsNullOrEmpty(key) && context.HttpContext.Request.Headers.TryGetValue(key, out upstreamRequestIds)) - { - //todo fix looking in both places - context.HttpContext.TraceIdentifier = upstreamRequestIds.First(); - _repo.Add("RequestId", context.HttpContext.TraceIdentifier); + if(configuration.IsError) + { + throw new Exception($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}"); } + + var key = configuration.Data.RequestId; + + if (!string.IsNullOrEmpty(key) && context.HttpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds)) + { + context.HttpContext.TraceIdentifier = upstreamRequestIds.First(); + } + + _repo.Add("RequestId", context.HttpContext.TraceIdentifier); } private void SetInternalServerErrorOnResponse(DownstreamContext context) diff --git a/src/Ocelot/Headers/AddHeadersToResponse.cs b/src/Ocelot/Headers/AddHeadersToResponse.cs index 40d829ee..76e9912b 100644 --- a/src/Ocelot/Headers/AddHeadersToResponse.cs +++ b/src/Ocelot/Headers/AddHeadersToResponse.cs @@ -27,7 +27,7 @@ namespace Ocelot.Headers if(value.IsError) { - _logger.LogError($"Unable to add header to response {add.Key}: {add.Value}"); + _logger.LogWarning($"Unable to add header to response {add.Key}: {add.Value}"); continue; } diff --git a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs index b09b40f8..de5c25da 100644 --- a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs @@ -10,7 +10,6 @@ namespace Ocelot.Headers.Middleware public class HttpHeadersTransformationMiddleware : OcelotMiddleware { private readonly OcelotRequestDelegate _next; - private readonly IOcelotLogger _logger; private readonly IHttpContextRequestHeaderReplacer _preReplacer; private readonly IHttpResponseHeaderReplacer _postReplacer; private readonly IAddHeadersToResponse _addHeaders; @@ -20,12 +19,12 @@ namespace Ocelot.Headers.Middleware IHttpContextRequestHeaderReplacer preReplacer, IHttpResponseHeaderReplacer postReplacer, IAddHeadersToResponse addHeaders) + :base(loggerFactory.CreateLogger()) { _addHeaders = addHeaders; _next = next; _postReplacer = postReplacer; _preReplacer = preReplacer; - _logger = loggerFactory.CreateLogger(); } public async Task Invoke(DownstreamContext context) diff --git a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs index 6e8dc31e..ba340b3a 100644 --- a/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpRequestHeadersBuilderMiddleware.cs @@ -12,34 +12,33 @@ namespace Ocelot.Headers.Middleware { private readonly OcelotRequestDelegate _next; private readonly IAddHeadersToRequest _addHeadersToRequest; - private readonly IOcelotLogger _logger; public HttpRequestHeadersBuilderMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, - IAddHeadersToRequest addHeadersToRequest) + IAddHeadersToRequest addHeadersToRequest) + :base(loggerFactory.CreateLogger()) { _next = next; _addHeadersToRequest = addHeadersToRequest; - _logger = loggerFactory.CreateLogger(); } public async Task Invoke(DownstreamContext context) { if (context.DownstreamReRoute.ClaimsToHeaders.Any()) { - _logger.LogDebug($"{ context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to headers"); + Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to headers"); var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.ClaimsToHeaders, context.HttpContext.User.Claims, context.DownstreamRequest); if (response.IsError) { - _logger.LogDebug("Error setting headers on context, setting pipeline error"); + Logger.LogWarning("Error setting headers on context, setting pipeline error"); SetPipelineError(context, response.Errors); return; } - _logger.LogDebug("headers have been set on context"); + Logger.LogInformation("headers have been set on context"); } await _next.Invoke(context); diff --git a/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs b/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs index 1af0acbc..773b4375 100644 --- a/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs +++ b/src/Ocelot/Infrastructure/Claims/Parser/ClaimsParser.cs @@ -26,10 +26,7 @@ if (splits.Length < index || index < 0) { - return new ErrorResponse(new List - { - new CannotFindClaimError($"Cannot find claim for key: {key}, delimiter: {delimiter}, index: {index}") - }); + return new ErrorResponse(new CannotFindClaimError($"Cannot find claim for key: {key}, delimiter: {delimiter}, index: {index}")); } var value = splits[index]; @@ -55,10 +52,7 @@ return new OkResponse(claim.Value); } - return new ErrorResponse(new List - { - new CannotFindClaimError($"Cannot find claim for key: {key}") - }); + return new ErrorResponse(new CannotFindClaimError($"Cannot find claim for key: {key}")); } } } diff --git a/src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs b/src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs index 1b6d1682..3c85ce5c 100644 --- a/src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs +++ b/src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs @@ -3,7 +3,7 @@ using System.Linq; namespace Ocelot.Infrastructure.Extensions { - internal static class StringValueExtensions + internal static class StringValuesExtensions { public static string GetValue(this StringValues stringValues) { @@ -11,6 +11,7 @@ namespace Ocelot.Infrastructure.Extensions { return stringValues; } + return stringValues.ToArray().LastOrDefault(); } } diff --git a/src/Ocelot/Infrastructure/RequestData/HttpDataRepository.cs b/src/Ocelot/Infrastructure/RequestData/HttpDataRepository.cs index 0f75411d..ec2ce4e0 100644 --- a/src/Ocelot/Infrastructure/RequestData/HttpDataRepository.cs +++ b/src/Ocelot/Infrastructure/RequestData/HttpDataRepository.cs @@ -24,10 +24,7 @@ namespace Ocelot.Infrastructure.RequestData } catch (Exception exception) { - return new ErrorResponse(new List - { - new CannotAddDataError(string.Format($"Unable to add data for key: {key}, exception: {exception.Message}")) - }); + return new ErrorResponse(new CannotAddDataError(string.Format($"Unable to add data for key: {key}, exception: {exception.Message}"))); } } @@ -40,10 +37,7 @@ namespace Ocelot.Infrastructure.RequestData } catch (Exception exception) { - return new ErrorResponse(new List - { - new CannotAddDataError(string.Format($"Unable to update data for key: {key}, exception: {exception.Message}")) - }); + return new ErrorResponse(new CannotAddDataError(string.Format($"Unable to update data for key: {key}, exception: {exception.Message}"))); } } @@ -53,10 +47,7 @@ namespace Ocelot.Infrastructure.RequestData if(_httpContextAccessor.HttpContext == null || _httpContextAccessor.HttpContext.Items == null) { - return new ErrorResponse(new List - { - new CannotFindDataError($"Unable to find data for key: {key} because HttpContext or HttpContext.Items is null") - }); + return new ErrorResponse(new CannotFindDataError($"Unable to find data for key: {key} because HttpContext or HttpContext.Items is null")); } if(_httpContextAccessor.HttpContext.Items.TryGetValue(key, out obj)) @@ -65,10 +56,7 @@ namespace Ocelot.Infrastructure.RequestData return new OkResponse(data); } - return new ErrorResponse(new List - { - new CannotFindDataError($"Unable to find data for key: {key}") - }); + return new ErrorResponse(new CannotFindDataError($"Unable to find data for key: {key}")); } } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs index 1c99c3c5..2810e021 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs @@ -28,12 +28,12 @@ namespace Ocelot.LoadBalancer.LoadBalancers if (services == null) { - return new ErrorResponse(new List() { new ServicesAreNullError($"services were null for {_serviceName}") }); + return new ErrorResponse(new ServicesAreNullError($"services were null for {_serviceName}") ); } if (!services.Any()) { - return new ErrorResponse(new List() { new ServicesAreEmptyError($"services were empty for {_serviceName}") }); + return new ErrorResponse(new ServicesAreEmptyError($"services were empty for {_serviceName}")); } lock(_syncLock) diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index 82f792f9..f5ddf6f2 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -9,15 +9,14 @@ namespace Ocelot.LoadBalancer.Middleware public class LoadBalancingMiddleware : OcelotMiddleware { private readonly OcelotRequestDelegate _next; - private readonly IOcelotLogger _logger; private readonly ILoadBalancerHouse _loadBalancerHouse; public LoadBalancingMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, ILoadBalancerHouse loadBalancerHouse) + :base(loggerFactory.CreateLogger()) { _next = next; - _logger = loggerFactory.CreateLogger(); _loadBalancerHouse = loadBalancerHouse; } @@ -26,7 +25,7 @@ namespace Ocelot.LoadBalancer.Middleware var loadBalancer = await _loadBalancerHouse.Get(context.DownstreamReRoute, context.ServiceProviderConfiguration); if(loadBalancer.IsError) { - _logger.LogDebug("there was an error retriving the loadbalancer, setting pipeline error"); + Logger.LogDebug("there was an error retriving the loadbalancer, setting pipeline error"); SetPipelineError(context, loadBalancer.Errors); return; } @@ -34,7 +33,7 @@ namespace Ocelot.LoadBalancer.Middleware var hostAndPort = await loadBalancer.Data.Lease(); if(hostAndPort.IsError) { - _logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error"); + Logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error"); SetPipelineError(context, hostAndPort.Errors); return; } @@ -52,7 +51,7 @@ namespace Ocelot.LoadBalancer.Middleware } catch (Exception) { - _logger.LogDebug("Exception calling next middleware, exception will be thrown to global handler"); + Logger.LogDebug("Exception calling next middleware, exception will be thrown to global handler"); throw; } finally diff --git a/src/Ocelot/Logging/AspDotNetLogger.cs b/src/Ocelot/Logging/AspDotNetLogger.cs index eae1eec5..5ee04e13 100644 --- a/src/Ocelot/Logging/AspDotNetLogger.cs +++ b/src/Ocelot/Logging/AspDotNetLogger.cs @@ -1,6 +1,5 @@ using System; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Internal; using Ocelot.Infrastructure.RequestData; namespace Ocelot.Logging @@ -10,34 +9,38 @@ namespace Ocelot.Logging private readonly ILogger _logger; private readonly IRequestScopedDataRepository _scopedDataRepository; - public string Name { get; } - - public AspDotNetLogger(ILogger logger, IRequestScopedDataRepository scopedDataRepository, string typeName) + public AspDotNetLogger(ILogger logger, IRequestScopedDataRepository scopedDataRepository) { - Name = typeName; _logger = logger; _scopedDataRepository = scopedDataRepository; } - public void LogTrace(string message, params object[] args) + public void LogTrace(string message) { var requestId = GetOcelotRequestId(); var previousRequestId = GetOcelotPreviousRequestId(); - _logger.LogTrace("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message},", requestId, previousRequestId, new FormattedLogValues(message, args).ToString()); + _logger.LogTrace("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}", requestId, previousRequestId, message); } - public void LogDebug(string message, params object[] args) + public void LogDebug(string message) { var requestId = GetOcelotRequestId(); var previousRequestId = GetOcelotPreviousRequestId(); - _logger.LogDebug("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message},", requestId, previousRequestId, new FormattedLogValues(message, args).ToString()); + _logger.LogDebug("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}", requestId, previousRequestId, message); } - public void LogInformation(string message, params object[] args) + public void LogInformation(string message) { var requestId = GetOcelotRequestId(); var previousRequestId = GetOcelotPreviousRequestId(); - _logger.LogInformation("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message},", requestId, previousRequestId, new FormattedLogValues(message, args).ToString()); + _logger.LogInformation("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}", requestId, previousRequestId, message); + } + + public void LogWarning(string message) + { + var requestId = GetOcelotRequestId(); + var previousRequestId = GetOcelotPreviousRequestId(); + _logger.LogWarning("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}", requestId, previousRequestId, message); } public void LogError(string message, Exception exception) @@ -47,18 +50,11 @@ namespace Ocelot.Logging _logger.LogError("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}, exception: {exception}", requestId, previousRequestId, message, exception); } - public void LogError(string message, params object[] args) - { - var requestId = GetOcelotRequestId(); - var previousRequestId = GetOcelotPreviousRequestId(); - _logger.LogError("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}", requestId, previousRequestId, new FormattedLogValues(message, args).ToString()); - } - public void LogCritical(string message, Exception exception) { var requestId = GetOcelotRequestId(); var previousRequestId = GetOcelotPreviousRequestId(); - _logger.LogError("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}", requestId, previousRequestId, message); + _logger.LogCritical("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}, exception: {exception}", requestId, previousRequestId, message, exception); } private string GetOcelotRequestId() @@ -85,4 +81,4 @@ namespace Ocelot.Logging return requestId.Data; } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Logging/AspDotNetLoggerFactory.cs b/src/Ocelot/Logging/AspDotNetLoggerFactory.cs index 298653bc..b988e09c 100644 --- a/src/Ocelot/Logging/AspDotNetLoggerFactory.cs +++ b/src/Ocelot/Logging/AspDotNetLoggerFactory.cs @@ -1,8 +1,8 @@ -using Microsoft.Extensions.Logging; -using Ocelot.Infrastructure.RequestData; - -namespace Ocelot.Logging -{ +using Microsoft.Extensions.Logging; +using Ocelot.Infrastructure.RequestData; + +namespace Ocelot.Logging +{ public class AspDotNetLoggerFactory : IOcelotLoggerFactory { private readonly ILoggerFactory _loggerFactory; @@ -17,7 +17,7 @@ namespace Ocelot.Logging public IOcelotLogger CreateLogger() { var logger = _loggerFactory.CreateLogger(); - return new AspDotNetLogger(logger, _scopedDataRepository, typeof(T).Name); + return new AspDotNetLogger(logger, _scopedDataRepository); } - } + } } \ No newline at end of file diff --git a/src/Ocelot/Logging/IOcelotLogger.cs b/src/Ocelot/Logging/IOcelotLogger.cs index 67bac731..7d1c1205 100644 --- a/src/Ocelot/Logging/IOcelotLogger.cs +++ b/src/Ocelot/Logging/IOcelotLogger.cs @@ -7,16 +7,11 @@ namespace Ocelot.Logging /// public interface IOcelotLogger { - void LogTrace(string message, params object[] args); - void LogDebug(string message, params object[] args); - void LogInformation(string message, params object[] args); + void LogTrace(string message); + void LogDebug(string message); + void LogInformation(string message); + void LogWarning(string message); void LogError(string message, Exception exception); - void LogError(string message, params object[] args); void LogCritical(string message, Exception exception); - - /// - /// The name of the type the logger has been built for. - /// - string Name { get; } } } diff --git a/src/Ocelot/Logging/OcelotDiagnosticListener.cs b/src/Ocelot/Logging/OcelotDiagnosticListener.cs index d75cfd2c..f2c51760 100644 --- a/src/Ocelot/Logging/OcelotDiagnosticListener.cs +++ b/src/Ocelot/Logging/OcelotDiagnosticListener.cs @@ -8,15 +8,14 @@ using Butterfly.Client.Tracing; using System.Linq; using System.Collections.Generic; using Ocelot.Infrastructure.Extensions; -using Microsoft.Extensions.Logging; using Ocelot.Requester; namespace Ocelot.Logging { public class OcelotDiagnosticListener { - private IServiceTracer _tracer; - private IOcelotLogger _logger; + private readonly IServiceTracer _tracer; + private readonly IOcelotLogger _logger; public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceTracer tracer) { @@ -27,7 +26,7 @@ namespace Ocelot.Logging [DiagnosticName("Ocelot.MiddlewareException")] public virtual void OcelotMiddlewareException(Exception exception, DownstreamContext context, string name) { - _logger.LogTrace($"Ocelot.MiddlewareException: {name}; {exception.Message}"); + _logger.LogTrace($"Ocelot.MiddlewareException: {name}; {exception.Message};"); Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}"); } @@ -41,7 +40,7 @@ namespace Ocelot.Logging [DiagnosticName("Ocelot.MiddlewareFinished")] public virtual void OcelotMiddlewareFinished(DownstreamContext context, string name) { - _logger.LogTrace($"OcelotMiddlewareFinished: {name}; {context.HttpContext.Request.Path}"); + _logger.LogTrace($"Ocelot.MiddlewareFinished: {name}; {context.HttpContext.Request.Path}"); Event(context.HttpContext, $"OcelotMiddlewareFinished: {name}; {context.HttpContext.Request.Path}"); } @@ -55,7 +54,7 @@ namespace Ocelot.Logging [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException")] public virtual void OnMiddlewareException(Exception exception, string name) { - _logger.LogTrace($"MiddlewareException: {name}; {exception.Message}"); + _logger.LogTrace($"MiddlewareException: {name}; {exception.Message};"); } [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareFinished")] @@ -67,16 +66,17 @@ namespace Ocelot.Logging private void Event(HttpContext httpContext, string @event) { - // Hack - if the user isnt using tracing the code gets here and will blow up on + // todo - if the user isnt using tracing the code gets here and will blow up on // _tracer.Tracer.TryExtract. We already use the fake tracer for another scenario - // so sticking it here as well..I guess we need a factory for this but no idea - // how to hook that into the diagnostic framework at the moment. + // so sticking it here as well..I guess we need a factory for this but cba to do it at + // the moment if(_tracer.GetType() == typeof(FakeServiceTracer)) { return; } var span = httpContext.GetSpan(); + if(span == null) { var spanBuilder = new SpanBuilder($"server {httpContext.Request.Method} {httpContext.Request.Path}"); @@ -84,10 +84,12 @@ namespace Ocelot.Logging c => c.Select(x => new KeyValuePair(x.Key, x.Value.GetValue())).GetEnumerator())) { spanBuilder.AsChildOf(spanContext); - }; + } + span = _tracer.Start(spanBuilder); httpContext.SetSpan(span); } + span?.Log(LogField.CreateNew().Event(@event)); } } diff --git a/src/Ocelot/Middleware/DownstreamContext.cs b/src/Ocelot/Middleware/DownstreamContext.cs index 9e054eb5..044d1510 100644 --- a/src/Ocelot/Middleware/DownstreamContext.cs +++ b/src/Ocelot/Middleware/DownstreamContext.cs @@ -13,17 +13,24 @@ namespace Ocelot.Middleware { public DownstreamContext(HttpContext httpContext) { - this.HttpContext = httpContext; + HttpContext = httpContext; Errors = new List(); } public List TemplatePlaceholderNameAndValues { get; set; } + public ServiceProviderConfiguration ServiceProviderConfiguration {get; set;} - public HttpContext HttpContext { get; private set; } + + public HttpContext HttpContext { get; } + public DownstreamReRoute DownstreamReRoute { get; set; } + public DownstreamRequest DownstreamRequest { get; set; } + public HttpResponseMessage DownstreamResponse { get; set; } + public List Errors { get;set; } + public bool IsError => Errors.Count > 0; } } diff --git a/src/Ocelot/Middleware/OcelotMiddleware.cs b/src/Ocelot/Middleware/OcelotMiddleware.cs index bf49fd03..fe51fd24 100644 --- a/src/Ocelot/Middleware/OcelotMiddleware.cs +++ b/src/Ocelot/Middleware/OcelotMiddleware.cs @@ -1,20 +1,33 @@ using System.Collections.Generic; using Ocelot.Errors; +using Ocelot.Logging; namespace Ocelot.Middleware { public abstract class OcelotMiddleware - { - protected OcelotMiddleware() + { + protected OcelotMiddleware(IOcelotLogger logger) { + Logger = logger; MiddlewareName = this.GetType().Name; } + public IOcelotLogger Logger { get; } + public string MiddlewareName { get; } public void SetPipelineError(DownstreamContext context, List errors) { - context.Errors = errors; + foreach(var error in errors) + { + SetPipelineError(context, error); + } + } + + public void SetPipelineError(DownstreamContext context, Error error) + { + Logger.LogWarning(error.Message); + context.Errors.Add(error); } } } diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs index 539fa6a1..573e0924 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs @@ -88,7 +88,7 @@ namespace Ocelot.Middleware.Pipeline } catch(Exception ex) { - Write(diagnosticListener, "Ocelot.MiddlewareException", middlewareName, context); + WriteException(diagnosticListener, ex, "Ocelot.MiddlewareException", middlewareName, context); throw ex; } finally @@ -123,6 +123,14 @@ namespace Ocelot.Middleware.Pipeline } } + private static void WriteException(DiagnosticListener diagnosticListener, Exception exception, string message, string middlewareName, DownstreamContext context) + { + if(diagnosticListener != null) + { + diagnosticListener.Write(message, new { name = middlewareName, context = context, exception = exception }); + } + } + public static IOcelotPipelineBuilder MapWhen(this IOcelotPipelineBuilder app, Predicate predicate, Action configuration) { if (app == null) diff --git a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs index ae5dca85..bc886c5d 100644 --- a/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs +++ b/src/Ocelot/QueryStrings/Middleware/QueryStringBuilderMiddleware.cs @@ -12,28 +12,27 @@ namespace Ocelot.QueryStrings.Middleware { private readonly OcelotRequestDelegate _next; private readonly IAddQueriesToRequest _addQueriesToRequest; - private readonly IOcelotLogger _logger; public QueryStringBuilderMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, IAddQueriesToRequest addQueriesToRequest) + : base(loggerFactory.CreateLogger()) { _next = next; _addQueriesToRequest = addQueriesToRequest; - _logger = loggerFactory.CreateLogger(); } public async Task Invoke(DownstreamContext context) { if (context.DownstreamReRoute.ClaimsToQueries.Any()) { - _logger.LogDebug($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to queries"); + Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to queries"); var response = _addQueriesToRequest.SetQueriesOnDownstreamRequest(context.DownstreamReRoute.ClaimsToQueries, context.HttpContext.User.Claims, context.DownstreamRequest); if (response.IsError) { - _logger.LogDebug("there was an error setting queries on context, setting pipeline error"); + Logger.LogWarning("there was an error setting queries on context, setting pipeline error"); SetPipelineError(context, response.Errors); return; diff --git a/src/Ocelot/Raft/RaftController.cs b/src/Ocelot/Raft/RaftController.cs index eb91e86f..c1222e4d 100644 --- a/src/Ocelot/Raft/RaftController.cs +++ b/src/Ocelot/Raft/RaftController.cs @@ -40,9 +40,13 @@ namespace Ocelot.Raft using(var reader = new StreamReader(HttpContext.Request.Body)) { var json = await reader.ReadToEndAsync(); + var appendEntries = JsonConvert.DeserializeObject(json, _jsonSerialiserSettings); + _logger.LogDebug($"{_baseSchemeUrlAndPort}/appendentries called, my state is {_node.State.GetType().FullName}"); + var appendEntriesResponse = _node.Handle(appendEntries); + return new OkObjectResult(appendEntriesResponse); } } @@ -53,9 +57,13 @@ namespace Ocelot.Raft using(var reader = new StreamReader(HttpContext.Request.Body)) { var json = await reader.ReadToEndAsync(); + var requestVote = JsonConvert.DeserializeObject(json, _jsonSerialiserSettings); + _logger.LogDebug($"{_baseSchemeUrlAndPort}/requestvote called, my state is {_node.State.GetType().FullName}"); + var requestVoteResponse = _node.Handle(requestVote); + return new OkObjectResult(requestVoteResponse); } } @@ -68,10 +76,15 @@ namespace Ocelot.Raft using(var reader = new StreamReader(HttpContext.Request.Body)) { var json = await reader.ReadToEndAsync(); + var command = JsonConvert.DeserializeObject(json, _jsonSerialiserSettings); + _logger.LogDebug($"{_baseSchemeUrlAndPort}/command called, my state is {_node.State.GetType().FullName}"); + var commandResponse = _node.Accept(command); + json = JsonConvert.SerializeObject(commandResponse, _jsonSerialiserSettings); + return StatusCode(200, json); } } diff --git a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs index 4ceb91f8..50eb7776 100644 --- a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs +++ b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs @@ -14,16 +14,15 @@ namespace Ocelot.RateLimit.Middleware public class ClientRateLimitMiddleware : OcelotMiddleware { private readonly OcelotRequestDelegate _next; - private readonly IOcelotLogger _logger; private readonly IRateLimitCounterHandler _counterHandler; private readonly ClientRateLimitProcessor _processor; public ClientRateLimitMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, IRateLimitCounterHandler counterHandler) + :base(loggerFactory.CreateLogger()) { _next = next; - _logger = loggerFactory.CreateLogger(); _counterHandler = counterHandler; _processor = new ClientRateLimitProcessor(counterHandler); } @@ -35,7 +34,7 @@ namespace Ocelot.RateLimit.Middleware // check if rate limiting is enabled if (!context.DownstreamReRoute.EnableEndpointEndpointRateLimiting) { - _logger.LogDebug($"EndpointRateLimiting is not enabled for {context.DownstreamReRoute.DownstreamPathTemplate}"); + Logger.LogInformation($"EndpointRateLimiting is not enabled for {context.DownstreamReRoute.DownstreamPathTemplate.Value}"); await _next.Invoke(context); return; } @@ -46,7 +45,7 @@ namespace Ocelot.RateLimit.Middleware // check white list if (IsWhitelisted(identity, options)) { - _logger.LogDebug($"{context.DownstreamReRoute.DownstreamPathTemplate} is white listed from rate limiting"); + Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} is white listed from rate limiting"); await _next.Invoke(context); return; } @@ -112,9 +111,10 @@ namespace Ocelot.RateLimit.Middleware public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule, DownstreamReRoute downstreamReRoute) { - _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 { downstreamReRoute.UpstreamPathTemplate }, TraceIdentifier {httpContext.TraceIdentifier}."); + Logger.LogInformation( + $"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { downstreamReRoute.UpstreamPathTemplate.Value }, TraceIdentifier {httpContext.TraceIdentifier}."); } - + 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; diff --git a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs index ecdb134a..0cb7c7dd 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs @@ -11,15 +11,14 @@ namespace Ocelot.Request.Middleware public class DownstreamRequestInitialiserMiddleware : OcelotMiddleware { private readonly OcelotRequestDelegate _next; - private readonly IOcelotLogger _logger; private readonly Mapper.IRequestMapper _requestMapper; public DownstreamRequestInitialiserMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, Mapper.IRequestMapper requestMapper) + :base(loggerFactory.CreateLogger()) { _next = next; - _logger = loggerFactory.CreateLogger(); _requestMapper = requestMapper; } diff --git a/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs b/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs index 7ae416b0..f999bf40 100644 --- a/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs +++ b/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs @@ -1,14 +1,11 @@ +using System; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; -using System.Net.Http; using System.Net.Http.Headers; using System.Collections.Generic; -using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Request.Middleware; namespace Ocelot.RequestId.Middleware @@ -16,16 +13,15 @@ namespace Ocelot.RequestId.Middleware public class ReRouteRequestIdMiddleware : OcelotMiddleware { private readonly OcelotRequestDelegate _next; - private readonly IOcelotLogger _logger; private readonly IRequestScopedDataRepository _requestScopedDataRepository; public ReRouteRequestIdMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, IRequestScopedDataRepository requestScopedDataRepository) + : base(loggerFactory.CreateLogger()) { _next = next; _requestScopedDataRepository = requestScopedDataRepository; - _logger = loggerFactory.CreateLogger(); } public async Task Invoke(DownstreamContext context) @@ -38,26 +34,23 @@ namespace Ocelot.RequestId.Middleware { // if get request ID is set on upstream request then retrieve it var key = context.DownstreamReRoute.RequestIdKey ?? DefaultRequestIdKey.Value; - - StringValues upstreamRequestIds; - if (context.HttpContext.Request.Headers.TryGetValue(key, out upstreamRequestIds)) + + if (context.HttpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds)) { - //set the traceidentifier context.HttpContext.TraceIdentifier = upstreamRequestIds.First(); - //todo fix looking in both places //check if we have previous id in scoped repo var previousRequestId = _requestScopedDataRepository.Get("RequestId"); - if (!previousRequestId.IsError && !string.IsNullOrEmpty(previousRequestId.Data)) + if (!previousRequestId.IsError && !string.IsNullOrEmpty(previousRequestId.Data) && previousRequestId.Data != context.HttpContext.TraceIdentifier) { //we have a previous request id lets store it and update request id - _requestScopedDataRepository.Add("PreviousRequestId", previousRequestId.Data); - _requestScopedDataRepository.Update("RequestId", context.HttpContext.TraceIdentifier); + _requestScopedDataRepository.Add("PreviousRequestId", previousRequestId.Data); + _requestScopedDataRepository.Update("RequestId", context.HttpContext.TraceIdentifier); } else { //else just add request id - _requestScopedDataRepository.Add("RequestId", context.HttpContext.TraceIdentifier); + _requestScopedDataRepository.Add("RequestId", context.HttpContext.TraceIdentifier); } } diff --git a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs index 68397b9f..23ebd9de 100644 --- a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs +++ b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs @@ -12,15 +12,14 @@ namespace Ocelot.Requester.Middleware { private readonly OcelotRequestDelegate _next; private readonly IHttpRequester _requester; - private readonly IOcelotLogger _logger; public HttpRequesterMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, IHttpRequester requester) + : base(loggerFactory.CreateLogger()) { _next = next; _requester = requester; - _logger = loggerFactory.CreateLogger(); } public async Task Invoke(DownstreamContext context) @@ -29,13 +28,13 @@ namespace Ocelot.Requester.Middleware if (response.IsError) { - _logger.LogDebug("IHttpRequester returned an error, setting pipeline error"); + Logger.LogDebug("IHttpRequester returned an error, setting pipeline error"); SetPipelineError(context, response.Errors); return; } - _logger.LogDebug("setting http response message"); + Logger.LogDebug("setting http response message"); context.DownstreamResponse = response.Data; } diff --git a/src/Ocelot/Requester/OcelotHttpTracingHandler.cs b/src/Ocelot/Requester/OcelotHttpTracingHandler.cs index 9de364ac..fd6dce2b 100644 --- a/src/Ocelot/Requester/OcelotHttpTracingHandler.cs +++ b/src/Ocelot/Requester/OcelotHttpTracingHandler.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -13,7 +12,7 @@ namespace Ocelot.Requester { private readonly IServiceTracer _tracer; private readonly IRequestScopedDataRepository _repo; - private const string prefix_spanId = "ot-spanId"; + private const string PrefixSpanId = "ot-spanId"; public OcelotHttpTracingHandler( IServiceTracer tracer, @@ -37,11 +36,10 @@ namespace Ocelot.Requester HttpRequestMessage request, CancellationToken cancellationToken) { - IEnumerable traceIdVals = null; - if (request.Headers.TryGetValues(prefix_spanId, out traceIdVals)) + if (request.Headers.Contains(PrefixSpanId)) { - request.Headers.Remove(prefix_spanId); - request.Headers.TryAddWithoutValidation(prefix_spanId, span.SpanContext.SpanId); + request.Headers.Remove(PrefixSpanId); + request.Headers.TryAddWithoutValidation(PrefixSpanId, span.SpanContext.SpanId); } _repo.Add("TraceId", span.SpanContext.TraceId); diff --git a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs index 125280b0..3e04fa3a 100644 --- a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs +++ b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs @@ -6,6 +6,7 @@ using Ocelot.Middleware; using System.Collections.Generic; using System.Threading.Tasks; using Ocelot.DownstreamRouteFinder.Middleware; +using Ocelot.Infrastructure.Extensions; namespace Ocelot.Responder.Middleware { @@ -17,18 +18,17 @@ namespace Ocelot.Responder.Middleware private readonly OcelotRequestDelegate _next; private readonly IHttpResponder _responder; private readonly IErrorsToHttpStatusCodeMapper _codeMapper; - private readonly IOcelotLogger _logger; public ResponderMiddleware(OcelotRequestDelegate next, IHttpResponder responder, IOcelotLoggerFactory loggerFactory, IErrorsToHttpStatusCodeMapper codeMapper ) + :base(loggerFactory.CreateLogger()) { _next = next; _responder = responder; _codeMapper = codeMapper; - _logger = loggerFactory.CreateLogger(); } public async Task Invoke(DownstreamContext context) @@ -37,19 +37,13 @@ namespace Ocelot.Responder.Middleware if (context.IsError) { - var errors = context.Errors; - _logger.LogError($"{errors.Count} pipeline errors found in {MiddlewareName}. Setting error response status code"); - - foreach(var error in errors) - { - _logger.LogError(error.Message); - } + Logger.LogWarning($"{context.Errors.ToErrorString()} errors found in {MiddlewareName}. Setting error response for request path:{context.HttpContext.Request.Path}, request method: {context.HttpContext.Request.Method}"); - SetErrorResponse(context.HttpContext, errors); + SetErrorResponse(context.HttpContext, context.Errors); } else { - _logger.LogDebug("no pipeline errors, setting and returning completed response"); + Logger.LogDebug("no pipeline errors, setting and returning completed response"); await _responder.SetResponseOnHttpContext(context.HttpContext, context.DownstreamResponse); } } diff --git a/src/Ocelot/Responses/ErrorResponse.cs b/src/Ocelot/Responses/ErrorResponse.cs index 7a6688e7..156243ab 100644 --- a/src/Ocelot/Responses/ErrorResponse.cs +++ b/src/Ocelot/Responses/ErrorResponse.cs @@ -5,11 +5,13 @@ namespace Ocelot.Responses { public class ErrorResponse : Response { - public ErrorResponse(Error error) : base(new List{error}) + public ErrorResponse(Error error) + : base(new List{error}) { } - public ErrorResponse(List errors) : base(errors) + public ErrorResponse(List errors) + : base(errors) { } } diff --git a/src/Ocelot/Responses/Response.cs b/src/Ocelot/Responses/Response.cs index 21ec91b5..3a387a2b 100644 --- a/src/Ocelot/Responses/Response.cs +++ b/src/Ocelot/Responses/Response.cs @@ -15,14 +15,8 @@ namespace Ocelot.Responses Errors = errors ?? new List(); } - public List Errors { get; private set; } + public List Errors { get; } - public bool IsError - { - get - { - return Errors.Count > 0; - } - } + public bool IsError => Errors.Count > 0; } -} \ No newline at end of file +} diff --git a/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs index 775033fd..de31188e 100644 --- a/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs +++ b/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs @@ -47,7 +47,7 @@ namespace Ocelot.ServiceDiscovery.Providers } else { - _logger.LogError($"Unable to use service Address: {serviceEntry.Service.Address} and Port: {serviceEntry.Service.Port} as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"); + _logger.LogWarning($"Unable to use service Address: {serviceEntry.Service.Address} and Port: {serviceEntry.Service.Port} as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"); } } diff --git a/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs b/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs index 7a9e0ad0..68b2df48 100644 --- a/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs +++ b/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs @@ -11,13 +11,12 @@ namespace Ocelot.WebSockets.Middleware public class WebSocketsProxyMiddleware : OcelotMiddleware { private OcelotRequestDelegate _next; - private IOcelotLogger _logger; public WebSocketsProxyMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory) + :base(loggerFactory.CreateLogger()) { _next = next; - _logger = loggerFactory.CreateLogger(); } public async Task Invoke(DownstreamContext context) diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index 4e728d3d..3bab21b7 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -37,11 +37,11 @@ namespace Ocelot.ManualTest { x.WithDictionaryHandle(); }) - .AddOpenTracing(option => + /* .AddOpenTracing(option => { option.CollectorUrl = "http://localhost:9618"; option.Service = "Ocelot.ManualTest"; - }) + })*/ .AddAdministration("/administration", "secret"); }) .ConfigureLogging((hostingContext, logging) => diff --git a/test/Ocelot.ManualTest/appsettings.json b/test/Ocelot.ManualTest/appsettings.json index c10bfed6..584fc4fc 100644 --- a/test/Ocelot.ManualTest/appsettings.json +++ b/test/Ocelot.ManualTest/appsettings.json @@ -2,7 +2,7 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Error", + "Default": "Trace", "System": "Error", "Microsoft": "Error" } diff --git a/test/Ocelot.UnitTests/Configuration/ClaimsToThingCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ClaimsToThingCreatorTests.cs index 522d853f..cda6d5de 100644 --- a/test/Ocelot.UnitTests/Configuration/ClaimsToThingCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ClaimsToThingCreatorTests.cs @@ -72,7 +72,7 @@ namespace Ocelot.UnitTests.Configuration private void ThenTheLoggerIsCalledCorrectly() { _logger - .Verify(x => x.LogDebug(It.IsAny(), It.IsAny()), Times.Once); + .Verify(x => x.LogDebug(It.IsAny()), Times.Once); } private void ThenClaimsToThingsAreReturned() diff --git a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs index aa285e8d..67031879 100644 --- a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs @@ -124,7 +124,7 @@ namespace Ocelot.UnitTests.Configuration private void ThenTheLoggerIsCalledCorrectly(string message) { - _logger.Verify(x => x.LogError(message), Times.Once); + _logger.Verify(x => x.LogWarning(message), Times.Once); } [Fact] diff --git a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs index 89eaaa38..6b52a1c5 100644 --- a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs @@ -1,5 +1,3 @@ -using Ocelot.Middleware; - namespace Ocelot.UnitTests.Errors { using System; @@ -15,18 +13,18 @@ namespace Ocelot.UnitTests.Errors using Moq; using Ocelot.Configuration; using Ocelot.Errors; - using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Infrastructure.RequestData; + using Ocelot.Middleware; public class ExceptionHandlerMiddlewareTests { - bool _shouldThrowAnException = false; - private Mock _provider; - private Mock _repo; + bool _shouldThrowAnException; + private readonly Mock _provider; + private readonly Mock _repo; private Mock _loggerFactory; private Mock _logger; - private ExceptionHandlerMiddleware _middleware; - private DownstreamContext _downstreamContext; + private readonly ExceptionHandlerMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; private OcelotRequestDelegate _next; public ExceptionHandlerMiddlewareTests() @@ -59,7 +57,7 @@ namespace Ocelot.UnitTests.Errors .And(_ => GivenTheConfigurationIs(config)) .When(_ => WhenICallTheMiddleware()) .Then(_ => ThenTheResponseIsOk()) - .And(_ => TheRequestIdIsNotSet()) + .And(_ => TheAspDotnetRequestIdIsSet()) .BDDfy(); } @@ -89,7 +87,7 @@ namespace Ocelot.UnitTests.Errors } [Fact] - public void ShouldNotSetRequestId() + public void ShouldSetAspDotNetRequestId() { var config = new OcelotConfiguration(null, null, null, null); @@ -97,7 +95,7 @@ namespace Ocelot.UnitTests.Errors .And(_ => GivenTheConfigurationIs(config)) .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) .Then(_ => ThenTheResponseIsOk()) - .And(_ => TheRequestIdIsNotSet()) + .And(_ => TheAspDotnetRequestIdIsSet()) .BDDfy(); } @@ -146,29 +144,19 @@ namespace Ocelot.UnitTests.Errors private void GivenTheConfigReturnsError() { - var config = new OcelotConfiguration(null, null, null, null); - - var response = new Ocelot.Responses.ErrorResponse(new FakeError()); + var response = new Responses.ErrorResponse(new FakeError()); _provider .Setup(x => x.Get()).ReturnsAsync(response); } - public class FakeError : Error - { - public FakeError() - : base("meh", OcelotErrorCode.CannotAddDataError) - { - } - } - private void TheRequestIdIsSet(string key, string value) { - _repo.Verify(x => x.Add(key, value), Times.Once); + _repo.Verify(x => x.Add(key, value), Times.Once); } private void GivenTheConfigurationIs(IOcelotConfiguration config) { - var response = new Ocelot.Responses.OkResponse(config); + var response = new Responses.OkResponse(config); _provider .Setup(x => x.Get()).ReturnsAsync(response); } @@ -193,9 +181,17 @@ namespace Ocelot.UnitTests.Errors _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(500); } - private void TheRequestIdIsNotSet() + private void TheAspDotnetRequestIdIsSet() { - _repo.Verify(x => x.Add(It.IsAny(), It.IsAny()), Times.Never); + _repo.Verify(x => x.Add(It.IsAny(), It.IsAny()), Times.Once); + } + + class FakeError : Error + { + internal FakeError() + : base("meh", OcelotErrorCode.CannotAddDataError) + { + } } } } diff --git a/test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs b/test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs index a4a19eec..a8eba48e 100644 --- a/test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs +++ b/test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs @@ -106,7 +106,7 @@ namespace Ocelot.UnitTests.Headers private void ThenTheErrorIsLogged() { - _logger.Verify(x => x.LogError("Unable to add header to response Trace-Id: {TraceId}"), Times.Once); + _logger.Verify(x => x.LogWarning("Unable to add header to response Trace-Id: {TraceId}"), Times.Once); } private void ThenTheHeaderIsNotAdded(string key) @@ -145,4 +145,4 @@ namespace Ocelot.UnitTests.Headers _addHeaders = addHeaders; } } -} \ No newline at end of file +} diff --git a/test/Ocelot.UnitTests/Logging/AspDotNetLoggerTests.cs b/test/Ocelot.UnitTests/Logging/AspDotNetLoggerTests.cs new file mode 100644 index 00000000..8487cf8d --- /dev/null +++ b/test/Ocelot.UnitTests/Logging/AspDotNetLoggerTests.cs @@ -0,0 +1,81 @@ +namespace Ocelot.UnitTests.Logging +{ + using Moq; + using Xunit; + using Ocelot.Logging; + using Microsoft.Extensions.Logging; + using Ocelot.Infrastructure.RequestData; + using System; + + public class AspDotNetLoggerTests + { + private readonly Mock> _coreLogger; + private readonly AspDotNetLogger _logger; + private Mock _repo; + private readonly string _b; + private readonly string _a; + private readonly Exception _ex; + + public AspDotNetLoggerTests() + { + _a = "tom"; + _b = "laura"; + _ex = new Exception("oh no"); + _coreLogger = new Mock>(); + _repo = new Mock(); + _logger = new AspDotNetLogger(_coreLogger.Object, _repo.Object); + } + + [Fact] + public void should_log_trace() + { + _logger.LogTrace($"a message from {_a} to {_b}"); + + ThenLevelIsLogged("requestId: no request id, previousRequestId: no previous request id, message: a message from tom to laura", LogLevel.Trace); + } + + [Fact] + public void should_log_info() + { + _logger.LogInformation($"a message from {_a} to {_b}"); + + ThenLevelIsLogged("requestId: no request id, previousRequestId: no previous request id, message: a message from tom to laura", LogLevel.Information); + } + + [Fact] + public void should_log_warning() + { + _logger.LogWarning($"a message from {_a} to {_b}"); + + ThenLevelIsLogged("requestId: no request id, previousRequestId: no previous request id, message: a message from tom to laura", LogLevel.Warning); + } + + [Fact] + public void should_log_error() + { + + _logger.LogError($"a message from {_a} to {_b}", _ex); + + ThenLevelIsLogged("requestId: no request id, previousRequestId: no previous request id, message: a message from tom to laura, exception: System.Exception: oh no", LogLevel.Error); + } + + [Fact] + public void should_log_critical() + { + _logger.LogCritical($"a message from {_a} to {_b}", _ex); + + ThenLevelIsLogged("requestId: no request id, previousRequestId: no previous request id, message: a message from tom to laura, exception: System.Exception: oh no", LogLevel.Critical); + } + + private void ThenLevelIsLogged(string expected, LogLevel expectedLogLevel) + { + _coreLogger.Verify( + x => x.Log( + expectedLogLevel, + It.IsAny(), + It.Is(o => o.ToString() == expected), + It.IsAny(), + It.IsAny>()), Times.Once); + } + } +} diff --git a/test/Ocelot.UnitTests/Logging/OcelotDiagnosticListenerTests.cs b/test/Ocelot.UnitTests/Logging/OcelotDiagnosticListenerTests.cs new file mode 100644 index 00000000..3703ddb0 --- /dev/null +++ b/test/Ocelot.UnitTests/Logging/OcelotDiagnosticListenerTests.cs @@ -0,0 +1,145 @@ +using Ocelot.Logging; +using Moq; +using TestStack.BDDfy; +using Butterfly.Client.Tracing; +using Ocelot.Requester; +using Xunit; +using Ocelot.Middleware; +using Microsoft.AspNetCore.Http; +using System; + +namespace Ocelot.UnitTests.Logging +{ + public class OcelotDiagnosticListenerTests + { + private readonly OcelotDiagnosticListener _listener; + private Mock _factory; + private readonly Mock _logger; + private IServiceTracer _tracer; + private DownstreamContext _downstreamContext; + private string _name; + private Exception _exception; + + public OcelotDiagnosticListenerTests() + { + _factory = new Mock(); + _logger = new Mock(); + _tracer = new FakeServiceTracer(); + _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _listener = new OcelotDiagnosticListener(_factory.Object, _tracer); + } + + [Fact] + public void should_trace_ocelot_middleware_started() + { + this.Given(_ => GivenAMiddlewareName()) + .And(_ => GivenAContext()) + .When(_ => WhenOcelotMiddlewareStartedCalled()) + .Then(_ => ThenTheLogIs($"Ocelot.MiddlewareStarted: {_name}; {_downstreamContext.HttpContext.Request.Path}")) + .BDDfy(); + } + + [Fact] + public void should_trace_ocelot_middleware_finished() + { + this.Given(_ => GivenAMiddlewareName()) + .And(_ => GivenAContext()) + .When(_ => WhenOcelotMiddlewareFinishedCalled()) + .Then(_ => ThenTheLogIs($"Ocelot.MiddlewareFinished: {_name}; {_downstreamContext.HttpContext.Request.Path}")) + .BDDfy(); + } + + [Fact] + public void should_trace_ocelot_middleware_exception() + { + this.Given(_ => GivenAMiddlewareName()) + .And(_ => GivenAContext()) + .And(_ => GivenAException(new Exception("oh no"))) + .When(_ => WhenOcelotMiddlewareExceptionCalled()) + .Then(_ => ThenTheLogIs($"Ocelot.MiddlewareException: {_name}; {_exception.Message};")) + .BDDfy(); + } + + [Fact] + public void should_trace_middleware_started() + { + this.Given(_ => GivenAMiddlewareName()) + .And(_ => GivenAContext()) + .When(_ => WhenMiddlewareStartedCalled()) + .Then(_ => ThenTheLogIs($"MiddlewareStarting: {_name}; {_downstreamContext.HttpContext.Request.Path}")) + .BDDfy(); + } + + [Fact] + public void should_trace_middleware_finished() + { + this.Given(_ => GivenAMiddlewareName()) + .And(_ => GivenAContext()) + .When(_ => WhenMiddlewareFinishedCalled()) + .Then(_ => ThenTheLogIs($"MiddlewareFinished: {_name}; {_downstreamContext.HttpContext.Response.StatusCode}")) + .BDDfy(); + } + + [Fact] + public void should_trace_middleware_exception() + { + this.Given(_ => GivenAMiddlewareName()) + .And(_ => GivenAContext()) + .And(_ => GivenAException(new Exception("oh no"))) + .When(_ => WhenMiddlewareExceptionCalled()) + .Then(_ => ThenTheLogIs($"MiddlewareException: {_name}; {_exception.Message};")) + .BDDfy(); + } + + private void GivenAException(Exception exception) + { + _exception = exception; + } + + private void WhenOcelotMiddlewareStartedCalled() + { + _listener.OcelotMiddlewareStarted(_downstreamContext, _name); + } + + private void WhenOcelotMiddlewareFinishedCalled() + { + _listener.OcelotMiddlewareFinished(_downstreamContext, _name); + } + + private void WhenOcelotMiddlewareExceptionCalled() + { + _listener.OcelotMiddlewareException(_exception, _downstreamContext, _name); + } + + private void WhenMiddlewareStartedCalled() + { + _listener.OnMiddlewareStarting(_downstreamContext.HttpContext, _name); + } + + private void WhenMiddlewareFinishedCalled() + { + _listener.OnMiddlewareFinished(_downstreamContext.HttpContext, _name); + } + + private void WhenMiddlewareExceptionCalled() + { + _listener.OnMiddlewareException(_exception, _name); + } + + private void GivenAContext() + { + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + } + + private void GivenAMiddlewareName() + { + _name = "name"; + } + + private void ThenTheLogIs(string expected) + { + _logger.Verify( + x => x.LogTrace(expected)); + } + } +} diff --git a/test/Ocelot.UnitTests/Middleware/OcelotMiddlewareTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotMiddlewareTests.cs new file mode 100644 index 00000000..fd4e018c --- /dev/null +++ b/test/Ocelot.UnitTests/Middleware/OcelotMiddlewareTests.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Moq; +using Ocelot.Errors; +using Ocelot.Logging; +using Ocelot.Middleware; +using Ocelot.UnitTests.Responder; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Middleware +{ + public class OcelotMiddlewareTests + { + private Mock _logger; + private FakeMiddleware _middleware; + private List _errors; + + public OcelotMiddlewareTests() + { + _errors = new List(); + _logger = new Mock(); + _middleware = new FakeMiddleware(_logger.Object); + } + + [Fact] + public void should_log_error() + { + this.Given(x => GivenAnError(new AnyError())) + .When(x => WhenISetTheError()) + .Then(x => ThenTheErrorIsLogged(1)) + .BDDfy(); + } + + [Fact] + public void should_log_errors() + { + this.Given(x => GivenAnError(new AnyError())) + .And(x => GivenAnError(new AnyError())) + .When(x => WhenISetTheErrors()) + .Then(x => ThenTheErrorIsLogged(2)) + .BDDfy(); + } + + private void WhenISetTheErrors() + { + _middleware.SetPipelineError(new DownstreamContext(new DefaultHttpContext()), _errors); + } + + private void ThenTheErrorIsLogged(int times) + { + _logger.Verify(x => x.LogWarning("blahh"), Times.Exactly(times)); + } + + private void WhenISetTheError() + { + _middleware.SetPipelineError(new DownstreamContext(new DefaultHttpContext()), _errors[0]); + } + + private void GivenAnError(Error error) + { + _errors.Add(error); + } + + } + + public class FakeMiddleware : OcelotMiddleware + { + public FakeMiddleware(IOcelotLogger logger) + : base(logger) + { + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index 80084a4d..89af54d8 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -57,4 +57,8 @@ + + + + diff --git a/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs b/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs index 91c1e15c..44d9c405 100644 --- a/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs @@ -1,9 +1,6 @@ -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.RequestId +namespace Ocelot.UnitTests.RequestId { using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.Primitives; using Ocelot.Infrastructure.RequestData; using System; using System.Collections.Generic; @@ -21,6 +18,7 @@ namespace Ocelot.UnitTests.RequestId using TestStack.BDDfy; using Xunit; using Ocelot.Request.Middleware; + using Ocelot.Middleware; public class ReRouteRequestIdMiddlewareTests { @@ -142,6 +140,30 @@ namespace Ocelot.UnitTests.RequestId .BDDfy(); } + [Fact] + public void should_not_update_if_global_request_id_is_same_as_re_route_request_id() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + var requestId = "alreadyset"; + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenTheRequestIdWasSetGlobally()) + .And(x => x.GivenTheRequestIdIsAddedToTheRequest("LSRequestId", requestId)) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheTraceIdIs(requestId)) + .And(x => ThenTheRequestIdIsNotUpdated()) + .BDDfy(); + } + private void WhenICallTheMiddleware() { _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); @@ -159,12 +181,17 @@ namespace Ocelot.UnitTests.RequestId private void ThenTheRequestIdIsSaved() { - _repo.Verify(x => x.Add("RequestId", _value), Times.Once); + _repo.Verify(x => x.Add("RequestId", _value), Times.Once); } private void ThenTheRequestIdIsUpdated() { - _repo.Verify(x => x.Update("RequestId", _value), Times.Once); + _repo.Verify(x => x.Update("RequestId", _value), Times.Once); + } + + private void ThenTheRequestIdIsNotUpdated() + { + _repo.Verify(x => x.Update("RequestId", _value), Times.Never); } private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) @@ -182,15 +209,13 @@ namespace Ocelot.UnitTests.RequestId private void ThenTheTraceIdIsAnything() { - StringValues value; - _downstreamContext.HttpContext.Response.Headers.TryGetValue("LSRequestId", out value); + _downstreamContext.HttpContext.Response.Headers.TryGetValue("LSRequestId", out var value); value.First().ShouldNotBeNullOrEmpty(); } private void ThenTheTraceIdIs(string expected) { - StringValues value; - _downstreamContext.HttpContext.Response.Headers.TryGetValue("LSRequestId", out value); + _downstreamContext.HttpContext.Response.Headers.TryGetValue("LSRequestId", out var value); value.First().ShouldBe(expected); } } diff --git a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs index 2e9ffcd6..54a77f4d 100644 --- a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs @@ -51,7 +51,7 @@ namespace Ocelot.UnitTests.Responder public void should_return_any_errors() { this.Given(x => x.GivenTheHttpResponseMessageIs(new HttpResponseMessage())) - .And(x => x.GivenThereArePipelineErrors(new UnableToFindDownstreamRouteError())) + .And(x => x.GivenThereArePipelineErrors(new UnableToFindDownstreamRouteError("/path", "GET"))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenThereAreNoErrors()) .BDDfy(); diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs index fc67ab8e..299da57f 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs @@ -142,12 +142,12 @@ namespace Ocelot.UnitTests.ServiceDiscovery private void ThenTheLoggerHasBeenCalledCorrectlyForInvalidAddress() { _logger.Verify( - x => x.LogError( + x => x.LogWarning( "Unable to use service Address: http://localhost and Port: 50881 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"), Times.Once); _logger.Verify( - x => x.LogError( + x => x.LogWarning( "Unable to use service Address: http://localhost and Port: 50888 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"), Times.Once); } @@ -155,12 +155,12 @@ namespace Ocelot.UnitTests.ServiceDiscovery private void ThenTheLoggerHasBeenCalledCorrectlyForInvalidPorts() { _logger.Verify( - x => x.LogError( + x => x.LogWarning( "Unable to use service Address: localhost and Port: -1 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"), Times.Once); _logger.Verify( - x => x.LogError( + x => x.LogWarning( "Unable to use service Address: localhost and Port: 0 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"), Times.Once); } From 1a4f16353a76cf6c09a91830c6dc41fafc9f6f5a Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sat, 7 Apr 2018 21:26:07 +0100 Subject: [PATCH 24/50] #287 removed some comments --- .../RequestId/Middleware/ReRouteRequestIdMiddleware.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs b/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs index f999bf40..b9ddf824 100644 --- a/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs +++ b/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs @@ -32,29 +32,24 @@ namespace Ocelot.RequestId.Middleware private void SetOcelotRequestId(DownstreamContext context) { - // if get request ID is set on upstream request then retrieve it var key = context.DownstreamReRoute.RequestIdKey ?? DefaultRequestIdKey.Value; if (context.HttpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds)) { context.HttpContext.TraceIdentifier = upstreamRequestIds.First(); - //check if we have previous id in scoped repo var previousRequestId = _requestScopedDataRepository.Get("RequestId"); if (!previousRequestId.IsError && !string.IsNullOrEmpty(previousRequestId.Data) && previousRequestId.Data != context.HttpContext.TraceIdentifier) { - //we have a previous request id lets store it and update request id _requestScopedDataRepository.Add("PreviousRequestId", previousRequestId.Data); _requestScopedDataRepository.Update("RequestId", context.HttpContext.TraceIdentifier); } else { - //else just add request id _requestScopedDataRepository.Add("RequestId", context.HttpContext.TraceIdentifier); } } - // set request ID on downstream request, if required var requestId = new RequestId(context.DownstreamReRoute.RequestIdKey, context.HttpContext.TraceIdentifier); if (ShouldAddRequestId(requestId, context.DownstreamRequest.Headers)) From d7ef956935634ce54d1e14a6e6ece7bd1ca6beca Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sat, 7 Apr 2018 21:55:54 +0100 Subject: [PATCH 25/50] fixed weird error --- .../Middleware/OcelotPiplineBuilderTests.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs index a6862bec..21649a80 100644 --- a/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs +++ b/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs @@ -10,6 +10,7 @@ namespace Ocelot.UnitTests.Middleware using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Ocelot.DependencyInjection; + using Ocelot.Logging; using Ocelot.Middleware; using Ocelot.Middleware.Pipeline; using Shouldly; @@ -100,6 +101,7 @@ namespace Ocelot.UnitTests.Middleware private readonly OcelotRequestDelegate _next; public MultiParametersInvokeMiddleware(OcelotRequestDelegate next) + :base(new FakeLogger()) { _next = next; } @@ -110,4 +112,31 @@ namespace Ocelot.UnitTests.Middleware } } } + + class FakeLogger : IOcelotLogger + { + public void LogCritical(string message, Exception exception) + { + } + + public void LogDebug(string message) + { + } + + public void LogError(string message, Exception exception) + { + } + + public void LogInformation(string message) + { + } + + public void LogTrace(string message) + { + } + + public void LogWarning(string message) + { + } + } } From 982eebfc74217a5fef34321c97f91cd1afaa9bed Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 8 Apr 2018 15:54:58 +0100 Subject: [PATCH 26/50] Feature/#295 consul acl (#307) * removed file * updated package * updated package * updated package * updated package * updated package * updated package * updated package * all packages updated * #295 can add token to service provider config and this will be used by consul clients to get services and configuration * #295 wait longer for this test --- docs/features/servicediscovery.rst | 15 + .../ServiceProviderConfigurationBuilder.cs | 15 +- .../ServiceProviderConfigurationCreator.cs | 7 +- .../File/FileServiceDiscoveryProvider.cs | 1 + .../ConsulFileConfigurationRepository.cs | 24 +- .../ServiceProviderConfiguration.cs | 12 +- .../DependencyInjection/OcelotBuilder.cs | 8 +- .../Consul/ConsulClientFactory.cs | 22 + .../Consul/IConsulClientFactory.cs | 10 + src/Ocelot/Ocelot.csproj | 34 +- .../ConsulRegistryConfiguration.cs | 12 +- .../ConsulServiceDiscoveryProvider.cs | 18 +- .../ServiceDiscoveryProviderFactory.cs | 9 +- .../ButterflyTracingTests.cs | 2 +- test/Ocelot.AcceptanceTests/HeaderTests.cs | 18 +- .../Ocelot.AcceptanceTests.csproj | 28 +- .../ServiceDiscoveryTests.cs | 106 ++- test/Ocelot.AcceptanceTests/Steps.cs.orig | 722 ------------------ test/Ocelot.AcceptanceTests/WebSocketTests.cs | 11 +- .../Ocelot.Benchmarks.csproj | 2 +- .../Ocelot.IntegrationTests.csproj | 26 +- .../Ocelot.ManualTest.csproj | 22 +- .../ServiceProviderCreatorTests.cs | 25 +- .../DownstreamUrlCreatorMiddlewareTests.cs | 24 +- .../LoadBalancer/LoadBalancerHouseTests.cs | 2 +- test/Ocelot.UnitTests/Ocelot.UnitTests.csproj | 24 +- .../ConsulServiceDiscoveryProviderTests.cs | 47 +- .../ServiceProviderFactoryTests.cs | 7 +- 28 files changed, 345 insertions(+), 908 deletions(-) create mode 100644 src/Ocelot/Infrastructure/Consul/ConsulClientFactory.cs create mode 100644 src/Ocelot/Infrastructure/Consul/IConsulClientFactory.cs delete mode 100644 test/Ocelot.AcceptanceTests/Steps.cs.orig diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index f8cadd2e..3add60f1 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -38,3 +38,18 @@ and LeastConnection algorithm you can use. If no load balancer is specified Ocel } When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. + +ACL Token +--------- + +If you are using ACL with Consul Ocelot supports adding the X-Consul-Token header. In order so this to work you must add the additional property below. + +.. code-block:: json + + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 9500, + "Token": "footoken" + } + +Ocelot will add this token to the consul client that it uses to make requests and that is then used for every request. \ No newline at end of file diff --git a/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs b/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs index b1f4e832..cd8446a5 100644 --- a/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ServiceProviderConfigurationBuilder.cs @@ -5,28 +5,35 @@ namespace Ocelot.Configuration.Builder private string _serviceDiscoveryProviderHost; private int _serviceDiscoveryProviderPort; private string _type; + private string _token; - public ServiceProviderConfigurationBuilder WithServiceDiscoveryProviderHost(string serviceDiscoveryProviderHost) + public ServiceProviderConfigurationBuilder WithHost(string serviceDiscoveryProviderHost) { _serviceDiscoveryProviderHost = serviceDiscoveryProviderHost; return this; } - public ServiceProviderConfigurationBuilder WithServiceDiscoveryProviderPort(int serviceDiscoveryProviderPort) + public ServiceProviderConfigurationBuilder WithPort(int serviceDiscoveryProviderPort) { _serviceDiscoveryProviderPort = serviceDiscoveryProviderPort; return this; } - public ServiceProviderConfigurationBuilder WithServiceDiscoveryProviderType(string type) + public ServiceProviderConfigurationBuilder WithType(string type) { _type = type; return this; } + public ServiceProviderConfigurationBuilder WithToken(string token) + { + _token = token; + return this; + } + public ServiceProviderConfiguration Build() { - return new ServiceProviderConfiguration(_type, _serviceDiscoveryProviderHost, _serviceDiscoveryProviderPort); + return new ServiceProviderConfiguration(_type, _serviceDiscoveryProviderHost, _serviceDiscoveryProviderPort, _token); } } } diff --git a/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs index c104385a..aa735f1a 100644 --- a/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/ServiceProviderConfigurationCreator.cs @@ -11,9 +11,10 @@ namespace Ocelot.Configuration.Creator var serviceProviderPort = globalConfiguration?.ServiceDiscoveryProvider?.Port ?? 0; return new ServiceProviderConfigurationBuilder() - .WithServiceDiscoveryProviderHost(globalConfiguration?.ServiceDiscoveryProvider?.Host) - .WithServiceDiscoveryProviderPort(serviceProviderPort) - .WithServiceDiscoveryProviderType(globalConfiguration?.ServiceDiscoveryProvider?.Type) + .WithHost(globalConfiguration?.ServiceDiscoveryProvider?.Host) + .WithPort(serviceProviderPort) + .WithType(globalConfiguration?.ServiceDiscoveryProvider?.Type) + .WithToken(globalConfiguration?.ServiceDiscoveryProvider?.Token) .Build(); } } diff --git a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs index 203cc675..9a96a6d3 100644 --- a/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs +++ b/src/Ocelot/Configuration/File/FileServiceDiscoveryProvider.cs @@ -5,5 +5,6 @@ namespace Ocelot.Configuration.File public string Host {get;set;} public int Port { get; set; } public string Type { get; set; } + public string Token { get; set; } } } diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs index 10e99c10..7db04a09 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs @@ -4,8 +4,8 @@ using System.Threading.Tasks; using Consul; using Newtonsoft.Json; using Ocelot.Configuration.File; +using Ocelot.Infrastructure.Consul; using Ocelot.Responses; -using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery.Configuration; namespace Ocelot.Configuration.Repository @@ -13,31 +13,31 @@ namespace Ocelot.Configuration.Repository public class ConsulFileConfigurationRepository : IFileConfigurationRepository { private readonly ConsulClient _consul; - private string _ocelotConfiguration = "OcelotConfiguration"; + private const string OcelotConfiguration = "OcelotConfiguration"; private readonly Cache.IOcelotCache _cache; - public ConsulFileConfigurationRepository(Cache.IOcelotCache cache, ServiceProviderConfiguration serviceProviderConfig) + public ConsulFileConfigurationRepository( + Cache.IOcelotCache cache, + ServiceProviderConfiguration serviceProviderConfig, + IConsulClientFactory factory) { var consulHost = string.IsNullOrEmpty(serviceProviderConfig?.Host) ? "localhost" : serviceProviderConfig?.Host; var consulPort = serviceProviderConfig?.Port ?? 8500; - var configuration = new ConsulRegistryConfiguration(consulHost, consulPort, _ocelotConfiguration); + var config = new ConsulRegistryConfiguration(consulHost, consulPort, OcelotConfiguration, serviceProviderConfig?.Token); _cache = cache; - _consul = new ConsulClient(c => - { - c.Address = new Uri($"http://{configuration.HostName}:{configuration.Port}"); - }); + _consul = factory.Get(config); } public async Task> Get() { - var config = _cache.Get(_ocelotConfiguration, _ocelotConfiguration); + var config = _cache.Get(OcelotConfiguration, OcelotConfiguration); if (config != null) { return new OkResponse(config); } - var queryResult = await _consul.KV.Get(_ocelotConfiguration); + var queryResult = await _consul.KV.Get(OcelotConfiguration); if (queryResult.Response == null) { @@ -59,7 +59,7 @@ namespace Ocelot.Configuration.Repository var bytes = Encoding.UTF8.GetBytes(json); - var kvPair = new KVPair(_ocelotConfiguration) + var kvPair = new KVPair(OcelotConfiguration) { Value = bytes }; @@ -68,7 +68,7 @@ namespace Ocelot.Configuration.Repository if (result.Response) { - _cache.AddAndDelete(_ocelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3), _ocelotConfiguration); + _cache.AddAndDelete(OcelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3), OcelotConfiguration); return new OkResponse(); } diff --git a/src/Ocelot/Configuration/ServiceProviderConfiguration.cs b/src/Ocelot/Configuration/ServiceProviderConfiguration.cs index aa7c492c..129d24db 100644 --- a/src/Ocelot/Configuration/ServiceProviderConfiguration.cs +++ b/src/Ocelot/Configuration/ServiceProviderConfiguration.cs @@ -2,15 +2,17 @@ { public class ServiceProviderConfiguration { - public ServiceProviderConfiguration(string type, string host, int port) + public ServiceProviderConfiguration(string type, string host, int port, string token) { Host = host; Port = port; + Token = token; Type = type; } - public string Host { get; private set; } - public int Port { get; private set; } - public string Type { get; private set; } + public string Host { get; } + public int Port { get; } + public string Type { get; } + public string Token { get; } } -} \ No newline at end of file +} diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 8ccb1459..c6fa20f4 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -53,6 +53,7 @@ namespace Ocelot.DependencyInjection using System.Net.Http; using Butterfly.Client.AspNetCore; using Ocelot.Infrastructure; + using Ocelot.Infrastructure.Consul; public class OcelotBuilder : IOcelotBuilder { @@ -152,6 +153,7 @@ namespace Ocelot.DependencyInjection _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); + _services.TryAddSingleton(); } public IOcelotAdministrationBuilder AddAdministration(string path, string secret) @@ -236,10 +238,12 @@ namespace Ocelot.DependencyInjection { var serviceDiscoveryPort = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Port", 0); var serviceDiscoveryHost = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Host", string.Empty); + var serviceDiscoveryToken = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Token", string.Empty); var config = new ServiceProviderConfigurationBuilder() - .WithServiceDiscoveryProviderPort(serviceDiscoveryPort) - .WithServiceDiscoveryProviderHost(serviceDiscoveryHost) + .WithPort(serviceDiscoveryPort) + .WithHost(serviceDiscoveryHost) + .WithToken(serviceDiscoveryToken) .Build(); _services.AddSingleton(config); diff --git a/src/Ocelot/Infrastructure/Consul/ConsulClientFactory.cs b/src/Ocelot/Infrastructure/Consul/ConsulClientFactory.cs new file mode 100644 index 00000000..0e7f2782 --- /dev/null +++ b/src/Ocelot/Infrastructure/Consul/ConsulClientFactory.cs @@ -0,0 +1,22 @@ +using System; +using Consul; +using Ocelot.ServiceDiscovery.Configuration; + +namespace Ocelot.Infrastructure.Consul +{ + public class ConsulClientFactory : IConsulClientFactory + { + public ConsulClient Get(ConsulRegistryConfiguration config) + { + return new ConsulClient(c => + { + c.Address = new Uri($"http://{config.Host}:{config.Port}"); + + if (!string.IsNullOrEmpty(config?.Token)) + { + c.Token = config.Token; + } + }); + } + } +} diff --git a/src/Ocelot/Infrastructure/Consul/IConsulClientFactory.cs b/src/Ocelot/Infrastructure/Consul/IConsulClientFactory.cs new file mode 100644 index 00000000..27e413fc --- /dev/null +++ b/src/Ocelot/Infrastructure/Consul/IConsulClientFactory.cs @@ -0,0 +1,10 @@ +using Consul; +using Ocelot.ServiceDiscovery.Configuration; + +namespace Ocelot.Infrastructure.Consul +{ + public interface IConsulClientFactory + { + ConsulClient Get(ConsulRegistryConfiguration config); + } +} diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index 3d9b65c7..68f1dc9f 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -26,27 +26,27 @@ - - - - - - - - - - - + + + + + + + + + + + all - - - - - - + + + + + + diff --git a/src/Ocelot/ServiceDiscovery/Configuration/ConsulRegistryConfiguration.cs b/src/Ocelot/ServiceDiscovery/Configuration/ConsulRegistryConfiguration.cs index 9a9e5de8..13ae68d2 100644 --- a/src/Ocelot/ServiceDiscovery/Configuration/ConsulRegistryConfiguration.cs +++ b/src/Ocelot/ServiceDiscovery/Configuration/ConsulRegistryConfiguration.cs @@ -2,15 +2,17 @@ namespace Ocelot.ServiceDiscovery.Configuration { public class ConsulRegistryConfiguration { - public ConsulRegistryConfiguration(string hostName, int port, string keyOfServiceInConsul) + public ConsulRegistryConfiguration(string host, int port, string keyOfServiceInConsul, string token) { - HostName = hostName; + Host = host; Port = port; KeyOfServiceInConsul = keyOfServiceInConsul; + Token = token; } - public string KeyOfServiceInConsul { get; private set; } - public string HostName { get; private set; } - public int Port { get; private set; } + public string KeyOfServiceInConsul { get; } + public string Host { get; } + public int Port { get; } + public string Token { get; } } } diff --git a/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs index de31188e..12c34cb2 100644 --- a/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs +++ b/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Consul; +using Ocelot.Infrastructure.Consul; using Ocelot.Infrastructure.Extensions; using Ocelot.Logging; using Ocelot.ServiceDiscovery.Configuration; @@ -12,30 +13,27 @@ namespace Ocelot.ServiceDiscovery.Providers { public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider { - private readonly ConsulRegistryConfiguration _consulConfig; + private readonly ConsulRegistryConfiguration _config; private readonly IOcelotLogger _logger; private readonly ConsulClient _consul; private const string VersionPrefix = "version-"; - public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration consulRegistryConfiguration, IOcelotLoggerFactory factory) + public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration config, IOcelotLoggerFactory factory, IConsulClientFactory clientFactory) {; _logger = factory.CreateLogger(); - var consulHost = string.IsNullOrEmpty(consulRegistryConfiguration?.HostName) ? "localhost" : consulRegistryConfiguration.HostName; + var consulHost = string.IsNullOrEmpty(config?.Host) ? "localhost" : config.Host; - var consulPort = consulRegistryConfiguration?.Port ?? 8500; + var consulPort = config?.Port ?? 8500; - _consulConfig = new ConsulRegistryConfiguration(consulHost, consulPort, consulRegistryConfiguration?.KeyOfServiceInConsul); + _config = new ConsulRegistryConfiguration(consulHost, consulPort, config?.KeyOfServiceInConsul, config?.Token); - _consul = new ConsulClient(config => - { - config.Address = new Uri($"http://{_consulConfig.HostName}:{_consulConfig.Port}"); - }); + _consul = clientFactory.Get(_config); } public async Task> Get() { - var queryResult = await _consul.Health.Service(_consulConfig.KeyOfServiceInConsul, string.Empty, true); + var queryResult = await _consul.Health.Service(_config.KeyOfServiceInConsul, string.Empty, true); var services = new List(); diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index 95201dfb..d13c333a 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Ocelot.Configuration; +using Ocelot.Infrastructure.Consul; using Ocelot.Logging; using Ocelot.ServiceDiscovery.Configuration; using Ocelot.ServiceDiscovery.Providers; @@ -10,10 +11,12 @@ namespace Ocelot.ServiceDiscovery public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory { private readonly IOcelotLoggerFactory _factory; + private readonly IConsulClientFactory _clientFactory; - public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory) + public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IConsulClientFactory clientFactory) { _factory = factory; + _clientFactory = clientFactory; } public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute) @@ -43,8 +46,8 @@ namespace Ocelot.ServiceDiscovery return new ServiceFabricServiceDiscoveryProvider(config); } - var consulRegistryConfiguration = new ConsulRegistryConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName); - return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory); + var consulRegistryConfiguration = new ConsulRegistryConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName, serviceConfig.Token); + return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory, _clientFactory); } } } diff --git a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs index d634e617..10cb7929 100644 --- a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs +++ b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs @@ -104,7 +104,7 @@ namespace Ocelot.AcceptanceTests .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) .BDDfy(); - var commandOnAllStateMachines = WaitFor(5000).Until(() => _butterflyCalled == 4); + var commandOnAllStateMachines = WaitFor(10000).Until(() => _butterflyCalled == 4); commandOnAllStateMachines.ShouldBeTrue(); } diff --git a/test/Ocelot.AcceptanceTests/HeaderTests.cs b/test/Ocelot.AcceptanceTests/HeaderTests.cs index 2bd3c2ab..b964161a 100644 --- a/test/Ocelot.AcceptanceTests/HeaderTests.cs +++ b/test/Ocelot.AcceptanceTests/HeaderTests.cs @@ -16,7 +16,6 @@ namespace Ocelot.AcceptanceTests public class HeaderTests : IDisposable { private IWebHost _builder; - private string _cookieValue; private int _count; private readonly Steps _steps; @@ -278,26 +277,27 @@ namespace Ocelot.AcceptanceTests .Configure(app => { app.UsePathBase(basePath); - app.Run(async context => - { - if(_count == 0) + app.Run(context => + { + if (_count == 0) { context.Response.Cookies.Append("test", "0"); _count++; context.Response.StatusCode = statusCode; - return; - } + return Task.CompletedTask; + } - if(context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue)) + if (context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue)) { - if(cookieValue == "0" || headerValue == "test=1; path=/") + if (cookieValue == "0" || headerValue == "test=1; path=/") { context.Response.StatusCode = statusCode; - return; + return Task.CompletedTask; } } context.Response.StatusCode = 500; + return Task.CompletedTask; }); }) .Build(); diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 90d5bc64..4f67b970 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -27,26 +27,26 @@ - - - - + + + + all - - - - - - - - + + + + + + + + - + - + diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index 97e104c4..972b165f 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; using Consul; using Microsoft.AspNetCore.Builder; @@ -22,9 +23,10 @@ namespace Ocelot.AcceptanceTests private readonly List _serviceEntries; private int _counterOne; private int _counterTwo; - private static readonly object _syncLock = new object(); + private static readonly object SyncLock = new object(); private IWebHost _builder; private string _downstreamPath; + private string _receivedToken; public ServiceDiscoveryTests() { @@ -100,13 +102,13 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } - //test from issue 213 + //test from issue #213 [Fact] public void should_handle_request_to_consul_for_downstream_service_and_make_request() { - var consulPort = 8505; - var serviceName = "web"; - var downstreamServiceOneUrl = "http://localhost:8080"; + const int consulPort = 8505; + const string serviceName = "web"; + const string downstreamServiceOneUrl = "http://localhost:8080"; var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; var serviceEntryOne = new ServiceEntry() { @@ -116,7 +118,7 @@ namespace Ocelot.AcceptanceTests Address = "localhost", Port = 8080, ID = "web_90_0_2_224_8080", - Tags = new string[1]{"version-v1"} + Tags = new[] {"version-v1"} }, }; @@ -145,14 +147,73 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura")) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/home")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura")) + .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) + .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/home")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + + //test from issue #295 + [Fact] + public void should_use_token_to_make_request_to_consul() + { + var token = "abctoken"; + var consulPort = 8515; + var serviceName = "web"; + var downstreamServiceOneUrl = "http://localhost:8081"; + var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; + var serviceEntryOne = new ServiceEntry() + { + Service = new AgentService() + { + Service = serviceName, + Address = "localhost", + Port = 8081, + ID = "web_90_0_2_224_8080", + Tags = new[] { "version-v1" } + }, + }; + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/home", + DownstreamScheme = "http", + UpstreamPathTemplate = "/home", + UpstreamHttpMethod = new List { "Get", "Options" }, + ServiceName = serviceName, + LoadBalancer = "LeastConnection", + UseServiceDiscovery = true, + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = consulPort, + Token = token + } + } + }; + + this.Given(_ => GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura")) + .And(_ => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) + .And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) + .And(_ => _steps.GivenThereIsAConfiguration(configuration)) + .And(_ => _steps.GivenOcelotIsRunning()) + .When(_ => _steps.WhenIGetUrlOnTheApiGateway("/home")) + .Then(_ => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(_ => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(_ => _receivedToken.ShouldBe(token)) .BDDfy(); } @@ -289,6 +350,11 @@ namespace Ocelot.AcceptanceTests { if(context.Request.Path.Value == $"/v1/health/service/{serviceName}") { + if (context.Request.Headers.TryGetValue("X-Consul-Token", out var values)) + { + _receivedToken = values.First(); + } + await context.Response.WriteJsonAsync(_serviceEntries); } }); @@ -312,8 +378,8 @@ namespace Ocelot.AcceptanceTests { try { - var response = string.Empty; - lock (_syncLock) + string response; + lock (SyncLock) { _counterOne++; response = _counterOne.ToString(); @@ -321,7 +387,7 @@ namespace Ocelot.AcceptanceTests context.Response.StatusCode = statusCode; await context.Response.WriteAsync(response); } - catch (System.Exception exception) + catch (Exception exception) { await context.Response.WriteAsync(exception.StackTrace); } @@ -346,8 +412,8 @@ namespace Ocelot.AcceptanceTests { try { - var response = string.Empty; - lock (_syncLock) + string response; + lock (SyncLock) { _counterTwo++; response = _counterTwo.ToString(); @@ -356,7 +422,7 @@ namespace Ocelot.AcceptanceTests context.Response.StatusCode = statusCode; await context.Response.WriteAsync(response); } - catch (System.Exception exception) + catch (Exception exception) { await context.Response.WriteAsync(exception.StackTrace); } diff --git a/test/Ocelot.AcceptanceTests/Steps.cs.orig b/test/Ocelot.AcceptanceTests/Steps.cs.orig deleted file mode 100644 index 84c119ab..00000000 --- a/test/Ocelot.AcceptanceTests/Steps.cs.orig +++ /dev/null @@ -1,722 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; -using CacheManager.Core; -using IdentityServer4.AccessTokenValidation; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using Shouldly; -using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; -using Ocelot.AcceptanceTests.Caching; -<<<<<<< HEAD -using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; -||||||| merged common ancestors -======= -using System.IO.Compression; -using System.Text; ->>>>>>> develop - -namespace Ocelot.AcceptanceTests -{ - public class Steps : IDisposable - { - private TestServer _ocelotServer; - private HttpClient _ocelotClient; - private HttpResponseMessage _response; - private HttpContent _postContent; - private BearerToken _token; - public HttpClient OcelotClient => _ocelotClient; - public string RequestIdKey = "OcRequestId"; - private readonly Random _random; - private IWebHostBuilder _webHostBuilder; - - public Steps() - { - _random = new Random(); - } - - public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = TestConfiguration.ConfigurationPath; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - } - - public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration, string configurationPath) - { - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - } - - /// - /// 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. - /// - public void GivenOcelotIsRunning() - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddOcelot(); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - internal void GivenOcelotIsRunningUsingButterfly(string butterflyUrl) - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddOcelot() - .AddOpenTracing(option => - { - //this is the url that the butterfly collector server is running on... - option.CollectorUrl = butterflyUrl; - option.Service = "Ocelot"; - }); - }) - .Configure(app => - { - app.Use(async (context, next) => - { - await next.Invoke(); - }); - app.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - -/* - public void GivenIHaveAddedXForwardedForHeader(string value) - { - _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation("X-Forwarded-For", value); - }*/ - - public void GivenOcelotIsRunningWithMiddleareBeforePipeline(Func callback) - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddOcelot(); - }) - .Configure(app => - { - app.UseMiddleware(callback); - app.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - public void GivenOcelotIsRunningWithHandlersRegisteredInDi() - where TOne : DelegatingHandler - where TWo : DelegatingHandler - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddSingleton(_webHostBuilder); - s.AddOcelot() - .AddDelegatingHandler() - .AddDelegatingHandler(); - }) - .Configure(a => - { - a.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - public void GivenOcelotIsRunningWithHandlersRegisteredInDi(FakeDependency dependency) - where TOne : DelegatingHandler - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddSingleton(_webHostBuilder); - s.AddSingleton(dependency); - s.AddOcelot() - .AddDelegatingHandler(); - }) - .Configure(a => - { - a.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - /// - /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. - /// - public void GivenOcelotIsRunning(Action options, string authenticationProviderKey) - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddOcelot(); - s.AddAuthentication() - .AddIdentityServerAuthentication(authenticationProviderKey, options); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - public void ThenTheResponseHeaderIs(string key, string value) - { - var header = _response.Headers.GetValues(key); - header.First().ShouldBe(value); - } - - public void GivenOcelotIsRunningUsingJsonSerializedCache() - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddOcelot() - .AddCacheManager((x) => - { - x.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithJsonSerializer() - .WithHandle(typeof(InMemoryJsonHandle<>)); - }); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - public void GivenOcelotIsRunningUsingConsulToStoreConfig() - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddOcelot().AddStoreOcelotConfigurationInConsul(); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - public void GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache() - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddOcelot() - .AddCacheManager((x) => - { - x.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithJsonSerializer() - .WithHandle(typeof(InMemoryJsonHandle<>)); - }) - .AddStoreOcelotConfigurationInConsul(); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - internal void ThenTheResponseShouldBe(FileConfiguration expecteds) - { - var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); - - response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); - response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); - response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for (var i = 0; i < response.ReRoutes.Count; i++) - { - for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++) - { - var result = response.ReRoutes[i].DownstreamHostAndPorts[j]; - var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; - result.Host.ShouldBe(expected.Host); - result.Port.ShouldBe(expected.Port); - } - - response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); - response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); - response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].UpstreamPathTemplate); - response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.ReRoutes[i].UpstreamHttpMethod); - } - } - - /// - /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. - /// - public void GivenOcelotIsRunning(OcelotPipelineConfiguration ocelotPipelineConfig) - { - var builder = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile("configuration.json") - .AddEnvironmentVariables(); - - var configuration = builder.Build(); - _webHostBuilder = new WebHostBuilder(); - _webHostBuilder.ConfigureServices(s => - { - s.AddSingleton(_webHostBuilder); - }); - - _ocelotServer = new TestServer(_webHostBuilder - .UseConfiguration(configuration) - .ConfigureServices(s => - { - Action settings = (x) => - { - x.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithDictionaryHandle(); - }; - - s.AddOcelot(configuration); - }) - .ConfigureLogging(l => - { - l.AddConsole(); - l.AddDebug(); - }) - .Configure(a => - { - a.UseOcelot(ocelotPipelineConfig).Wait(); - })); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - public void GivenIHaveAddedATokenToMyRequest() - { - _ocelotClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - } - - public void GivenIHaveAToken(string url) - { - var tokenUrl = $"{url}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "client"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "api"), - new KeyValuePair("username", "test"), - new KeyValuePair("password", "test"), - new KeyValuePair("grant_type", "password") - }; - var content = new FormUrlEncodedContent(formData); - - using (var httpClient = new HttpClient()) - { - var response = httpClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - } - } - - public void GivenIHaveATokenForApiReadOnlyScope(string url) - { - var tokenUrl = $"{url}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "client"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "api.readOnly"), - new KeyValuePair("username", "test"), - new KeyValuePair("password", "test"), - new KeyValuePair("grant_type", "password") - }; - var content = new FormUrlEncodedContent(formData); - - using (var httpClient = new HttpClient()) - { - var response = httpClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - } - } - - public void GivenIHaveATokenForApi2(string url) - { - var tokenUrl = $"{url}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "client"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "api2"), - new KeyValuePair("username", "test"), - new KeyValuePair("password", "test"), - new KeyValuePair("grant_type", "password") - }; - var content = new FormUrlEncodedContent(formData); - - using (var httpClient = new HttpClient()) - { - var response = httpClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - } - } - - 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()) - { - var response = httpClient.GetAsync($"{url}/.well-known/openid-configuration").Result; - response.EnsureSuccessStatusCode(); - } - } - - public void WhenIGetUrlOnTheApiGateway(string url) - { - _response = _ocelotClient.GetAsync(url).Result; - } - - public void GivenIAddAHeader(string key, string value) - { - _ocelotClient.DefaultRequestHeaders.Add(key, value); - } - - public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times) - { - 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 async Task GetForServiceDiscoveryTest(string url) - { - var response = await _ocelotClient.GetAsync(url); - var content = await response.Content.ReadAsStringAsync(); - int count = int.Parse(content); - 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); - - _response = _ocelotClient.GetAsync(url).Result; - } - - public void WhenIPostUrlOnTheApiGateway(string url) - { - _response = _ocelotClient.PostAsync(url, _postContent).Result; - } - - public void GivenThePostHasContent(string postcontent) - { - _postContent = new StringContent(postcontent); - } - - public void GivenThePostHasGzipContent(object input) - { - string json = JsonConvert.SerializeObject(input); - byte[] jsonBytes = Encoding.UTF8.GetBytes(json); - MemoryStream ms = new MemoryStream(); - using (GZipStream gzip = new GZipStream(ms, CompressionMode.Compress, true)) - { - gzip.Write(jsonBytes, 0, jsonBytes.Length); - } - ms.Position = 0; - StreamContent content = new StreamContent(ms); - content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - content.Headers.ContentEncoding.Add("gzip"); - _postContent = content; - } - - public void ThenTheResponseBodyShouldBe(string expectedBody) - { - _response.Content.ReadAsStringAsync().Result.ShouldBe(expectedBody); - } - - public void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) - { - _response.StatusCode.ShouldBe(expectedHttpStatusCode); - } - - public void ThenTheStatusCodeShouldBe(int expectedHttpStatusCode) - { - var responseStatusCode = (int)_response.StatusCode; - responseStatusCode.ShouldBe(expectedHttpStatusCode); - } - - public void Dispose() - { - _ocelotClient?.Dispose(); - _ocelotServer?.Dispose(); - } - - public void ThenTheRequestIdIsReturned() - { - _response.Headers.GetValues(RequestIdKey).First().ShouldNotBeNullOrEmpty(); - } - - public void ThenTheRequestIdIsReturned(string expected) - { - _response.Headers.GetValues(RequestIdKey).First().ShouldBe(expected); - } - - public void ThenTheContentLengthIs(int expected) - { - _response.Content.Headers.ContentLength.ShouldBe(expected); - } - - public void WhenIMakeLotsOfDifferentRequestsToTheApiGateway() - { - int numberOfRequests = 100; - var aggregateUrl = "/"; - var aggregateExpected = "{\"Laura\":{Hello from Laura},\"Tom\":{Hello from Tom}}"; - var tomUrl = "/tom"; - var tomExpected = "{Hello from Tom}"; - var lauraUrl = "/laura"; - var lauraExpected = "{Hello from Laura}"; - var random = new Random(); - - var aggregateTasks = new Task[numberOfRequests]; - - for (int i = 0; i < numberOfRequests; i++) - { - aggregateTasks[i] = Fire(aggregateUrl, aggregateExpected, random); - } - - var tomTasks = new Task[numberOfRequests]; - - for (int i = 0; i < numberOfRequests; i++) - { - tomTasks[i] = Fire(tomUrl, tomExpected, random); - } - - var lauraTasks = new Task[numberOfRequests]; - - for (int i = 0; i < numberOfRequests; i++) - { - lauraTasks[i] = Fire(lauraUrl, lauraExpected, random); - } - - Task.WaitAll(lauraTasks); - Task.WaitAll(tomTasks); - Task.WaitAll(aggregateTasks); - } - - private async Task Fire(string url, string expectedBody, Random random) - { - var request = new HttpRequestMessage(new HttpMethod("GET"), url); - await Task.Delay(random.Next(0, 2)); - var response = await _ocelotClient.SendAsync(request); - var content = await response.Content.ReadAsStringAsync(); - content.ShouldBe(expectedBody); - } - } -} diff --git a/test/Ocelot.AcceptanceTests/WebSocketTests.cs b/test/Ocelot.AcceptanceTests/WebSocketTests.cs index 8137649a..dce2f306 100644 --- a/test/Ocelot.AcceptanceTests/WebSocketTests.cs +++ b/test/Ocelot.AcceptanceTests/WebSocketTests.cs @@ -37,7 +37,7 @@ namespace Ocelot.AcceptanceTests } [Fact] - public async Task should_proxy_websocket_input_to_downstream_service() + public void should_proxy_websocket_input_to_downstream_service() { var downstreamPort = 5001; var downstreamHost = "localhost"; @@ -72,7 +72,7 @@ namespace Ocelot.AcceptanceTests } [Fact] - public async Task should_proxy_websocket_input_to_downstream_service_and_use_load_balancer() + public void should_proxy_websocket_input_to_downstream_service_and_use_load_balancer() { var downstreamPort = 5005; var downstreamHost = "localhost"; @@ -116,7 +116,7 @@ namespace Ocelot.AcceptanceTests } [Fact] - public async Task should_proxy_websocket_input_to_downstream_service_and_use_service_discovery_and_load_balancer() + public void should_proxy_websocket_input_to_downstream_service_and_use_service_discovery_and_load_balancer() { var downstreamPort = 5007; var downstreamHost = "localhost"; @@ -274,7 +274,6 @@ namespace Ocelot.AcceptanceTests { _firstRecieved.Add(Encoding.UTF8.GetString(buffer, 0, result.Count)); } - else if (result.MessageType == WebSocketMessageType.Close) { await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); @@ -321,7 +320,6 @@ namespace Ocelot.AcceptanceTests { _secondRecieved.Add(Encoding.UTF8.GetString(buffer, 0, result.Count)); } - else if (result.MessageType == WebSocketMessageType.Close) { await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); @@ -333,7 +331,6 @@ namespace Ocelot.AcceptanceTests await Task.WhenAll(sending, receiving); } - private async Task StartFakeDownstreamService(string url, string path) { _firstDownstreamHost = new WebHostBuilder() @@ -380,7 +377,6 @@ namespace Ocelot.AcceptanceTests await _firstDownstreamHost.StartAsync(); } - private async Task StartSecondFakeDownstreamService(string url, string path) { _secondDownstreamHost = new WebHostBuilder() @@ -427,7 +423,6 @@ namespace Ocelot.AcceptanceTests await _secondDownstreamHost.StartAsync(); } - private async Task Echo(WebSocket webSocket) { try diff --git a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj index 8c63914c..7761dab1 100644 --- a/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj +++ b/test/Ocelot.Benchmarks/Ocelot.Benchmarks.csproj @@ -19,7 +19,7 @@ - + all diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj index 8dc250b2..644d217b 100644 --- a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj @@ -25,26 +25,26 @@ - - + + all - - - - - - - + + + + + + + - - + + - + - + \ No newline at end of file diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj index 1e708c68..d2e3e7bc 100644 --- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj +++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj @@ -21,19 +21,19 @@ - + - - - - - - - - - - + + + + + + + + + + all diff --git a/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs index 8b0cc078..214d6d54 100644 --- a/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ServiceProviderCreatorTests.cs @@ -10,8 +10,7 @@ namespace Ocelot.UnitTests.Configuration { public class ServiceProviderCreatorTests { - private ServiceProviderConfigurationCreator _creator; - private FileReRoute _reRoute; + private readonly ServiceProviderConfigurationCreator _creator; private FileGlobalConfiguration _globalConfig; private ServiceProviderConfiguration _result; @@ -23,36 +22,30 @@ namespace Ocelot.UnitTests.Configuration [Fact] public void should_create_service_provider_config() { - var reRoute = new FileReRoute(); - var globalConfig = new FileGlobalConfiguration { ServiceDiscoveryProvider = new FileServiceDiscoveryProvider { Host = "127.0.0.1", Port = 1234, - Type = "ServiceFabric" + Type = "ServiceFabric", + Token = "testtoken" } }; var expected = new ServiceProviderConfigurationBuilder() - .WithServiceDiscoveryProviderHost("127.0.0.1") - .WithServiceDiscoveryProviderPort(1234) - .WithServiceDiscoveryProviderType("ServiceFabric") + .WithHost("127.0.0.1") + .WithPort(1234) + .WithType("ServiceFabric") + .WithToken("testtoken") .Build(); - this.Given(x => x.GivenTheFollowingReRoute(reRoute)) - .And(x => x.GivenTheFollowingGlobalConfig(globalConfig)) + this.Given(x => x.GivenTheFollowingGlobalConfig(globalConfig)) .When(x => x.WhenICreate()) .Then(x => x.ThenTheConfigIs(expected)) .BDDfy(); } - private void GivenTheFollowingReRoute(FileReRoute fileReRoute) - { - _reRoute = fileReRoute; - } - private void GivenTheFollowingGlobalConfig(FileGlobalConfiguration fileGlobalConfig) { _globalConfig = fileGlobalConfig; @@ -67,6 +60,8 @@ namespace Ocelot.UnitTests.Configuration { _result.Host.ShouldBe(expected.Host); _result.Port.ShouldBe(expected.Port); + _result.Token.ShouldBe(expected.Token); + _result.Type.ShouldBe(expected.Type); } } } diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index 57177126..636174eb 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -82,9 +82,9 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator .Build(); var config = new ServiceProviderConfigurationBuilder() - .WithServiceDiscoveryProviderType("ServiceFabric") - .WithServiceDiscoveryProviderHost("localhost") - .WithServiceDiscoveryProviderPort(19081) + .WithType("ServiceFabric") + .WithHost("localhost") + .WithPort(19081) .Build(); this.Given(x => x.GivenTheDownStreamRouteIs( @@ -118,9 +118,9 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator .Build()); var config = new ServiceProviderConfigurationBuilder() - .WithServiceDiscoveryProviderType("ServiceFabric") - .WithServiceDiscoveryProviderHost("localhost") - .WithServiceDiscoveryProviderPort(19081) + .WithType("ServiceFabric") + .WithHost("localhost") + .WithPort(19081) .Build(); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) @@ -148,9 +148,9 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator .Build()); var config = new ServiceProviderConfigurationBuilder() - .WithServiceDiscoveryProviderType("ServiceFabric") - .WithServiceDiscoveryProviderHost("localhost") - .WithServiceDiscoveryProviderPort(19081) + .WithType("ServiceFabric") + .WithHost("localhost") + .WithPort(19081) .Build(); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) @@ -178,9 +178,9 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator .Build()); var config = new ServiceProviderConfigurationBuilder() - .WithServiceDiscoveryProviderType("ServiceFabric") - .WithServiceDiscoveryProviderHost("localhost") - .WithServiceDiscoveryProviderPort(19081) + .WithType("ServiceFabric") + .WithHost("localhost") + .WithPort(19081) .Build(); this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs index 1e30a0d1..cdd63d5a 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -25,7 +25,7 @@ namespace Ocelot.UnitTests.LoadBalancer { _factory = new Mock(); _loadBalancerHouse = new LoadBalancerHouse(_factory.Object); - _serviceProviderConfig = new ServiceProviderConfiguration("myType","myHost",123); + _serviceProviderConfig = new ServiceProviderConfiguration("myType","myHost",123, string.Empty); } [Fact] diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index 89af54d8..8df6245a 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -35,23 +35,23 @@ - - - + + + all - - - - - - - - - + + + + + + + + + diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs index 299da57f..b60330d8 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs @@ -1,14 +1,14 @@ using System; using System.Collections.Generic; using System.IO; -using System.Text; +using System.Linq; using Consul; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Moq; +using Ocelot.Infrastructure.Consul; using Ocelot.Logging; -using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery.Configuration; using Ocelot.ServiceDiscovery.Providers; using Ocelot.Values; @@ -22,14 +22,16 @@ namespace Ocelot.UnitTests.ServiceDiscovery { private IWebHost _fakeConsulBuilder; private readonly List _serviceEntries; - private readonly ConsulServiceDiscoveryProvider _provider; + private ConsulServiceDiscoveryProvider _provider; private readonly string _serviceName; private readonly int _port; private readonly string _consulHost; private readonly string _fakeConsulServiceDiscoveryUrl; private List _services; - private Mock _factory; + private readonly Mock _factory; private readonly Mock _logger; + private string _receivedToken; + private IConsulClientFactory _clientFactory; public ConsulServiceDiscoveryProviderTests() { @@ -40,11 +42,12 @@ namespace Ocelot.UnitTests.ServiceDiscovery _serviceEntries = new List(); _factory = new Mock(); + _clientFactory = new ConsulClientFactory(); _logger = new Mock(); _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - var config = new ConsulRegistryConfiguration(_consulHost, _port, _serviceName); - _provider = new ConsulServiceDiscoveryProvider(config, _factory.Object); + var config = new ConsulRegistryConfiguration(_consulHost, _port, _serviceName, null); + _provider = new ConsulServiceDiscoveryProvider(config, _factory.Object, _clientFactory); } [Fact] @@ -69,6 +72,33 @@ namespace Ocelot.UnitTests.ServiceDiscovery .BDDfy(); } + [Fact] + public void should_use_token() + { + var token = "test token"; + var config = new ConsulRegistryConfiguration(_consulHost, _port, _serviceName, token); + _provider = new ConsulServiceDiscoveryProvider(config, _factory.Object, _clientFactory); + + var serviceEntryOne = new ServiceEntry() + { + Service = new AgentService() + { + Service = _serviceName, + Address = "localhost", + Port = 50881, + ID = Guid.NewGuid().ToString(), + Tags = new string[0] + }, + }; + + this.Given(_ => GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName)) + .And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) + .When(_ => WhenIGetTheServices()) + .Then(_ => ThenTheCountIs(1)) + .And(_ => _receivedToken.ShouldBe(token)) + .BDDfy(); + } + [Fact] public void should_not_return_services_with_invalid_address() { @@ -197,6 +227,11 @@ namespace Ocelot.UnitTests.ServiceDiscovery { if (context.Request.Path.Value == $"/v1/health/service/{serviceName}") { + if (context.Request.Headers.TryGetValue("X-Consul-Token", out var values)) + { + _receivedToken = values.First(); + } + await context.Response.WriteJsonAsync(_serviceEntries); } }); diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index 2eef9b78..3e998431 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; +using Ocelot.Infrastructure.Consul; using Ocelot.Logging; using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery.Providers; @@ -19,11 +20,13 @@ namespace Ocelot.UnitTests.ServiceDiscovery private readonly ServiceDiscoveryProviderFactory _factory; private DownstreamReRoute _reRoute; private Mock _loggerFactory; + private IConsulClientFactory _clientFactory; public ServiceProviderFactoryTests() { _loggerFactory = new Mock(); - _factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object); + _clientFactory = new ConsulClientFactory(); + _factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, _clientFactory); } [Fact] @@ -87,7 +90,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery .Build(); var serviceConfig = new ServiceProviderConfigurationBuilder() - .WithServiceDiscoveryProviderType("ServiceFabric") + .WithType("ServiceFabric") .Build(); this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) From a15f75dda80003481661f67b2610fc42f96b1c90 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Thu, 12 Apr 2018 17:35:04 +0100 Subject: [PATCH 27/50] #298 initial hacking around better aggregation (#310) * #298 initial hacking around better aggregation * #298 bit more hacking around * #298 abstraction over httpresponsemessage * #298 tidying up * #298 docs * #298 missed this --- docs/features/requestaggregation.rst | 126 +++++++++++++-- .../Cache/Middleware/OutputCacheMiddleware.cs | 25 +-- .../Configuration/Builder/ReRouteBuilder.cs | 10 +- .../Creator/FileOcelotConfigurationCreator.cs | 1 + .../File/FileAggregateReRoute.cs | 1 + src/Ocelot/Configuration/ReRoute.cs | 5 +- .../DependencyInjection/IOcelotBuilder.cs | 6 + .../DependencyInjection/OcelotBuilder.cs | 16 ++ src/Ocelot/Errors/OcelotErrorCode.cs | 3 +- src/Ocelot/Headers/AddHeadersToResponse.cs | 8 +- .../Headers/HttpResponseHeaderReplacer.cs | 27 ++-- src/Ocelot/Headers/IAddHeadersToResponse.cs | 6 +- .../Headers/IHttpResponseHeaderReplacer.cs | 7 +- src/Ocelot/Headers/IRemoveOutputHeaders.cs | 6 +- .../HttpHeadersTransformationMiddleware.cs | 3 - src/Ocelot/Headers/RemoveOutputHeaders.cs | 16 +- src/Ocelot/Middleware/DownstreamContext.cs | 5 +- src/Ocelot/Middleware/DownstreamResponse.cs | 31 ++++ src/Ocelot/Middleware/Header.cs | 16 ++ .../CouldNotFindAggregatorError.cs | 12 ++ .../Multiplexer/IDefinedAggregator.cs | 10 ++ .../Multiplexer/IDefinedAggregatorProvider.cs | 10 ++ .../Multiplexer/IResponseAggregator.cs | 2 +- .../Multiplexer/IResponseAggregatorFactory.cs | 9 ++ .../InMemoryResponseAggregatorFactory.cs | 26 +++ .../Middleware/Multiplexer/Multiplexer.cs | 38 ++++- ...ServiceLocatorDefinedAggregatorProvider.cs | 29 ++++ .../SimpleJsonResponseAggregator.cs | 36 +---- .../UserDefinedResponseAggregator.cs | 34 ++++ .../Middleware/OcelotMiddlewareExtensions.cs | 29 +--- src/Ocelot/Request/Mapper/RequestMapper.cs | 3 - .../Middleware/HttpRequesterMiddleware.cs | 6 +- src/Ocelot/Responder/HttpContextResponder.cs | 15 +- src/Ocelot/Responder/IHttpResponder.cs | 7 +- .../Middleware/ResponderMiddleware.cs | 2 - test/Ocelot.AcceptanceTests/AggregateTests.cs | 106 +++++++++++- .../ButterflyTracingTests.cs | 14 +- test/Ocelot.AcceptanceTests/Steps.cs | 40 ++++- .../OutputCacheMiddlewareRealCacheTests.cs | 28 ++-- .../Cache/OutputCacheMiddlewareTests.cs | 37 +++-- .../FileConfigurationCreatorTests.cs | 3 +- .../HeaderFindAndReplaceCreatorTests.cs | 1 - .../DependencyInjection/OcelotBuilderTests.cs | 62 +++++++ .../DownstreamUrlCreatorMiddlewareTests.cs | 16 +- .../Headers/AddHeadersToResponseTests.cs | 24 +-- ...ttpHeadersTransformationMiddlewareTests.cs | 44 ++--- .../HttpResponseHeaderReplacerTests.cs | 153 +++++++++++------- .../Headers/RemoveHeadersTests.cs | 14 +- .../Infrastructure/HttpDataRepositoryTests.cs | 4 +- .../Infrastructure/PlaceholdersTests.cs | 3 +- .../Logging/AspDotNetLoggerTests.cs | 1 - .../DefinedAggregatorProviderTests.cs | 86 ++++++++++ .../Middleware/MultiplexerTests.cs | 5 +- .../Middleware/OcelotMiddlewareTests.cs | 3 +- .../ResponseAggregatorFactoryTests.cs | 64 ++++++++ .../SimpleJsonResponseAggregatorTests.cs | 50 +----- .../UserDefinedResponseAggregatorTests.cs | 153 ++++++++++++++++++ .../QueryStrings/AddQueriesToRequestTests.cs | 2 - .../Requester/HttpClientBuilderTests.cs | 10 +- .../Requester/HttpRequesterMiddlewareTests.cs | 54 +++++-- .../ErrorsToHttpStatusCodeMapperTests.cs | 2 +- .../Responder/HttpContextResponderTests.cs | 33 ++-- .../Responder/ResponderMiddlewareTests.cs | 15 +- 63 files changed, 1203 insertions(+), 410 deletions(-) create mode 100644 src/Ocelot/Middleware/DownstreamResponse.cs create mode 100644 src/Ocelot/Middleware/Header.cs create mode 100644 src/Ocelot/Middleware/Multiplexer/CouldNotFindAggregatorError.cs create mode 100644 src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs create mode 100644 src/Ocelot/Middleware/Multiplexer/IDefinedAggregatorProvider.cs create mode 100644 src/Ocelot/Middleware/Multiplexer/IResponseAggregatorFactory.cs create mode 100644 src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs create mode 100644 src/Ocelot/Middleware/Multiplexer/ServiceLocatorDefinedAggregatorProvider.cs create mode 100644 src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs create mode 100644 test/Ocelot.UnitTests/Middleware/DefinedAggregatorProviderTests.cs create mode 100644 test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs create mode 100644 test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs diff --git a/docs/features/requestaggregation.rst b/docs/features/requestaggregation.rst index b359eb72..c3c7ad13 100644 --- a/docs/features/requestaggregation.rst +++ b/docs/features/requestaggregation.rst @@ -5,12 +5,114 @@ Ocelot allow's you to specify Aggregate ReRoutes that compose multiple normal Re a client that is making multiple requests to a server where it could just be one. This feature allows you to start implementing back end for a front end type architecture with Ocelot. -This feature was requested as part of `Issue 79 `_ . +This feature was requested as part of `Issue 79 `_ and further improvements were made as part of `Issue 298 `_. In order to set this up you must do something like the following in your configuration.json. Here we have specified two normal ReRoutes and each one has a Key property. We then specify an Aggregate that composes the two ReRoutes using their keys in the ReRouteKeys list and says then we have the UpstreamPathTemplate which works like a normal ReRoute. Obviously you cannot have duplicate UpstreamPathTemplates between ReRoutes and Aggregates. You can use all of Ocelot's normal ReRoute options apart from RequestIdKey (explained in gotchas below). +Advanced register your own Aggregators +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Ocelot started with just the basic request aggregation and since then we have added a more advanced method that let's the user take in the responses from the +downstream services and then aggregate them into a response object. + +The configuration.json setup is pretty much the same as the basic aggregation approach apart from you need to add an Aggregator property like below. + +.. code-block:: json + + { + "ReRoutes": [ + { + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/laura", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51881 + } + ], + "Key": "Laura" + }, + { + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/tom", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51882 + } + ], + "Key": "Tom" + } + ], + "Aggregates": [ + { + "ReRouteKeys": [ + "Tom", + "Laura" + ], + "UpstreamPathTemplate": "/", + "Aggregator": "FakeDefinedAggregator" + } + ] + } + +Here we have added an aggregator called FakeDefinedAggregator. Ocelot is going to look for this aggregator when it tries to aggregate this ReRoute. + +In order to make the aggregator available we must add the FakeDefinedAggregator to the OcelotBuilder like below. + +.. code-block:: csharp + + services + .AddOcelot() + .AddSingletonDefinedAggregator(); + +Now when Ocelot tries to aggregate the ReRoute above it will find the FakeDefinedAggregator in the container and use it to aggregate the ReRoute. +Because the FakeDefinedAggregator is registered in the container you can add any dependencies it needs into the container like below. + +.. code-block:: csharp + + services.AddSingleton(); + + services + .AddOcelot() + .AddSingletonDefinedAggregator(); + +In this example FooAggregator takes a dependency on FooDependency and it will be resolved by the container. + +In addition to this Ocelot lets you add transient aggregators like below. + +.. code-block:: csharp + + services + .AddOcelot() + .AddTransientDefinedAggregator(); + +In order to make an Aggregator you must implement this interface. + +.. code-block:: csharp + + public interface IDefinedAggregator + { + Task Aggregate(List responses); + } + +With this feature you can pretty much do whatever you want because DownstreamResponse contains Content, Headers and Status Code. We can add extra things if needed +just raise an issue on GitHub. Please note if the HttpClient throws an exception when making a request to a ReRoute in the aggregate then you will not get a DownstreamResponse for +it but you would for any that succeed. If it does throw an exception this will be logged. + +Basic expecting JSON from Downstream Services +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + .. code-block:: json { @@ -65,9 +167,6 @@ If the ReRoute /tom returned a body of {"Age": 19} and /laura returned {"Age": 2 {"Tom":{"Age": 19},"Laura":{"Age": 25}} -Gotcha's / Further info -^^^^^^^^^^^^^^^^^^^^^^^ - At the moment the aggregation is very simple. Ocelot just gets the response from your downstream service and sticks it into a json dictionary as above. With the ReRoute key being the key of the dictionary and the value the response body from your downstream service. You can see that the object is just JSON without any pretty spaces etc. @@ -76,20 +175,13 @@ All headers will be lost from the downstream services response. Ocelot will always return content type application/json with an aggregate request. +If you downstream services return a 404 the aggregate will just return nothing for that downstream service. +It will not change the aggregate response into a 404 even if all the downstreams return a 404. + +Gotcha's / Further info +----------------------- + You cannot use ReRoutes with specific RequestIdKeys as this would be crazy complicated to track. Aggregation only supports the GET HTTP Verb. -If you downstream services return a 404 the aggregate will just return nothing for that downstream service. -It will not change the aggregate response into a 404 even if all the downstreams return a 404. - -Future -^^^^^^ - -There are loads of cool ways to enchance this such as.. - -What happens when downstream goes slow..should we timeout? -Can we do something like GraphQL where the user chooses what fields are returned? -Can we handle 404 better etc? -Can we make this not just support a JSON dictionary response? - diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 93548f3b..aee3bec3 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -2,12 +2,10 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; using System.IO; -using Ocelot.DownstreamRouteFinder.Middleware; +using Ocelot.Middleware.Multiplexer; namespace Ocelot.Cache.Middleware { @@ -72,38 +70,31 @@ namespace Ocelot.Cache.Middleware Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}"); } - private void SetHttpResponseMessageThisRequest(DownstreamContext context, HttpResponseMessage response) + private void SetHttpResponseMessageThisRequest(DownstreamContext context, DownstreamResponse response) { context.DownstreamResponse = response; } - internal HttpResponseMessage CreateHttpResponseMessage(CachedResponse cached) + internal DownstreamResponse CreateHttpResponseMessage(CachedResponse cached) { if (cached == null) { return null; } - var response = new HttpResponseMessage(cached.StatusCode); - - foreach (var header in cached.Headers) - { - response.Headers.Add(header.Key, header.Value); - } - var content = new MemoryStream(Convert.FromBase64String(cached.Body)); - response.Content = new StreamContent(content); + var streamContent = new StreamContent(content); foreach (var header in cached.ContentHeaders) { - response.Content.Headers.Add(header.Key, header.Value); + streamContent.Headers.Add(header.Key, header.Value); } - return response; + return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList()); } - internal async Task CreateCachedResponse(HttpResponseMessage response) + internal async Task CreateCachedResponse(DownstreamResponse response) { if (response == null) { @@ -111,7 +102,7 @@ namespace Ocelot.Cache.Middleware } var statusCode = response.StatusCode; - var headers = response.Headers.ToDictionary(v => v.Key, v => v.Value); + var headers = response.Headers.ToDictionary(v => v.Key, v => v.Values); string body = null; if (response.Content != null) diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 0d059932..31367f26 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -14,6 +14,7 @@ namespace Ocelot.Configuration.Builder private List _upstreamHttpMethod; private string _upstreamHost; private List _downstreamReRoutes; + private string _aggregator; public ReRouteBuilder() { @@ -56,6 +57,12 @@ namespace Ocelot.Configuration.Builder return this; } + public ReRouteBuilder WithAggregator(string aggregator) + { + _aggregator = aggregator; + return this; + } + public ReRoute Build() { return new ReRoute( @@ -63,7 +70,8 @@ namespace Ocelot.Configuration.Builder new PathTemplate(_upstreamTemplate), _upstreamHttpMethod, _upstreamTemplatePattern, - _upstreamHost + _upstreamHost, + _aggregator ); } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 1036f8df..821c0ed7 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -132,6 +132,7 @@ namespace Ocelot.Configuration.Creator .WithUpstreamTemplatePattern(upstreamTemplatePattern) .WithDownstreamReRoutes(applicableReRoutes) .WithUpstreamHost(aggregateReRoute.UpstreamHost) + .WithAggregator(aggregateReRoute.Aggregator) .Build(); return reRoute; diff --git a/src/Ocelot/Configuration/File/FileAggregateReRoute.cs b/src/Ocelot/Configuration/File/FileAggregateReRoute.cs index ca5013b2..c862094a 100644 --- a/src/Ocelot/Configuration/File/FileAggregateReRoute.cs +++ b/src/Ocelot/Configuration/File/FileAggregateReRoute.cs @@ -8,6 +8,7 @@ namespace Ocelot.Configuration.File public string UpstreamPathTemplate { get;set; } public string UpstreamHost { get; set; } public bool ReRouteIsCaseSensitive { get; set; } + public string Aggregator { get; set; } // Only supports GET..are you crazy!! POST, PUT WOULD BE CRAZY!! :) public List UpstreamHttpMethod diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 47f26291..c4c3952d 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -12,13 +12,15 @@ namespace Ocelot.Configuration PathTemplate upstreamPathTemplate, List upstreamHttpMethod, UpstreamPathTemplate upstreamTemplatePattern, - string upstreamHost) + string upstreamHost, + string aggregator) { UpstreamHost = upstreamHost; DownstreamReRoute = downstreamReRoute; UpstreamPathTemplate = upstreamPathTemplate; UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; + Aggregator = aggregator; } public PathTemplate UpstreamPathTemplate { get; private set; } @@ -26,5 +28,6 @@ namespace Ocelot.Configuration public List UpstreamHttpMethod { get; private set; } public string UpstreamHost { get; private set; } public List DownstreamReRoute { get; private set; } + public string Aggregator {get; private set;} } } diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index 37cfa0d8..a513e19e 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -3,6 +3,7 @@ using CacheManager.Core; using System; using System.Net.Http; using IdentityServer4.AccessTokenValidation; +using Ocelot.Middleware.Multiplexer; namespace Ocelot.DependencyInjection { @@ -23,5 +24,10 @@ namespace Ocelot.DependencyInjection IOcelotBuilder AddTransientDelegatingHandler(bool global = false) where T : DelegatingHandler; + + IOcelotBuilder AddSingletonDefinedAggregator() + where T : class, IDefinedAggregator; + IOcelotBuilder AddTransientDefinedAggregator() + where T : class, IDefinedAggregator; } } diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index c6fa20f4..96e084c1 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -154,6 +154,8 @@ namespace Ocelot.DependencyInjection _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); } public IOcelotAdministrationBuilder AddAdministration(string path, string secret) @@ -188,6 +190,20 @@ namespace Ocelot.DependencyInjection return new OcelotAdministrationBuilder(_services, _configurationRoot); } + public IOcelotBuilder AddSingletonDefinedAggregator() + where T : class, IDefinedAggregator + { + _services.AddSingleton(); + return this; + } + + public IOcelotBuilder AddTransientDefinedAggregator() + where T : class, IDefinedAggregator + { + _services.AddTransient(); + return this; + } + public IOcelotBuilder AddSingletonDelegatingHandler(bool global = false) where THandler : DelegatingHandler { diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index ec44ae23..8109bb52 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -35,6 +35,7 @@ PathTemplateDoesntStartWithForwardSlash, FileValidationFailedError, UnableToFindDelegatingHandlerProviderError, - CouldNotFindPlaceholderError + CouldNotFindPlaceholderError, + CouldNotFindAggregatorError } } diff --git a/src/Ocelot/Headers/AddHeadersToResponse.cs b/src/Ocelot/Headers/AddHeadersToResponse.cs index 76e9912b..6cdf9a22 100644 --- a/src/Ocelot/Headers/AddHeadersToResponse.cs +++ b/src/Ocelot/Headers/AddHeadersToResponse.cs @@ -1,10 +1,10 @@ namespace Ocelot.Headers { using System.Collections.Generic; - using System.Net.Http; using Ocelot.Configuration.Creator; using Ocelot.Infrastructure; using Ocelot.Logging; + using Ocelot.Middleware; public class AddHeadersToResponse : IAddHeadersToResponse { @@ -17,7 +17,7 @@ namespace Ocelot.Headers _placeholders = placeholders; } - public void Add(List addHeaders, HttpResponseMessage response) + public void Add(List addHeaders, DownstreamResponse response) { foreach(var add in addHeaders) { @@ -31,11 +31,11 @@ namespace Ocelot.Headers continue; } - response.Headers.TryAddWithoutValidation(add.Key, value.Data); + response.Headers.Add(new Header(add.Key, new List { value.Data })); } else { - response.Headers.TryAddWithoutValidation(add.Key, add.Value); + response.Headers.Add(new Header(add.Key, new List { add.Value })); } } } diff --git a/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs b/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs index c4763746..2bc43cfe 100644 --- a/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs +++ b/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs @@ -1,10 +1,10 @@ -using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; using Ocelot.Configuration; using Ocelot.Infrastructure; using Ocelot.Infrastructure.Extensions; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; using Ocelot.Request.Middleware; using Ocelot.Responses; @@ -12,19 +12,21 @@ namespace Ocelot.Headers { public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer { - private IPlaceholders _placeholders; + private readonly IPlaceholders _placeholders; public HttpResponseHeaderReplacer(IPlaceholders placeholders) { _placeholders = placeholders; } - public Response Replace(HttpResponseMessage response, List fAndRs, DownstreamRequest request) + public Response Replace(DownstreamResponse response, List fAndRs, DownstreamRequest request) { foreach (var f in fAndRs) { + var dict = response.Headers.ToDictionary(x => x.Key); + //if the response headers contain a matching find and replace - if(response.Headers.TryGetValues(f.Key, out var values)) + if(dict.TryGetValue(f.Key, out var values)) { //check to see if it is a placeholder in the find... var placeholderValue = _placeholders.Get(f.Find, request); @@ -32,16 +34,17 @@ namespace Ocelot.Headers if(!placeholderValue.IsError) { //if it is we need to get the value of the placeholder - //var find = replacePlaceholder(httpRequestMessage); - var replaced = values.ToList()[f.Index].Replace(placeholderValue.Data, f.Replace.LastCharAsForwardSlash()); - response.Headers.Remove(f.Key); - response.Headers.Add(f.Key, replaced); + var replaced = values.Values.ToList()[f.Index].Replace(placeholderValue.Data, f.Replace.LastCharAsForwardSlash()); + + response.Headers.Remove(response.Headers.First(item => item.Key == f.Key)); + response.Headers.Add(new Header(f.Key, new List { replaced })); } else { - var replaced = values.ToList()[f.Index].Replace(f.Find, f.Replace); - response.Headers.Remove(f.Key); - response.Headers.Add(f.Key, replaced); + var replaced = values.Values.ToList()[f.Index].Replace(f.Find, f.Replace); + + response.Headers.Remove(response.Headers.First(item => item.Key == f.Key)); + response.Headers.Add(new Header(f.Key, new List { replaced })); } } } diff --git a/src/Ocelot/Headers/IAddHeadersToResponse.cs b/src/Ocelot/Headers/IAddHeadersToResponse.cs index 51f23758..210ddd32 100644 --- a/src/Ocelot/Headers/IAddHeadersToResponse.cs +++ b/src/Ocelot/Headers/IAddHeadersToResponse.cs @@ -1,11 +1,13 @@ +using Ocelot.Middleware; + namespace Ocelot.Headers { using System.Collections.Generic; - using System.Net.Http; using Ocelot.Configuration.Creator; + using Ocelot.Middleware.Multiplexer; public interface IAddHeadersToResponse { - void Add(List addHeaders, HttpResponseMessage response); + void Add(List addHeaders, DownstreamResponse response); } } diff --git a/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs b/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs index 6c805b86..b871ac19 100644 --- a/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs +++ b/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; -using System.Net.Http; using Ocelot.Configuration; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; using Ocelot.Request.Middleware; using Ocelot.Responses; @@ -8,6 +9,6 @@ namespace Ocelot.Headers { public interface IHttpResponseHeaderReplacer { - Response Replace(HttpResponseMessage response, List fAndRs, DownstreamRequest httpRequestMessage); + Response Replace(DownstreamResponse response, List fAndRs, DownstreamRequest httpRequestMessage); } -} \ No newline at end of file +} diff --git a/src/Ocelot/Headers/IRemoveOutputHeaders.cs b/src/Ocelot/Headers/IRemoveOutputHeaders.cs index 339d0c35..8c081c3b 100644 --- a/src/Ocelot/Headers/IRemoveOutputHeaders.cs +++ b/src/Ocelot/Headers/IRemoveOutputHeaders.cs @@ -1,10 +1,12 @@ -using System.Net.Http.Headers; +using System.Collections.Generic; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; using Ocelot.Responses; namespace Ocelot.Headers { public interface IRemoveOutputHeaders { - Response Remove(HttpResponseHeaders headers); + Response Remove(List
headers); } } diff --git a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs index de5c25da..9544fe7f 100644 --- a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs @@ -1,7 +1,4 @@ using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Ocelot.DownstreamRouteFinder.Middleware; -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 19d863eb..2664b696 100644 --- a/src/Ocelot/Headers/RemoveOutputHeaders.cs +++ b/src/Ocelot/Headers/RemoveOutputHeaders.cs @@ -1,4 +1,7 @@ -using System.Net.Http.Headers; +using System.Collections.Generic; +using System.Linq; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; using Ocelot.Responses; namespace Ocelot.Headers @@ -14,14 +17,11 @@ namespace Ocelot.Headers { "Transfer-Encoding" }; - public Response Remove(HttpResponseHeaders headers) - { - foreach (var unsupported in _unsupportedRequestHeaders) - { - headers.Remove(unsupported); - } + public Response Remove(List
headers) + { + headers.RemoveAll(x => _unsupportedRequestHeaders.Contains(x.Key)); return new OkResponse(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Middleware/DownstreamContext.cs b/src/Ocelot/Middleware/DownstreamContext.cs index 044d1510..1913eea9 100644 --- a/src/Ocelot/Middleware/DownstreamContext.cs +++ b/src/Ocelot/Middleware/DownstreamContext.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Http; using Ocelot.Configuration; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Errors; +using Ocelot.Middleware.Multiplexer; using Ocelot.Request.Middleware; namespace Ocelot.Middleware @@ -27,9 +28,9 @@ namespace Ocelot.Middleware public DownstreamRequest DownstreamRequest { get; set; } - public HttpResponseMessage DownstreamResponse { get; set; } + public DownstreamResponse DownstreamResponse { get; set; } - public List Errors { get;set; } + public List Errors { get; } public bool IsError => Errors.Count > 0; } diff --git a/src/Ocelot/Middleware/DownstreamResponse.cs b/src/Ocelot/Middleware/DownstreamResponse.cs new file mode 100644 index 00000000..19345735 --- /dev/null +++ b/src/Ocelot/Middleware/DownstreamResponse.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; + +namespace Ocelot.Middleware +{ + public class DownstreamResponse + { + public DownstreamResponse(HttpContent content, HttpStatusCode statusCode, List
headers) + { + Content = content; + StatusCode = statusCode; + Headers = headers ?? new List
(); + } + + public DownstreamResponse(HttpResponseMessage response) + :this(response.Content, response.StatusCode, response.Headers.Select(x => new Header(x.Key, x.Value)).ToList()) + { + } + + public DownstreamResponse(HttpContent content, HttpStatusCode statusCode, IEnumerable>> headers) + :this(content, statusCode, headers.Select(x => new Header(x.Key, x.Value)).ToList()) + { + } + + public HttpContent Content { get; } + public HttpStatusCode StatusCode { get; } + public List
Headers { get; } + } +} diff --git a/src/Ocelot/Middleware/Header.cs b/src/Ocelot/Middleware/Header.cs new file mode 100644 index 00000000..96065699 --- /dev/null +++ b/src/Ocelot/Middleware/Header.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Ocelot.Middleware +{ + public class Header + { + public Header(string key, IEnumerable values) + { + Key = key; + Values = values ?? new List(); + } + + public string Key { get; } + public IEnumerable Values { get; } + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/CouldNotFindAggregatorError.cs b/src/Ocelot/Middleware/Multiplexer/CouldNotFindAggregatorError.cs new file mode 100644 index 00000000..76a19d7c --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/CouldNotFindAggregatorError.cs @@ -0,0 +1,12 @@ +using Ocelot.Errors; + +namespace Ocelot.Middleware.Multiplexer +{ + public class CouldNotFindAggregatorError : Error + { + public CouldNotFindAggregatorError(string aggregator) + : base($"Could not find Aggregator: {aggregator}", OcelotErrorCode.CouldNotFindAggregatorError) + { + } + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs b/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs new file mode 100644 index 00000000..b1585165 --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Ocelot.Middleware.Multiplexer +{ + public interface IDefinedAggregator + { + Task Aggregate(List responses); + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/IDefinedAggregatorProvider.cs b/src/Ocelot/Middleware/Multiplexer/IDefinedAggregatorProvider.cs new file mode 100644 index 00000000..375ebc33 --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/IDefinedAggregatorProvider.cs @@ -0,0 +1,10 @@ +using Ocelot.Configuration; +using Ocelot.Responses; + +namespace Ocelot.Middleware.Multiplexer +{ + public interface IDefinedAggregatorProvider + { + Response Get(ReRoute reRoute); + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/IResponseAggregator.cs b/src/Ocelot/Middleware/Multiplexer/IResponseAggregator.cs index b85c2cc8..89f2b00b 100644 --- a/src/Ocelot/Middleware/Multiplexer/IResponseAggregator.cs +++ b/src/Ocelot/Middleware/Multiplexer/IResponseAggregator.cs @@ -6,6 +6,6 @@ namespace Ocelot.Middleware.Multiplexer { public interface IResponseAggregator { - Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List downstreamContexts); + Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List downstreamResponses); } } diff --git a/src/Ocelot/Middleware/Multiplexer/IResponseAggregatorFactory.cs b/src/Ocelot/Middleware/Multiplexer/IResponseAggregatorFactory.cs new file mode 100644 index 00000000..b032ff1f --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/IResponseAggregatorFactory.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration; + +namespace Ocelot.Middleware.Multiplexer +{ + public interface IResponseAggregatorFactory + { + IResponseAggregator Get(ReRoute reRoute); + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs b/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs new file mode 100644 index 00000000..a74386db --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs @@ -0,0 +1,26 @@ +using Ocelot.Configuration; + +namespace Ocelot.Middleware.Multiplexer +{ + public class InMemoryResponseAggregatorFactory : IResponseAggregatorFactory + { + private readonly UserDefinedResponseAggregator _userDefined; + private readonly SimpleJsonResponseAggregator _simple; + + public InMemoryResponseAggregatorFactory(IDefinedAggregatorProvider provider) + { + _userDefined = new UserDefinedResponseAggregator(provider); + _simple = new SimpleJsonResponseAggregator(); + } + + public IResponseAggregator Get(ReRoute reRoute) + { + if(!string.IsNullOrEmpty(reRoute.Aggregator)) + { + return _userDefined; + } + + return _simple; + } + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs b/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs index c4ebb08b..ffe530e8 100644 --- a/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs +++ b/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Ocelot.Configuration; @@ -6,11 +7,11 @@ namespace Ocelot.Middleware.Multiplexer { public class Multiplexer : IMultiplexer { - private readonly IResponseAggregator _aggregator; + private readonly IResponseAggregatorFactory _factory; - public Multiplexer(IResponseAggregator aggregator) + public Multiplexer(IResponseAggregatorFactory factory) { - _aggregator = aggregator; + _factory = factory; } public async Task Multiplex(DownstreamContext context, ReRoute reRoute, OcelotRequestDelegate next) @@ -31,15 +32,40 @@ namespace Ocelot.Middleware.Multiplexer await Task.WhenAll(tasks); - var downstreamContexts = new List(); + var contexts = new List(); foreach (var task in tasks) { var finished = await task; - downstreamContexts.Add(finished); + contexts.Add(finished); } - await _aggregator.Aggregate(reRoute, context, downstreamContexts); + await Map(reRoute, context, contexts); + } + + private async Task Map(ReRoute reRoute, DownstreamContext context, List contexts) + { + if (reRoute.DownstreamReRoute.Count > 1) + { + var aggregator = _factory.Get(reRoute); + await aggregator.Aggregate(reRoute, context, contexts); + } + else + { + MapNotAggregate(context, contexts); + } + } + + private void MapNotAggregate(DownstreamContext originalContext, List downstreamContexts) + { + //assume at least one..if this errors then it will be caught by global exception handler + var finished = downstreamContexts.First(); + + originalContext.Errors.AddRange(finished.Errors); + + originalContext.DownstreamRequest = finished.DownstreamRequest; + + originalContext.DownstreamResponse = finished.DownstreamResponse; } private async Task Fire(DownstreamContext context, OcelotRequestDelegate next) diff --git a/src/Ocelot/Middleware/Multiplexer/ServiceLocatorDefinedAggregatorProvider.cs b/src/Ocelot/Middleware/Multiplexer/ServiceLocatorDefinedAggregatorProvider.cs new file mode 100644 index 00000000..b7b85d26 --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/ServiceLocatorDefinedAggregatorProvider.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Configuration; +using Ocelot.Responses; + +namespace Ocelot.Middleware.Multiplexer +{ + public class ServiceLocatorDefinedAggregatorProvider : IDefinedAggregatorProvider + { + private readonly Dictionary _aggregators; + + public ServiceLocatorDefinedAggregatorProvider(IServiceProvider services) + { + _aggregators = services.GetServices().ToDictionary(x => x.GetType().Name); + } + + public Response Get(ReRoute reRoute) + { + if(_aggregators.ContainsKey(reRoute.Aggregator)) + { + return new OkResponse(_aggregators[reRoute.Aggregator]); + } + + return new ErrorResponse(new CouldNotFindAggregatorError(reRoute.Aggregator)); + } + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs b/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs index 3d9a997d..bfc1351f 100644 --- a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs +++ b/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -12,18 +11,6 @@ namespace Ocelot.Middleware.Multiplexer public class SimpleJsonResponseAggregator : IResponseAggregator { public async Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List downstreamContexts) - { - if (reRoute.DownstreamReRoute.Count > 1) - { - await MapAggregtes(originalContext, downstreamContexts); - } - else - { - MapNotAggregate(originalContext, downstreamContexts); - } - } - - private async Task MapAggregtes(DownstreamContext originalContext, List downstreamContexts) { await MapAggregateContent(originalContext, downstreamContexts); } @@ -34,7 +21,7 @@ namespace Ocelot.Middleware.Multiplexer contentBuilder.Append("{"); - for (int i = 0; i < downstreamContexts.Count; i++) + for (var i = 0; i < downstreamContexts.Count; i++) { if (downstreamContexts[i].IsError) { @@ -54,13 +41,12 @@ namespace Ocelot.Middleware.Multiplexer contentBuilder.Append("}"); - originalContext.DownstreamResponse = new HttpResponseMessage(HttpStatusCode.OK) + var stringContent = new StringContent(contentBuilder.ToString()) { - Content = new StringContent(contentBuilder.ToString()) - { - Headers = {ContentType = new MediaTypeHeaderValue("application/json")} - } + Headers = {ContentType = new MediaTypeHeaderValue("application/json")} }; + + originalContext.DownstreamResponse = new DownstreamResponse(stringContent, HttpStatusCode.OK, new List>>()); } private static void MapAggregateError(DownstreamContext originalContext, List downstreamContexts, int i) @@ -68,17 +54,5 @@ namespace Ocelot.Middleware.Multiplexer originalContext.Errors.AddRange(downstreamContexts[i].Errors); originalContext.DownstreamResponse = downstreamContexts[i].DownstreamResponse; } - - private void MapNotAggregate(DownstreamContext originalContext, List downstreamContexts) - { - //assume at least one..if this errors then it will be caught by global exception handler - var finished = downstreamContexts.First(); - - originalContext.Errors = finished.Errors; - - originalContext.DownstreamRequest = finished.DownstreamRequest; - - originalContext.DownstreamResponse = finished.DownstreamResponse; - } } } diff --git a/src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs b/src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs new file mode 100644 index 00000000..aa44466d --- /dev/null +++ b/src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Configuration; + +namespace Ocelot.Middleware.Multiplexer +{ + public class UserDefinedResponseAggregator : IResponseAggregator + { + private readonly IDefinedAggregatorProvider _provider; + + public UserDefinedResponseAggregator(IDefinedAggregatorProvider provider) + { + _provider = provider; + } + + public async Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List downstreamResponses) + { + var aggregator = _provider.Get(reRoute); + + if (!aggregator.IsError) + { + var aggregateResponse = await aggregator.Data + .Aggregate(downstreamResponses.Select(x => x.DownstreamResponse).ToList()); + + originalContext.DownstreamResponse = aggregateResponse; + } + else + { + originalContext.Errors.AddRange(aggregator.Errors); + } + } + } +} diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 61d13f52..9d345dc2 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -22,11 +22,6 @@ public static class OcelotMiddlewareExtensions { - /// - /// Registers the Ocelot default middlewares - /// - /// - /// public static async Task UseOcelot(this IApplicationBuilder builder) { await builder.UseOcelot(new OcelotPipelineConfiguration()); @@ -34,12 +29,6 @@ return builder; } - /// - /// Registers Ocelot with a combination of default middlewares and optional middlewares in the configuration - /// - /// - /// - /// public static async Task UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) { var configuration = await CreateConfiguration(builder); @@ -230,26 +219,14 @@ }); } } - - private static void UseIfNotNull(this IApplicationBuilder builder, Func, Task> middleware) - { - if (middleware != null) - { - builder.Use(middleware); - } - } - /// - /// Configure a DiagnosticListener to listen for diagnostic events when the middleware starts and ends - /// - /// - private static void ConfigureDiagnosticListener(IApplicationBuilder builder) - { + private static void ConfigureDiagnosticListener(IApplicationBuilder builder) + { var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment)); var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener)); var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener)); diagnosticListener.SubscribeWithAdapter(listener); - } + } private static void OnShutdown(IApplicationBuilder app) { diff --git a/src/Ocelot/Request/Mapper/RequestMapper.cs b/src/Ocelot/Request/Mapper/RequestMapper.cs index ee04a551..67afc53d 100644 --- a/src/Ocelot/Request/Mapper/RequestMapper.cs +++ b/src/Ocelot/Request/Mapper/RequestMapper.cs @@ -5,9 +5,7 @@ using System.IO; using System.Linq; using System.Net.Http; - using System.Net.Http.Headers; using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Primitives; @@ -83,7 +81,6 @@ { foreach (var header in request.Headers) { - //todo get rid of if.. if (IsSupportedHeader(header)) { requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); diff --git a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs index 23ebd9de..4204374b 100644 --- a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs +++ b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs @@ -1,10 +1,6 @@ -using Microsoft.AspNetCore.Http; -using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; using System.Threading.Tasks; -using Ocelot.DownstreamRouteFinder.Middleware; -using Ocelot.Requester.QoS; namespace Ocelot.Requester.Middleware { @@ -36,7 +32,7 @@ namespace Ocelot.Requester.Middleware Logger.LogDebug("setting http response message"); - context.DownstreamResponse = response.Data; + context.DownstreamResponse = new DownstreamResponse(response.Data); } } } diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index 7bc55d94..bd265a61 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -1,17 +1,14 @@ using System.IO; using System.Linq; using System.Net; -using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using Ocelot.Headers; -using Ocelot.Responses; +using Ocelot.Middleware; namespace Ocelot.Responder { - using System.Collections.Generic; - /// /// Cannot unit test things in this class due to methods not being implemented /// on .net concretes used for testing @@ -25,7 +22,7 @@ namespace Ocelot.Responder _removeOutputHeaders = removeOutputHeaders; } - public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response) + public async Task SetResponseOnHttpContext(HttpContext context, DownstreamResponse response) { _removeOutputHeaders.Remove(response.Headers); @@ -36,12 +33,12 @@ namespace Ocelot.Responder foreach (var httpResponseHeader in response.Content.Headers) { - AddHeaderIfDoesntExist(context, httpResponseHeader); + AddHeaderIfDoesntExist(context, new Header(httpResponseHeader.Key, httpResponseHeader.Value)); } var content = await response.Content.ReadAsByteArrayAsync(); - AddHeaderIfDoesntExist(context, new KeyValuePair>("Content-Length", new []{ content.Length.ToString() }) ); + AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ content.Length.ToString() }) ); context.Response.OnStarting(state => { @@ -70,11 +67,11 @@ namespace Ocelot.Responder }, context); } - private static void AddHeaderIfDoesntExist(HttpContext context, KeyValuePair> httpResponseHeader) + private static void AddHeaderIfDoesntExist(HttpContext context, Header httpResponseHeader) { if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key)) { - context.Response.Headers.Add(httpResponseHeader.Key, new StringValues(httpResponseHeader.Value.ToArray())); + context.Response.Headers.Add(httpResponseHeader.Key, new StringValues(httpResponseHeader.Values.ToArray())); } } } diff --git a/src/Ocelot/Responder/IHttpResponder.cs b/src/Ocelot/Responder/IHttpResponder.cs index c5e5a0ca..73722f53 100644 --- a/src/Ocelot/Responder/IHttpResponder.cs +++ b/src/Ocelot/Responder/IHttpResponder.cs @@ -1,12 +1,13 @@ -using System.Net.Http; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; namespace Ocelot.Responder { public interface IHttpResponder { - Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response); + Task SetResponseOnHttpContext(HttpContext context, DownstreamResponse response); void SetErrorResponseOnContext(HttpContext context, int statusCode); } } diff --git a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs index 3e04fa3a..0487d811 100644 --- a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs +++ b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs @@ -1,11 +1,9 @@ using Microsoft.AspNetCore.Http; using Ocelot.Errors; -using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; using System.Collections.Generic; using System.Threading.Tasks; -using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Infrastructure.Extensions; namespace Ocelot.Responder.Middleware diff --git a/test/Ocelot.AcceptanceTests/AggregateTests.cs b/test/Ocelot.AcceptanceTests/AggregateTests.cs index 3944adfc..8e0e5dfd 100644 --- a/test/Ocelot.AcceptanceTests/AggregateTests.cs +++ b/test/Ocelot.AcceptanceTests/AggregateTests.cs @@ -1,11 +1,17 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -15,6 +21,7 @@ namespace Ocelot.AcceptanceTests public class AggregateTests : IDisposable { private IWebHost _serviceOneBuilder; + private IWebHost _serviceTwoBuilder; private readonly Steps _steps; private string _downstreamPathOne; private string _downstreamPathTwo; @@ -24,6 +31,75 @@ namespace Ocelot.AcceptanceTests _steps = new Steps(); } + [Fact] + public void should_return_response_200_with_simple_url_user_defined_aggregate() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51885, + } + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura" + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51886, + } + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Tom", + "Laura" + }, + Aggregator = "FakeDefinedAggregator" + } + } + }; + + var expected = "Bye from Laura, Bye from Tom"; + + this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51885", "/", 200, "{Hello from Laura}")) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51886", "/", 200, "{Hello from Tom}")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningWithSpecficAggregatorsRegisteredInDi()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) + .BDDfy(); + } + [Fact] public void should_return_response_200_with_simple_url() { @@ -325,7 +401,7 @@ namespace Ocelot.AcceptanceTests private void GivenServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) { - _serviceOneBuilder = new WebHostBuilder() + _serviceTwoBuilder = new WebHostBuilder() .UseUrls(baseUrl) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) @@ -351,7 +427,7 @@ namespace Ocelot.AcceptanceTests }) .Build(); - _serviceOneBuilder.Start(); + _serviceTwoBuilder.Start(); } internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPathOne, string expectedDownstreamPath) @@ -363,7 +439,33 @@ namespace Ocelot.AcceptanceTests public void Dispose() { _serviceOneBuilder?.Dispose(); + _serviceTwoBuilder?.Dispose(); _steps.Dispose(); } } + + public class FakeDepdendency + { + } + + public class FakeDefinedAggregator : IDefinedAggregator + { + private readonly FakeDepdendency _dep; + + public FakeDefinedAggregator(FakeDepdendency dep) + { + _dep = dep; + } + + public async Task Aggregate(List responses) + { + var one = await responses[0].Content.ReadAsStringAsync(); + var two = await responses[1].Content.ReadAsStringAsync(); + + var merge = $"{one}, {two}"; + merge = merge.Replace("Hello", "Bye").Replace("{", "").Replace("}", ""); + var headers = responses.SelectMany(x => x.Headers).ToList(); + return new DownstreamResponse(new StringContent(merge), HttpStatusCode.OK, headers); + } + } } diff --git a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs index 10cb7929..cde7b3bc 100644 --- a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs +++ b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs @@ -91,17 +91,17 @@ namespace Ocelot.AcceptanceTests var butterflyUrl = "http://localhost:9618"; - this.Given(x => GivenServiceOneIsRunning("http://localhost:51887", "/api/values", 200, "Hello from Laura", butterflyUrl)) + this.Given(x => GivenFakeButterfly(butterflyUrl)) + .And(x => GivenServiceOneIsRunning("http://localhost:51887", "/api/values", 200, "Hello from Laura", butterflyUrl)) .And(x => GivenServiceTwoIsRunning("http://localhost:51388", "/api/values", 200, "Hello from Tom", butterflyUrl)) - .And(x => GivenFakeButterfly(butterflyUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl)) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api002/values")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api002/values")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) .BDDfy(); var commandOnAllStateMachines = WaitFor(10000).Until(() => _butterflyCalled == 4); @@ -151,8 +151,8 @@ namespace Ocelot.AcceptanceTests var butterflyUrl = "http://localhost:9618"; - this.Given(x => GivenServiceOneIsRunning("http://localhost:51387", "/api/values", 200, "Hello from Laura", butterflyUrl)) - .And(x => GivenFakeButterfly(butterflyUrl)) + this.Given(x => GivenFakeButterfly(butterflyUrl)) + .And(x => GivenServiceOneIsRunning("http://localhost:51387", "/api/values", 200, "Hello from Laura", butterflyUrl)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl)) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values")) diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index cca5ac18..ba0da5d1 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -26,6 +26,7 @@ using System.IO.Compression; using System.Text; using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; using Ocelot.Requester; +using Ocelot.Middleware.Multiplexer; namespace Ocelot.AcceptanceTests { @@ -178,12 +179,6 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } -/* - public void GivenIHaveAddedXForwardedForHeader(string value) - { - _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation("X-Forwarded-For", value); - }*/ - public void GivenOcelotIsRunningWithMiddleareBeforePipeline(Func callback) { _webHostBuilder = new WebHostBuilder(); @@ -246,6 +241,39 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } + public void GivenOcelotIsRunningWithSpecficAggregatorsRegisteredInDi() + where TAggregator : class, IDefinedAggregator + where TDepedency : class + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddSingleton(); + s.AddOcelot() + .AddSingletonDefinedAggregator(); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + public void GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi() where TOne : DelegatingHandler where TWo : DelegatingHandler diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs index c1bc9b9d..b3bbface 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs @@ -1,7 +1,4 @@ -using Ocelot.Errors; -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.Cache +namespace Ocelot.UnitTests.Cache { using System.Linq; using System.Net; @@ -17,15 +14,17 @@ namespace Ocelot.UnitTests.Cache using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Logging; + using Ocelot.Middleware; using TestStack.BDDfy; using Xunit; using Microsoft.AspNetCore.Http; + using Ocelot.Middleware.Multiplexer; public class OutputCacheMiddlewareRealCacheTests { - private IOcelotCache _cacheManager; - private OutputCacheMiddleware _middleware; - private DownstreamContext _downstreamContext; + private readonly IOcelotCache _cacheManager; + private readonly OutputCacheMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; private OcelotRequestDelegate _next; private Mock _loggerFactory; private IRegionCreator _regionCreator; @@ -56,14 +55,10 @@ namespace Ocelot.UnitTests.Cache Headers = { ContentType = new MediaTypeHeaderValue("application/json")} }; - var response = new HttpResponseMessage(HttpStatusCode.OK) - { - Content = content, - }; + var response = new DownstreamResponse(content, HttpStatusCode.OK, new List>>()); this.Given(x => x.GivenResponseIsNotCached(response)) .And(x => x.GivenTheDownstreamRouteIs()) - .And(x => x.GivenThereAreNoErrors()) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheContentTypeHeaderIsCached()) .BDDfy(); @@ -81,9 +76,9 @@ namespace Ocelot.UnitTests.Cache header.First().ShouldBe("application/json"); } - private void GivenResponseIsNotCached(HttpResponseMessage message) + private void GivenResponseIsNotCached(DownstreamResponse response) { - _downstreamContext.DownstreamResponse = message; + _downstreamContext.DownstreamResponse = response; } private void GivenTheDownstreamRouteIs() @@ -96,10 +91,5 @@ namespace Ocelot.UnitTests.Cache _downstreamContext.DownstreamReRoute = reRoute; } - - private void GivenThereAreNoErrors() - { - _downstreamContext.Errors = new List(); - } } } diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index 83a4744d..cd0f9719 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -1,8 +1,4 @@ -using System.Net; -using Ocelot.Errors; -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.Cache +namespace Ocelot.UnitTests.Cache { using System; using System.Collections.Generic; @@ -19,17 +15,20 @@ namespace Ocelot.UnitTests.Cache using Ocelot.Logging; using TestStack.BDDfy; using Xunit; + using System.Net; + using Ocelot.Middleware; + using Ocelot.Middleware.Multiplexer; public class OutputCacheMiddlewareTests { private readonly Mock> _cacheManager; - private Mock _loggerFactory; + private readonly Mock _loggerFactory; private Mock _logger; private OutputCacheMiddleware _middleware; - private DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; + private readonly DownstreamContext _downstreamContext; + private readonly OcelotRequestDelegate _next; private CachedResponse _response; - private IRegionCreator _regionCreator; + private readonly IRegionCreator _regionCreator; public OutputCacheMiddlewareTests() { @@ -46,7 +45,17 @@ namespace Ocelot.UnitTests.Cache [Fact] public void should_returned_cached_item_when_it_is_in_cache() { - var cachedResponse = new CachedResponse(HttpStatusCode.OK, new Dictionary>(), "", new Dictionary>()); + var headers = new Dictionary> + { + { "test", new List { "test" } } + }; + + var contentHeaders = new Dictionary> + { + { "content-type", new List { "application/json" } } + }; + + var cachedResponse = new CachedResponse(HttpStatusCode.OK, headers, "", contentHeaders); this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) .And(x => x.GivenTheDownstreamRouteIs()) .When(x => x.WhenICallTheMiddleware()) @@ -59,7 +68,6 @@ namespace Ocelot.UnitTests.Cache { this.Given(x => x.GivenResponseIsNotCached()) .And(x => x.GivenTheDownstreamRouteIs()) - .And(x => x.GivenThereAreNoErrors()) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenTheCacheAddIsCalledCorrectly()) .BDDfy(); @@ -81,7 +89,7 @@ namespace Ocelot.UnitTests.Cache private void GivenResponseIsNotCached() { - _downstreamContext.DownstreamResponse = new HttpResponseMessage(); + _downstreamContext.DownstreamResponse = new DownstreamResponse(new HttpResponseMessage()); } private void GivenTheDownstreamRouteIs() @@ -101,11 +109,6 @@ namespace Ocelot.UnitTests.Cache _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; } - private void GivenThereAreNoErrors() - { - _downstreamContext.Errors = new List(); - } - private void ThenTheCacheGetIsCalledCorrectly() { _cacheManager diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index ce1d6989..04dcc0c1 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -134,7 +134,8 @@ namespace Ocelot.UnitTests.Configuration { "Tom", "Laura" - } + }, + Aggregator = "asdf" } } }; diff --git a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs index 67031879..1ec251cf 100644 --- a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs @@ -149,7 +149,6 @@ namespace Ocelot.UnitTests.Configuration .Then(x => ThenTheFollowingDownstreamIsReturned(downstream)) .BDDfy(); } - [Fact] public void should_add_trace_id_header() diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 1cd22388..0b13954a 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -18,6 +18,8 @@ using Shouldly; using IdentityServer4.AccessTokenValidation; using TestStack.BDDfy; using Xunit; +using static Ocelot.UnitTests.Middleware.UserDefinedResponseAggregatorTests; +using Ocelot.Middleware.Multiplexer; namespace Ocelot.UnitTests.DependencyInjection { @@ -177,6 +179,40 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } + [Fact] + public void should_add_singleton_defined_aggregators() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddSingletonDefinedAggregator()) + .When(x => AddSingletonDefinedAggregator()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificAggregators()) + .And(x => ThenTheAggregatorsAreSingleton()) + .BDDfy(); + } + + [Fact] + public void should_add_transient_defined_aggregators() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddTransientDefinedAggregator()) + .When(x => AddTransientDefinedAggregator()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificAggregators()) + .And(x => ThenTheAggregatorsAreTransient()) + .BDDfy(); + } + + private void AddSingletonDefinedAggregator() + where T : class, IDefinedAggregator + { + _ocelotBuilder.AddSingletonDefinedAggregator(); + } + + private void AddTransientDefinedAggregator() + where T : class, IDefinedAggregator + { + _ocelotBuilder.AddTransientDefinedAggregator(); + } + private void ThenTheSpecificHandlersAreSingleton() { var handlers = _serviceProvider.GetServices().ToList(); @@ -258,6 +294,32 @@ namespace Ocelot.UnitTests.DependencyInjection handlers[1].ShouldBeOfType(); } + private void ThenTheProviderIsRegisteredAndReturnsSpecificAggregators() + { + _serviceProvider = _services.BuildServiceProvider(); + var handlers = _serviceProvider.GetServices().ToList(); + handlers[0].ShouldBeOfType(); + handlers[1].ShouldBeOfType(); + } + + private void ThenTheAggregatorsAreTransient() + { + var aggregators = _serviceProvider.GetServices().ToList(); + var first = aggregators[0]; + aggregators = _serviceProvider.GetServices().ToList(); + var second = aggregators[0]; + first.ShouldNotBe(second); + } + + private void ThenTheAggregatorsAreSingleton() + { + var aggregators = _serviceProvider.GetServices().ToList(); + var first = aggregators[0]; + aggregators = _serviceProvider.GetServices().ToList(); + var second = aggregators[0]; + first.ShouldBe(second); + } + private void OnlyOneVersionOfEachCacheIsRegistered() { var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index 636174eb..63797346 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -1,7 +1,4 @@ -using Ocelot.Configuration; -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.DownstreamUrlCreator +namespace Ocelot.UnitTests.DownstreamUrlCreator { using System; using System.Collections.Generic; @@ -21,17 +18,19 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator using Shouldly; using Microsoft.AspNetCore.Http; using Ocelot.Request.Middleware; + using Ocelot.Configuration; + using Ocelot.Middleware; public class DownstreamUrlCreatorMiddlewareTests { private readonly Mock _downstreamUrlTemplateVariableReplacer; private OkResponse _downstreamPath; - private Mock _loggerFactory; + private readonly Mock _loggerFactory; private Mock _logger; private DownstreamUrlCreatorMiddleware _middleware; - private DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - private HttpRequestMessage _request; + private readonly DownstreamContext _downstreamContext; + private readonly OcelotRequestDelegate _next; + private readonly HttpRequestMessage _request; public DownstreamUrlCreatorMiddlewareTests() { @@ -212,7 +211,6 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator private void GivenTheDownstreamRequestUriIs(string uri) { _request.RequestUri = new Uri(uri); - //todo - not sure if needed _downstreamContext.DownstreamRequest = new DownstreamRequest(_request); } diff --git a/test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs b/test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs index a8eba48e..9c668dd1 100644 --- a/test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs +++ b/test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs @@ -1,29 +1,29 @@ using Xunit; -using Shouldly; using TestStack.BDDfy; using Ocelot.Headers; using System.Net.Http; using System.Collections.Generic; -using Ocelot.Configuration.Creator; using System.Linq; +using Ocelot.Configuration.Creator; using Moq; -using Ocelot.Infrastructure.RequestData; using Ocelot.Responses; using Ocelot.Infrastructure; using Ocelot.UnitTests.Responder; -using System; using Ocelot.Logging; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; +using Shouldly; namespace Ocelot.UnitTests.Headers { public class AddHeadersToResponseTests { - private IAddHeadersToResponse _adder; - private Mock _placeholders; - private HttpResponseMessage _response; + private readonly IAddHeadersToResponse _adder; + private readonly Mock _placeholders; + private DownstreamResponse _response; private List _addHeaders; private Mock _factory; - private Mock _logger; + private readonly Mock _logger; public AddHeadersToResponseTests() { @@ -111,7 +111,7 @@ namespace Ocelot.UnitTests.Headers private void ThenTheHeaderIsNotAdded(string key) { - _response.Headers.TryGetValues(key, out var values).ShouldBeFalse(); + _response.Headers.Any(x => x.Key == key).ShouldBeFalse(); } private void GivenTheTraceIdIs(string traceId) @@ -126,8 +126,8 @@ namespace Ocelot.UnitTests.Headers private void ThenTheHeaderIsReturned(string key, string value) { - var values = _response.Headers.GetValues(key); - values.First().ShouldBe(value); + var values = _response.Headers.First(x => x.Key == key); + values.Values.First().ShouldBe(value); } private void WhenIAdd() @@ -137,7 +137,7 @@ namespace Ocelot.UnitTests.Headers private void GivenAResponseMessage() { - _response = new HttpResponseMessage(); + _response = new DownstreamResponse(new HttpResponseMessage()); } private void GivenTheAddHeaders(List addHeaders) diff --git a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs index 4992479b..cc65624c 100644 --- a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs @@ -1,33 +1,33 @@ -using Xunit; -using Ocelot.Logging; -using Ocelot.Headers.Middleware; -using TestStack.BDDfy; -using Microsoft.AspNetCore.Http; -using System.Collections.Generic; -using Moq; -using Ocelot.Configuration; -using Ocelot.DownstreamRouteFinder; -using Ocelot.Configuration.Builder; -using Ocelot.Headers; -using System.Net.Http; -using Ocelot.Authorisation.Middleware; -using Ocelot.Middleware; - namespace Ocelot.UnitTests.Headers { + using Xunit; + using Ocelot.Logging; + using Ocelot.Headers.Middleware; + using TestStack.BDDfy; + using Microsoft.AspNetCore.Http; + using System.Collections.Generic; + using Moq; + using Ocelot.Configuration; + using Ocelot.DownstreamRouteFinder; + using Ocelot.Configuration.Builder; + using Ocelot.Headers; + using System.Net.Http; + using Ocelot.Authorisation.Middleware; + using Ocelot.Middleware; + using Ocelot.Middleware.Multiplexer; using System.Threading.Tasks; using Ocelot.Request.Middleware; public class HttpHeadersTransformationMiddlewareTests { - private Mock _preReplacer; - private Mock _postReplacer; + private readonly Mock _preReplacer; + private readonly Mock _postReplacer; private Mock _loggerFactory; private Mock _logger; - private HttpHeadersTransformationMiddleware _middleware; - private DownstreamContext _downstreamContext; + private readonly HttpHeadersTransformationMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; private OcelotRequestDelegate _next; - private Mock _addHeaders; + private readonly Mock _addHeaders; public HttpHeadersTransformationMiddlewareTests() { @@ -74,7 +74,7 @@ namespace Ocelot.UnitTests.Headers private void GivenTheHttpResponseMessageIs() { - _downstreamContext.DownstreamResponse = new HttpResponseMessage(); + _downstreamContext.DownstreamResponse = new DownstreamResponse(new HttpResponseMessage()); } private void GivenTheReRouteHasPreFindAndReplaceSetUp() @@ -98,7 +98,7 @@ namespace Ocelot.UnitTests.Headers private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly() { - _postReplacer.Verify(x => x.Replace(It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once); + _postReplacer.Verify(x => x.Replace(It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once); } private void GivenTheFollowingRequest() diff --git a/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs b/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs index 6eefdc4c..3e7bc077 100644 --- a/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs @@ -7,19 +7,21 @@ using Ocelot.Configuration; using System.Collections.Generic; using Ocelot.Responses; using System.Linq; +using System.Net; using Moq; using Ocelot.Infrastructure; using Ocelot.Middleware; using Ocelot.Infrastructure.RequestData; +using Ocelot.Middleware.Multiplexer; using Ocelot.Request.Middleware; namespace Ocelot.UnitTests.Headers { public class HttpResponseHeaderReplacerTests { - private HttpResponseMessage _response; + private DownstreamResponse _response; private Placeholders _placeholders; - private HttpResponseHeaderReplacer _replacer; + private readonly HttpResponseHeaderReplacer _replacer; private List _headerFindAndReplaces; private Response _result; private DownstreamRequest _request; @@ -37,11 +39,13 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_replace_headers() { - var response = new HttpResponseMessage(); - response.Headers.Add("test", "test"); + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("test", new List {"test"}) + }); - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("test", "test", "chiken", 0)); + var fAndRs = new List {new HeaderFindAndReplace("test", "test", "chiken", 0)}; this.Given(x => GivenTheHttpResponse(response)) .And(x => GivenTheFollowingHeaderReplacements(fAndRs)) @@ -53,8 +57,11 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_not_replace_headers() { - var response = new HttpResponseMessage(); - response.Headers.Add("test", "test"); + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("test", new List {"test"}) + }); var fAndRs = new List(); @@ -68,16 +75,21 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_replace_downstream_base_url_with_ocelot_base_url() { - var downstreamUrl = "http://downstream.com/"; + const string downstreamUrl = "http://downstream.com/"; - var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); - request.RequestUri = new System.Uri(downstreamUrl); + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }); - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0) + }; this.Given(x => GivenTheHttpResponse(response)) .And(x => GivenTheRequestIs(request)) @@ -90,16 +102,21 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_replace_downstream_base_url_with_ocelot_base_url_with_port() { - var downstreamUrl = "http://downstream.com/"; + const string downstreamUrl = "http://downstream.com/"; - var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); - request.RequestUri = new System.Uri(downstreamUrl); + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }); - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0)); + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0) + }; this.Given(x => GivenTheHttpResponse(response)) .And(x => GivenTheRequestIs(request)) @@ -112,16 +129,21 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_replace_downstream_base_url_with_ocelot_base_url_and_path() { - var downstreamUrl = "http://downstream.com/test/product"; + const string downstreamUrl = "http://downstream.com/test/product"; - var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); - request.RequestUri = new System.Uri(downstreamUrl); + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }); - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0) + }; this.Given(x => GivenTheHttpResponse(response)) .And(x => GivenTheRequestIs(request)) @@ -134,16 +156,21 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_replace_downstream_base_url_with_ocelot_base_url_with_path_and_port() { - var downstreamUrl = "http://downstream.com/test/product"; + const string downstreamUrl = "http://downstream.com/test/product"; - var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); - request.RequestUri = new System.Uri(downstreamUrl); + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }); - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0)); + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0) + }; this.Given(x => GivenTheHttpResponse(response)) .And(x => GivenTheRequestIs(request)) @@ -156,16 +183,21 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_replace_downstream_base_url_and_port_with_ocelot_base_url() { - var downstreamUrl = "http://downstream.com:123/test/product"; + const string downstreamUrl = "http://downstream.com:123/test/product"; - var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); - request.RequestUri = new System.Uri(downstreamUrl); + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }); - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0) + }; this.Given(x => GivenTheHttpResponse(response)) .And(x => GivenTheRequestIs(request)) @@ -178,16 +210,21 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_replace_downstream_base_url_and_port_with_ocelot_base_url_and_port() { - var downstreamUrl = "http://downstream.com:123/test/product"; + const string downstreamUrl = "http://downstream.com:123/test/product"; - var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); - request.RequestUri = new System.Uri(downstreamUrl); + var request = + new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)}; - var response = new HttpResponseMessage(); - response.Headers.Add("Location", downstreamUrl); + var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted, + new List>>() + { + new KeyValuePair>("Location", new List {downstreamUrl}) + }); - var fAndRs = new List(); - fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:321/", 0)); + var fAndRs = new List + { + new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:321/", 0) + }; this.Given(x => GivenTheHttpResponse(response)) .And(x => GivenTheRequestIs(request)) @@ -207,8 +244,8 @@ namespace Ocelot.UnitTests.Headers _result.ShouldBeOfType(); foreach (var f in _headerFindAndReplaces) { - _response.Headers.TryGetValues(f.Key, out var values); - values.ToList()[f.Index].ShouldBe("test"); + var values = _response.Headers.First(x => x.Key == f.Key); + values.Values.ToList()[f.Index].ShouldBe("test"); } } @@ -217,7 +254,7 @@ namespace Ocelot.UnitTests.Headers _headerFindAndReplaces = fAndRs; } - private void GivenTheHttpResponse(HttpResponseMessage response) + private void GivenTheHttpResponse(DownstreamResponse response) { _response = response; } @@ -229,17 +266,17 @@ namespace Ocelot.UnitTests.Headers private void ThenTheHeaderShouldBe(string key, string value) { - var test = _response.Headers.GetValues(key); - test.First().ShouldBe(value); + var test = _response.Headers.First(x => x.Key == key); + test.Values.First().ShouldBe(value); } - private void ThenTheHeadersAreReplaced() + private void ThenTheHeadersAreReplaced() { _result.ShouldBeOfType(); foreach (var f in _headerFindAndReplaces) { - _response.Headers.TryGetValues(f.Key, out var values); - values.ToList()[f.Index].ShouldBe(f.Replace); + var values = _response.Headers.First(x => x.Key == f.Key); + values.Values.ToList()[f.Index].ShouldBe(f.Replace); } } } diff --git a/test/Ocelot.UnitTests/Headers/RemoveHeadersTests.cs b/test/Ocelot.UnitTests/Headers/RemoveHeadersTests.cs index 35c792aa..9818c944 100644 --- a/test/Ocelot.UnitTests/Headers/RemoveHeadersTests.cs +++ b/test/Ocelot.UnitTests/Headers/RemoveHeadersTests.cs @@ -1,5 +1,5 @@ -using System.Net.Http; -using System.Net.Http.Headers; +using System.Collections.Generic; +using Ocelot.Middleware; using Ocelot.Responses; using Shouldly; using TestStack.BDDfy; @@ -9,7 +9,7 @@ namespace Ocelot.UnitTests.Headers { public class RemoveHeadersTests { - private HttpResponseHeaders _headers; + private List
_headers; private readonly Ocelot.Headers.RemoveOutputHeaders _removeOutputHeaders; private Response _result; @@ -21,18 +21,18 @@ namespace Ocelot.UnitTests.Headers [Fact] public void should_remove_header() { - var httpResponse = new HttpResponseMessage() + var headers = new List
() { - Headers = {{ "Transfer-Encoding", "chunked"}} + new Header("Transfer-Encoding", new List {"chunked"}) }; - this.Given(x => x.GivenAHttpContext(httpResponse.Headers)) + this.Given(x => x.GivenAHttpContext(headers)) .When(x => x.WhenIRemoveTheHeaders()) .Then(x => x.TheHeaderIsNoLongerInTheContext()) .BDDfy(); } - private void GivenAHttpContext(HttpResponseHeaders headers) + private void GivenAHttpContext(List
headers) { _headers = headers; } diff --git a/test/Ocelot.UnitTests/Infrastructure/HttpDataRepositoryTests.cs b/test/Ocelot.UnitTests/Infrastructure/HttpDataRepositoryTests.cs index c9f66009..555cfe32 100644 --- a/test/Ocelot.UnitTests/Infrastructure/HttpDataRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Infrastructure/HttpDataRepositoryTests.cs @@ -9,9 +9,9 @@ namespace Ocelot.UnitTests.Infrastructure { public class HttpDataRepositoryTests { - private HttpContext _httpContext; + private readonly HttpContext _httpContext; private IHttpContextAccessor _httpContextAccessor; - private HttpDataRepository _httpDataRepository; + private readonly HttpDataRepository _httpDataRepository; private object _result; public HttpDataRepositoryTests() diff --git a/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs b/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs index 51cbffef..729c4329 100644 --- a/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs +++ b/test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs @@ -51,7 +51,6 @@ namespace Ocelot.UnitTests.Infrastructure result.Data.ShouldBe("http://www.bbc.co.uk/"); } - [Fact] public void should_return_downstream_base_url_when_port_is_80_or_443() { @@ -80,4 +79,4 @@ namespace Ocelot.UnitTests.Infrastructure result.Data.ShouldBe(traceId); } } -} \ No newline at end of file +} diff --git a/test/Ocelot.UnitTests/Logging/AspDotNetLoggerTests.cs b/test/Ocelot.UnitTests/Logging/AspDotNetLoggerTests.cs index 8487cf8d..8a1fa891 100644 --- a/test/Ocelot.UnitTests/Logging/AspDotNetLoggerTests.cs +++ b/test/Ocelot.UnitTests/Logging/AspDotNetLoggerTests.cs @@ -53,7 +53,6 @@ namespace Ocelot.UnitTests.Logging [Fact] public void should_log_error() { - _logger.LogError($"a message from {_a} to {_b}", _ex); ThenLevelIsLogged("requestId: no request id, previousRequestId: no previous request id, message: a message from tom to laura, exception: System.Exception: oh no", LogLevel.Error); diff --git a/test/Ocelot.UnitTests/Middleware/DefinedAggregatorProviderTests.cs b/test/Ocelot.UnitTests/Middleware/DefinedAggregatorProviderTests.cs new file mode 100644 index 00000000..1874e94b --- /dev/null +++ b/test/Ocelot.UnitTests/Middleware/DefinedAggregatorProviderTests.cs @@ -0,0 +1,86 @@ +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Middleware.Multiplexer; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using static Ocelot.UnitTests.Middleware.UserDefinedResponseAggregatorTests; + +namespace Ocelot.UnitTests.Middleware +{ + public class DefinedAggregatorProviderTests + { + private ServiceLocatorDefinedAggregatorProvider _provider; + private Response _aggregator; + private ReRoute _reRoute; + + [Fact] + public void should_find_aggregator() + { + var reRoute = new ReRouteBuilder() + .WithAggregator("TestDefinedAggregator") + .Build(); + + this.Given(_ => GivenDefinedAggregator()) + .And(_ => GivenReRoute(reRoute)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheAggregatorIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_not_find_aggregator() + { + var reRoute = new ReRouteBuilder() + .WithAggregator("TestDefinedAggregator") + .Build(); + + this.Given(_ => GivenNoDefinedAggregator()) + .And(_ => GivenReRoute(reRoute)) + .When(_ => WhenIGet()) + .Then(_ => ThenAnErrorIsReturned()) + .BDDfy(); + } + + private void GivenDefinedAggregator() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(); + var services = serviceCollection.BuildServiceProvider(); + _provider = new ServiceLocatorDefinedAggregatorProvider(services); + } + + private void ThenTheAggregatorIsReturned() + { + _aggregator.Data.ShouldNotBeNull(); + _aggregator.Data.ShouldBeOfType(); + _aggregator.IsError.ShouldBeFalse(); + } + + private void GivenNoDefinedAggregator() + { + var serviceCollection = new ServiceCollection(); + var services = serviceCollection.BuildServiceProvider(); + _provider = new ServiceLocatorDefinedAggregatorProvider(services); + } + + private void GivenReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGet() + { + _aggregator = _provider.Get(_reRoute); + } + + private void ThenAnErrorIsReturned() + { + _aggregator.IsError.ShouldBeTrue(); + _aggregator.Errors[0].Message.ShouldBe("Could not find Aggregator: TestDefinedAggregator"); + _aggregator.Errors[0].ShouldBeOfType(); + } + } +} diff --git a/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs b/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs index 1584c987..787bfefa 100644 --- a/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs +++ b/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs @@ -19,13 +19,16 @@ namespace Ocelot.UnitTests.Middleware private readonly OcelotRequestDelegate _pipeline; private int _count; private Mock _aggregator; + private Mock _factory; public MultiplexerTests() { + _factory = new Mock(); _aggregator = new Mock(); _context = new DownstreamContext(new DefaultHttpContext()); _pipeline = context => Task.FromResult(_count++); - _multiplexer = new Multiplexer(_aggregator.Object); + _factory.Setup(x => x.Get(It.IsAny())).Returns(_aggregator.Object); + _multiplexer = new Multiplexer(_factory.Object); } [Fact] diff --git a/test/Ocelot.UnitTests/Middleware/OcelotMiddlewareTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotMiddlewareTests.cs index fd4e018c..9e13ed1a 100644 --- a/test/Ocelot.UnitTests/Middleware/OcelotMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Middleware/OcelotMiddlewareTests.cs @@ -62,7 +62,6 @@ namespace Ocelot.UnitTests.Middleware { _errors.Add(error); } - } public class FakeMiddleware : OcelotMiddleware @@ -72,4 +71,4 @@ namespace Ocelot.UnitTests.Middleware { } } -} \ No newline at end of file +} diff --git a/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs b/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs new file mode 100644 index 00000000..b4413b45 --- /dev/null +++ b/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs @@ -0,0 +1,64 @@ +namespace Ocelot.UnitTests.Middleware +{ + using Moq; + using Ocelot.Configuration.Builder; + using Ocelot.Middleware.Multiplexer; + using Shouldly; + using Xunit; + using Ocelot.Configuration; + using TestStack.BDDfy; + + public class ResponseAggregatorFactoryTests + { + private readonly InMemoryResponseAggregatorFactory _factory; + private Mock _provider; + private ReRoute _reRoute; + private IResponseAggregator _aggregator; + + public ResponseAggregatorFactoryTests() + { + _provider = new Mock(); + _factory = new InMemoryResponseAggregatorFactory(_provider.Object); + } + + [Fact] + public void should_return_simple_json_aggregator() + { + var reRoute = new ReRouteBuilder() + .Build(); + + this.Given(_ => GivenReRoute(reRoute)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheAggregatorIs()) + .BDDfy(); + } + + [Fact] + public void should_return_user_defined_aggregator() + { + var reRoute = new ReRouteBuilder() + .WithAggregator("doesntmatter") + .Build(); + + this.Given(_ => GivenReRoute(reRoute)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheAggregatorIs()) + .BDDfy(); + } + + private void GivenReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGet() + { + _aggregator = _factory.Get(_reRoute); + } + + private void ThenTheAggregatorIs() + { + _aggregator.ShouldBeOfType(); + } + } +} diff --git a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs b/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs index f0a2b224..fb2a1c98 100644 --- a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs +++ b/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Text; +using Castle.Components.DictionaryAdapter; using Microsoft.AspNetCore.Http; using Ocelot.Configuration; using Ocelot.Configuration.Builder; @@ -29,40 +30,6 @@ namespace Ocelot.UnitTests.Middleware _aggregator = new SimpleJsonResponseAggregator(); } - [Fact] - public void should_map_all_downstream_to_upstream_when_not_aggregate() - { - var billDownstreamReRoute = new DownstreamReRouteBuilder().WithKey("Bill").Build(); - - var downstreamReRoutes = new List - { - billDownstreamReRoute, - }; - - var reRoute = new ReRouteBuilder() - .WithDownstreamReRoutes(downstreamReRoutes) - .Build(); - - var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) - { - DownstreamResponse = - new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Bill says hi") }, - DownstreamReRoute = billDownstreamReRoute, - Errors = new List { new AnyError() }, - DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, new Uri("http://www.bbc.co.uk"))), - }; - - var downstreamContexts = new List { billDownstreamContext }; - - this.Given(x => GivenTheUpstreamContext(new DownstreamContext(new DefaultHttpContext()))) - .And(x => GivenTheReRoute(reRoute)) - .And(x => GivenTheDownstreamContext(downstreamContexts)) - .When(x => WhenIAggregate()) - .Then(x => ThenTheContentIs("Bill says hi")) - .And(x => ThenTheUpstreamContextIsMappedForNonAggregate()) - .BDDfy(); - } - [Fact] public void should_aggregate_n_responses_and_set_response_content_on_upstream_context() { @@ -82,15 +49,13 @@ namespace Ocelot.UnitTests.Middleware var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) { - DownstreamResponse = - new HttpResponseMessage(HttpStatusCode.OK) {Content = new StringContent("Bill says hi")}, + DownstreamResponse = new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new EditableList>>()), DownstreamReRoute = billDownstreamReRoute }; var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext()) { - DownstreamResponse = - new HttpResponseMessage(HttpStatusCode.OK) {Content = new StringContent("George says hi")}, + DownstreamResponse = new DownstreamResponse(new StringContent("George says hi"), HttpStatusCode.OK, new List>>()), DownstreamReRoute = georgeDownstreamReRoute }; @@ -126,19 +91,18 @@ namespace Ocelot.UnitTests.Middleware var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) { - DownstreamResponse = - new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Bill says hi") }, + DownstreamResponse = new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new List>>()), DownstreamReRoute = billDownstreamReRoute }; var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext()) { - DownstreamResponse = - new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Error") }, + DownstreamResponse = new DownstreamResponse(new StringContent("Error"), HttpStatusCode.OK, new List>>()), DownstreamReRoute = georgeDownstreamReRoute, - Errors = new List() { new AnyError() } }; + georgeDownstreamContext.Errors.Add(new AnyError()); + var downstreamContexts = new List { billDownstreamContext, georgeDownstreamContext }; var expected = "Error"; diff --git a/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs b/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs new file mode 100644 index 00000000..2b5bd552 --- /dev/null +++ b/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; +using Ocelot.Responses; +using Ocelot.UnitTests.Responder; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Middleware +{ + public class UserDefinedResponseAggregatorTests + { + private readonly UserDefinedResponseAggregator _aggregator; + private readonly Mock _provider; + private ReRoute _reRoute; + private List _contexts; + private DownstreamContext _context; + + public UserDefinedResponseAggregatorTests() + { + _provider = new Mock(); + _aggregator = new UserDefinedResponseAggregator(_provider.Object); + } + + [Fact] + public void should_call_aggregator() + { + var reRoute = new ReRouteBuilder().Build(); + + var context = new DownstreamContext(new DefaultHttpContext()); + + var contexts = new List + { + new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List>>()) + }, + new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List>>()) + } + }; + + this.Given(_ => GivenTheProviderReturnsAggregator()) + .And(_ => GivenReRoute(reRoute)) + .And(_ => GivenContexts(contexts)) + .And(_ => GivenContext(context)) + .When(_ => WhenIAggregate()) + .Then(_ => ThenTheProviderIsCalled()) + .And(_ => ThenTheContentIsCorrect()) + .BDDfy(); + } + + [Fact] + public void should_not_find_aggregator() + { + var reRoute = new ReRouteBuilder().Build(); + + var context = new DownstreamContext(new DefaultHttpContext()); + + var contexts = new List + { + new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List>>()) + }, + new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List>>()) + } + }; + + this.Given(_ => GivenTheProviderReturnsError()) + .And(_ => GivenReRoute(reRoute)) + .And(_ => GivenContexts(contexts)) + .And(_ => GivenContext(context)) + .When(_ => WhenIAggregate()) + .Then(_ => ThenTheProviderIsCalled()) + .And(_ => ThenTheErrorIsReturned()) + .BDDfy(); + } + + private void ThenTheErrorIsReturned() + { + _context.IsError.ShouldBeTrue(); + _context.Errors.Count.ShouldBe(1); + } + + private void GivenTheProviderReturnsError() + { + _provider.Setup(x => x.Get(It.IsAny())).Returns(new ErrorResponse(new AnyError())); + } + + private async Task ThenTheContentIsCorrect() + { + var content = await _context.DownstreamResponse.Content.ReadAsStringAsync(); + content.ShouldBe("Tom, Laura"); + } + + private void ThenTheProviderIsCalled() + { + _provider.Verify(x => x.Get(_reRoute), Times.Once); + } + + private void GivenContext(DownstreamContext context) + { + _context = context; + } + + private void GivenContexts(List contexts) + { + _contexts = contexts; + } + + private async Task WhenIAggregate() + { + await _aggregator.Aggregate(_reRoute, _context, _contexts); + } + + private void GivenTheProviderReturnsAggregator() + { + var aggregator = new TestDefinedAggregator(); + _provider.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse(aggregator)); + } + + private void GivenReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + public class TestDefinedAggregator : IDefinedAggregator + { + public async Task Aggregate(List responses) + { + var tom = await responses[0].Content.ReadAsStringAsync(); + var laura = await responses[1].Content.ReadAsStringAsync(); + var content = $"{tom}, {laura}"; + var headers = responses.SelectMany(x => x.Headers).ToList(); + return new DownstreamResponse(new StringContent(content), HttpStatusCode.OK, headers); + } + } + } +} diff --git a/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs b/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs index 83c486f2..8672ebf5 100644 --- a/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs @@ -153,8 +153,6 @@ namespace Ocelot.UnitTests.QueryStrings .AddQueryString(_downstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString, key, value); _request.RequestUri = new Uri(newUri); - //todo - might not need to instanciate - _downstreamRequest = new DownstreamRequest(_request); } private void GivenTheClaimParserReturns(Response claimValue) diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index c5895c1d..45855f65 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -140,25 +140,28 @@ namespace Ocelot.UnitTests.Requester .UseIISIntegration() .Configure(app => { - app.Run(async context => + app.Run(context => { if (_count == 0) { context.Response.Cookies.Append("test", "0"); context.Response.StatusCode = 200; _count++; - return; + return Task.CompletedTask; } + if (_count == 1) { if (context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue)) { context.Response.StatusCode = 200; - return; + return Task.CompletedTask; } context.Response.StatusCode = 500; } + + return Task.CompletedTask; }); }) .Build(); @@ -200,6 +203,7 @@ namespace Ocelot.UnitTests.Requester .Setup(x => x.Get(It.IsAny())) .Returns(new OkResponse>>(handlers)); } + private void GivenTheFactoryReturnsNothing() { var handlers = new List>(); diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index f4facf5e..910b3be8 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -1,6 +1,3 @@ -using Ocelot.Configuration.Builder; -using Ocelot.Middleware; - namespace Ocelot.UnitTests.Requester { using Microsoft.AspNetCore.Http; @@ -14,11 +11,16 @@ namespace Ocelot.UnitTests.Requester using Xunit; using Shouldly; using System.Threading.Tasks; + using Ocelot.Configuration.Builder; + using Ocelot.Middleware; + using System; + using System.Linq; + using Ocelot.UnitTests.Responder; public class HttpRequesterMiddlewareTests { private readonly Mock _requester; - private OkResponse _response; + private Response _response; private Mock _loggerFactory; private Mock _logger; private readonly HttpRequesterMiddleware _middleware; @@ -39,12 +41,27 @@ namespace Ocelot.UnitTests.Requester public void should_call_services_correctly() { this.Given(x => x.GivenTheRequestIs()) - .And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) + .And(x => x.GivenTheRequesterReturns(new OkResponse(new HttpResponseMessage()))) .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheScopedRepoIsCalledCorrectly()) + .Then(x => x.ThenTheDownstreamResponseIsSet()) .BDDfy(); } + [Fact] + public void should_set_error() + { + this.Given(x => x.GivenTheRequestIs()) + .And(x => x.GivenTheRequesterReturns(new ErrorResponse(new AnyError()))) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheErrorIsSet()) + .BDDfy(); + } + + private void ThenTheErrorIsSet() + { + _downstreamContext.IsError.ShouldBeTrue(); + } + private void WhenICallTheMiddleware() { _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); @@ -52,21 +69,34 @@ namespace Ocelot.UnitTests.Requester private void GivenTheRequestIs() { - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _downstreamContext.DownstreamReRoute = new DownstreamReRouteBuilder().Build(); + _downstreamContext = + new DownstreamContext(new DefaultHttpContext()) + { + DownstreamReRoute = new DownstreamReRouteBuilder().Build() + }; } - private void GivenTheRequesterReturns(HttpResponseMessage response) + private void GivenTheRequesterReturns(Response response) { - _response = new OkResponse(response); + _response = response; + _requester .Setup(x => x.GetResponse(It.IsAny())) .ReturnsAsync(_response); } - private void ThenTheScopedRepoIsCalledCorrectly() + private void ThenTheDownstreamResponseIsSet() { - _downstreamContext.DownstreamResponse.ShouldBe(_response.Data); + foreach (var httpResponseHeader in _response.Data.Headers) + { + if (_downstreamContext.DownstreamResponse.Headers.Any(x => x.Key == httpResponseHeader.Key)) + { + throw new Exception("Header in response not in downstreamresponse headers"); + } + } + + _downstreamContext.DownstreamResponse.Content.ShouldBe(_response.Data.Content); + _downstreamContext.DownstreamResponse.StatusCode.ShouldBe(_response.Data.StatusCode); } } } diff --git a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs index 8ee2be1f..27417907 100644 --- a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs +++ b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs @@ -120,7 +120,7 @@ namespace Ocelot.UnitTests.Responder // If this test fails then it's because the number of error codes has changed. // You should make the appropriate changes to the test cases here to ensure // they cover all the error codes, and then modify this assertion. - Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(34, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); + Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(35, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); } private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode) diff --git a/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs b/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs index ae465774..6ff956d2 100644 --- a/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs +++ b/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs @@ -1,10 +1,11 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; -using System.Text; using Microsoft.AspNetCore.Http; using Ocelot.Headers; +using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; using Ocelot.Responder; using Shouldly; using Xunit; @@ -26,9 +27,13 @@ namespace Ocelot.UnitTests.Responder public void should_remove_transfer_encoding_header() { var httpContext = new DefaultHttpContext(); - var httpResponseMessage = new HttpResponseMessage {Content = new StringContent("")}; - httpResponseMessage.Headers.Add("Transfer-Encoding", "woop"); - _responder.SetResponseOnHttpContext(httpContext, httpResponseMessage).GetAwaiter().GetResult(); + var response = new DownstreamResponse(new StringContent(""), HttpStatusCode.OK, + new List>> + { + new KeyValuePair>("Transfer-Encoding", new List {"woop"}) + }); + + _responder.SetResponseOnHttpContext(httpContext, response).GetAwaiter().GetResult(); var header = httpContext.Response.Headers["Transfer-Encoding"]; header.ShouldBeEmpty(); } @@ -37,8 +42,10 @@ namespace Ocelot.UnitTests.Responder public void should_have_content_length() { var httpContext = new DefaultHttpContext(); - var httpResponseMessage = new HttpResponseMessage { Content = new StringContent("test") }; - _responder.SetResponseOnHttpContext(httpContext, httpResponseMessage).GetAwaiter().GetResult(); + var response = new DownstreamResponse(new StringContent("test"), HttpStatusCode.OK, + new List>>()); + + _responder.SetResponseOnHttpContext(httpContext, response).GetAwaiter().GetResult(); var header = httpContext.Response.Headers["Content-Length"]; header.First().ShouldBe("4"); } @@ -47,9 +54,13 @@ namespace Ocelot.UnitTests.Responder public void should_add_header() { var httpContext = new DefaultHttpContext(); - var httpResponseMessage = new HttpResponseMessage { Content = new StringContent("test") }; - httpResponseMessage.Headers.Add("test", "test"); - _responder.SetResponseOnHttpContext(httpContext, httpResponseMessage).GetAwaiter().GetResult(); + var response = new DownstreamResponse(new StringContent(""), HttpStatusCode.OK, + new List>> + { + new KeyValuePair>("test", new List {"test"}) + }); + + _responder.SetResponseOnHttpContext(httpContext, response).GetAwaiter().GetResult(); var header = httpContext.Response.Headers["test"]; header.First().ShouldBe("test"); } diff --git a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs index 54a77f4d..79262997 100644 --- a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Ocelot.Middleware; +using Ocelot.Middleware.Multiplexer; namespace Ocelot.UnitTests.Responder { @@ -40,8 +41,7 @@ namespace Ocelot.UnitTests.Responder [Fact] public void should_not_return_any_errors() { - this.Given(x => x.GivenTheHttpResponseMessageIs(new HttpResponseMessage())) - .And(x => x.GivenThereAreNoPipelineErrors()) + this.Given(x => x.GivenTheHttpResponseMessageIs(new DownstreamResponse(new HttpResponseMessage()))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenThereAreNoErrors()) .BDDfy(); @@ -50,7 +50,7 @@ namespace Ocelot.UnitTests.Responder [Fact] public void should_return_any_errors() { - this.Given(x => x.GivenTheHttpResponseMessageIs(new HttpResponseMessage())) + this.Given(x => x.GivenTheHttpResponseMessageIs(new DownstreamResponse(new HttpResponseMessage()))) .And(x => x.GivenThereArePipelineErrors(new UnableToFindDownstreamRouteError("/path", "GET"))) .When(x => x.WhenICallTheMiddleware()) .Then(x => x.ThenThereAreNoErrors()) @@ -62,16 +62,11 @@ namespace Ocelot.UnitTests.Responder _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } - private void GivenTheHttpResponseMessageIs(HttpResponseMessage response) + private void GivenTheHttpResponseMessageIs(DownstreamResponse response) { _downstreamContext.DownstreamResponse = response; } - private void GivenThereAreNoPipelineErrors() - { - _downstreamContext.Errors = new List(); - } - private void ThenThereAreNoErrors() { //todo a better assert? @@ -79,7 +74,7 @@ namespace Ocelot.UnitTests.Responder private void GivenThereArePipelineErrors(Error error) { - _downstreamContext.Errors = new List(){error}; + _downstreamContext.Errors.Add(error); } } } From b46ef1945d6b7616f6b61ab79b42bb38054e2de0 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Thu, 12 Apr 2018 17:48:43 +0100 Subject: [PATCH 28/50] Feature/graphql (#312) * #298 initial hacking around better aggregation * #298 bit more hacking around * #298 abstraction over httpresponsemessage * #298 tidying up * #298 docs * #298 missed this * #306 example of how to do GraphQL --- docs/features/graphql.rst | 15 +++ docs/index.rst | 1 + docs/introduction/notsupported.rst | 19 ++- samples/OcelotGraphQL/OcelotGraphQL.csproj | 18 +++ samples/OcelotGraphQL/Program.cs | 131 +++++++++++++++++++++ samples/OcelotGraphQL/README.md | 71 +++++++++++ samples/OcelotGraphQL/configuration.json | 19 +++ 7 files changed, 270 insertions(+), 4 deletions(-) create mode 100644 docs/features/graphql.rst create mode 100644 samples/OcelotGraphQL/OcelotGraphQL.csproj create mode 100644 samples/OcelotGraphQL/Program.cs create mode 100644 samples/OcelotGraphQL/README.md create mode 100644 samples/OcelotGraphQL/configuration.json diff --git a/docs/features/graphql.rst b/docs/features/graphql.rst new file mode 100644 index 00000000..1b527314 --- /dev/null +++ b/docs/features/graphql.rst @@ -0,0 +1,15 @@ +GraphQL +======= + +OK you got me Ocelot doesn't directly support GraphQL but so many people have asked about it I wanted to show how easy it is to integrate +the `graphql-dotnet `_ library. + + +Please see the sample project `OcelotGraphQL `_. +Using a combination of the graphql-dotnet project and Ocelot's DelegatingHandler features this is pretty easy to do. +However I do not intend to integrate more closely with GraphQL at the moment. Check out the samples readme and that should give +you enough instruction on how to do this! + +Good luck and have fun :> + + diff --git a/docs/index.rst b/docs/index.rst index 78b305b2..7989fcdd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,6 +21,7 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n features/configuration features/routing features/requestaggregation + features/graphql features/servicediscovery features/servicefabric features/authentication diff --git a/docs/introduction/notsupported.rst b/docs/introduction/notsupported.rst index 35c916a0..37e5b5eb 100644 --- a/docs/introduction/notsupported.rst +++ b/docs/introduction/notsupported.rst @@ -7,7 +7,10 @@ Ocelot does not support... * Fowarding a host header - The host header that you send to Ocelot will not be forwarded to the downstream service. Obviously this would break everything :( -* Swagger - I have looked multiple times at building swagger.json out of the Ocelot configuration.json but it doesnt fit into the vision I have for Ocelot. If you would like to have Swagger in Ocelot then you must roll your own swagger.json and do the following in your Startup.cs or Program.cs. The code sample below registers a piece of middleware that loads your hand rolled swagger.json and returns it on /swagger/v1/swagger.json. It then registers the SwaggerUI middleware from Swashbuckle.AspNetCore +* Swagger - I have looked multiple times at building swagger.json out of the Ocelot configuration.json but it doesnt fit into the vision +I have for Ocelot. If you would like to have Swagger in Ocelot then you must roll your own swagger.json and do the following in your +Startup.cs or Program.cs. The code sample below registers a piece of middleware that loads your hand rolled swagger.json and returns +it on /swagger/v1/swagger.json. It then registers the SwaggerUI middleware from Swashbuckle.AspNetCore .. code-block:: csharp @@ -25,8 +28,16 @@ Ocelot does not support... app.UseOcelot().Wait(); -The main reasons why I don't think Swagger makes sense is we already hand roll our definition in configuration.json. If we want people developing against Ocelot to be able to see what routes are available then either share the configuration.json with them (This should be as easy as granting access to a repo etc) or use the Ocelot administration API so that they can query Ocelot for the configuration. +The main reasons why I don't think Swagger makes sense is we already hand roll our definition in configuration.json. +If we want people developing against Ocelot to be able to see what routes are available then either share the configuration.json +with them (This should be as easy as granting access to a repo etc) or use the Ocelot administration API so that they can query Ocelot for the configuration. -In addition to this many people will configure Ocelot to proxy all traffic like /products/{everything} to there product service and you would not be describing what is actually available if you parsed this and turned it into a Swagger path. Also Ocelot has no concept of the models that the downstream services can return and linking to the above problem the same endpoint can return multiple models. Ocelot does not know what models might be used in POST, PUT etc so it all gets a bit messy and finally the Swashbuckle package doesnt reload swagger.json if it changes during runtime. Ocelot's configuration can change during runtime so the Swagger and Ocelot information would not match. Unless I rolled my own Swagger implementation. +In addition to this many people will configure Ocelot to proxy all traffic like /products/{everything} to there product service +and you would not be describing what is actually available if you parsed this and turned it into a Swagger path. Also Ocelot has +no concept of the models that the downstream services can return and linking to the above problem the same endpoint can return +multiple models. Ocelot does not know what models might be used in POST, PUT etc so it all gets a bit messy and finally the Swashbuckle +package doesnt reload swagger.json if it changes during runtime. Ocelot's configuration can change during runtime so the Swagger and Ocelot +information would not match. Unless I rolled my own Swagger implementation. -If the user wants something to easily test against the Ocelot API then I suggest using Postman as a simple way to do this. It might even be possible to write something that maps configuration.json to the postman json spec. However I don't intend to do this. \ No newline at end of file +If the user wants something to easily test against the Ocelot API then I suggest using Postman as a simple way to do this. It might +even be possible to write something that maps configuration.json to the postman json spec. However I don't intend to do this. \ No newline at end of file diff --git a/samples/OcelotGraphQL/OcelotGraphQL.csproj b/samples/OcelotGraphQL/OcelotGraphQL.csproj new file mode 100644 index 00000000..2f29e395 --- /dev/null +++ b/samples/OcelotGraphQL/OcelotGraphQL.csproj @@ -0,0 +1,18 @@ + + + netcoreapp2.0 + + + + PreserveNewest + + + + + + + + + + + \ No newline at end of file diff --git a/samples/OcelotGraphQL/Program.cs b/samples/OcelotGraphQL/Program.cs new file mode 100644 index 00000000..94e1295b --- /dev/null +++ b/samples/OcelotGraphQL/Program.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Ocelot.Middleware; +using Ocelot.DependencyInjection; +using GraphQL.Types; +using GraphQL; +using Ocelot.Requester; +using Ocelot.Responses; +using System.Net.Http; +using System.Net; +using Microsoft.Extensions.DependencyInjection; +using System.Threading; + +namespace OcelotGraphQL +{ + public class Hero + { + public int Id { get; set; } + public string Name { get; set; } + } + + public class Query + { + private List _heroes = new List + { + new Hero { Id = 1, Name = "R2-D2" }, + new Hero { Id = 2, Name = "Batman" }, + new Hero { Id = 3, Name = "Wonder Woman" }, + new Hero { Id = 4, Name = "Tom Pallister" } + }; + + [GraphQLMetadata("hero")] + public Hero GetHero(int id) + { + return _heroes.FirstOrDefault(x => x.Id == id); + } + } + + public class GraphQlDelegatingHandler : DelegatingHandler + { + private readonly ISchema _schema; + + public GraphQlDelegatingHandler(ISchema schema) + { + _schema = schema; + } + + protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + //try get query from body, could check http method :) + var query = await request.Content.ReadAsStringAsync(); + + //if not body try query string, dont hack like this in real world.. + if(query.Length == 0) + { + var decoded = WebUtility.UrlDecode(request.RequestUri.Query); + query = decoded.Replace("?query=", ""); + } + + var result = _schema.Execute(_ => + { + _.Query = query; + }); + + //maybe check for errors and headers etc in real world? + var response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(result) + }; + + //ocelot will treat this like any other http request... + return response; + } + } + + public class Program + { + public static void Main(string[] args) + { + var schema = Schema.For(@" + type Hero { + id: Int + name: String + } + + type Query { + hero(id: Int): Hero + } + ", _ => { + _.Types.Include(); + }); + + new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddJsonFile("configuration.json") + .AddEnvironmentVariables(); + }) + .ConfigureServices(s => { + s.AddSingleton(schema); + s.AddOcelot() + .AddSingletonDelegatingHandler(); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + }) + .UseIISIntegration() + .Configure(app => + { + app.UseOcelot().Wait(); + }) + .Build() + .Run(); + } + } +} diff --git a/samples/OcelotGraphQL/README.md b/samples/OcelotGraphQL/README.md new file mode 100644 index 00000000..06ec668a --- /dev/null +++ b/samples/OcelotGraphQL/README.md @@ -0,0 +1,71 @@ +# Ocelot using GraphQL example + +Loads of people keep asking me if Ocelot will every support GraphQL, in my mind Ocelot and GraphQL are two different things that can work together. +I would not try and implement GraphQL in Ocelot instead I would either have Ocelot in front of GraphQL to handle things like authorisation / authentication or I would +bring in the awesome [graphql-dotnet](https://github.com/graphql-dotnet/graphql-dotnet) library and use it in a [DelegatingHandler](http://ocelot.readthedocs.io/en/latest/features/delegatinghandlers.html). This way you could have Ocelot and GraphQL without the extra hop to GraphQL. This same is an example of how to do that. + +## Example + +If you run this project with + +$ dotnet run + +Use postman or something to make the following requests and you can see Ocelot and GraphQL in action together... + +GET http://localhost:5000/graphql?query={ hero(id: 4) { id name } } + +RESPONSE +```json + { + "data": { + "hero": { + "id": 4, + "name": "Tom Pallister" + } + } + } +``` + +POST http://localhost:5000/graphql + +BODY +```json + { hero(id: 4) { id name } } +``` + +RESPONSE +```json + { + "data": { + "hero": { + "id": 4, + "name": "Tom Pallister" + } + } + } +``` + +## Notes + +Please note this project never goes out to another service, it just gets the data for GraphQL in memory. You would need to add the details of your GraphQL server in configuration.json e.g. + +```json +{ + "ReRoutes": [ + { + "DownstreamPathTemplate": "/graphql", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "yourgraphqlhost.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/graphql", + "DelegatingHandlers": [ + "GraphQlDelegatingHandler" + ] + } + ] + } +``` \ No newline at end of file diff --git a/samples/OcelotGraphQL/configuration.json b/samples/OcelotGraphQL/configuration.json new file mode 100644 index 00000000..115006f9 --- /dev/null +++ b/samples/OcelotGraphQL/configuration.json @@ -0,0 +1,19 @@ +{ + "ReRoutes": [ + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "jsonplaceholder.typicode.com", + "Port": 80 + } + ], + "UpstreamPathTemplate": "/graphql", + "DelegatingHandlers": [ + "GraphQlDelegatingHandler" + ] + } + ] + } + \ No newline at end of file From cbaa2c3096b031dd8458484fe3bf3c26c7cbf132 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Fri, 13 Apr 2018 11:23:44 +0100 Subject: [PATCH 29/50] #296 renamed configuration.json to ocelot.json in preparation --- .gitignore | 4 +- docs/features/configuration.rst | 8 +-- docs/features/delegatinghandlers.rst | 8 +-- docs/features/headerstransformation.rst | 6 +- docs/features/loadbalancer.rst | 2 +- docs/features/ratelimiting.rst | 2 +- docs/features/requestaggregation.rst | 4 +- docs/features/requestid.rst | 4 +- docs/features/routing.rst | 4 +- docs/features/tracing.rst | 2 +- docs/features/websockets.rst | 2 +- docs/introduction/gettingstarted.rst | 8 +-- docs/introduction/notsupported.rst | 8 +-- samples/OcelotGraphQL/OcelotGraphQL.csproj | 2 +- samples/OcelotGraphQL/Program.cs | 2 +- samples/OcelotGraphQL/README.md | 2 +- .../{configuration.json => ocelot.json} | 0 samples/OcelotServiceFabric/.gitignore | 2 +- .../OcelotApplicationApiGateway.csproj | 2 +- .../WebCommunicationListener.cs | 2 +- .../{configuration.json => ocelot.json} | 0 .../Provider/FileConfigurationProvider.cs | 7 +-- .../Repository/FileConfigurationRepository.cs | 6 +- .../ConfigurationBuilderExtensions.cs | 2 +- .../CustomMiddlewareTests.cs | 2 +- .../Ocelot.AcceptanceTests.csproj | 2 +- test/Ocelot.AcceptanceTests/Steps.cs | 26 ++++---- .../TestConfiguration.cs | 2 +- .../Ocelot.AcceptanceTests/configuration.json | 59 ------------------- .../AdministrationTests.cs | 12 ++-- .../Ocelot.IntegrationTests.csproj | 2 +- test/Ocelot.IntegrationTests/RaftTests.cs | 6 +- .../ThreadSafeHeadersTests.cs | 6 +- .../{configuration.json => ocelot.json} | 0 .../Ocelot.ManualTest.csproj | 2 +- test/Ocelot.ManualTest/Program.cs | 2 +- .../{configuration.json => ocelot.json} | 0 .../FileConfigurationRepositoryTests.cs | 2 +- .../{configuration.json => ocelot.json} | 0 39 files changed, 76 insertions(+), 136 deletions(-) rename samples/OcelotGraphQL/{configuration.json => ocelot.json} (100%) rename samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/{configuration.json => ocelot.json} (100%) delete mode 100755 test/Ocelot.AcceptanceTests/configuration.json rename test/Ocelot.IntegrationTests/{configuration.json => ocelot.json} (100%) mode change 100755 => 100644 rename test/Ocelot.ManualTest/{configuration.json => ocelot.json} (100%) rename test/Ocelot.UnitTests/{configuration.json => ocelot.json} (100%) mode change 100755 => 100644 diff --git a/.gitignore b/.gitignore index 836e0bfe..476938f0 100644 --- a/.gitignore +++ b/.gitignore @@ -243,7 +243,7 @@ tools/ .DS_Store # Ocelot acceptance test config -test/Ocelot.AcceptanceTests/configuration.json +test/Ocelot.AcceptanceTests/ocelot.json # Read the docstates _build/ @@ -251,4 +251,4 @@ _static/ _templates/ # JetBrains Rider -.idea/ \ No newline at end of file +.idea/ diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 8a1e61a1..6bc05f66 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -1,7 +1,7 @@ Configuration ============ -An example configuration can be found `here `_. +An example configuration can be found `here `_. There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration. The ReRoutes are the objects that tell Ocelot how to treat an upstream request. The Global configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful @@ -99,12 +99,12 @@ to you .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("configuration.json") + .AddJsonFile("ocelot.json") .AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json") .AddEnvironmentVariables(); }) -Ocelot should now use the environment specific configuration and fall back to configuration.json if there isnt one. +Ocelot should now use the environment specific configuration and fall back to ocelot.json if there isnt one. You also need to set the corresponding environment variable which is ASPNETCORE_ENVIRONMENT. More info on this can be found in the `asp.net core docs `_. @@ -119,7 +119,7 @@ If you add the following when you register your services Ocelot will attempt to .AddOcelot() .AddStoreOcelotConfigurationInConsul(); -You also need to add the following to your configuration.json. This is how Ocelot +You also need to add the following to your ocelot.json. This is how Ocelot finds your Consul agent and interacts to load and store the configuration from Consul. .. code-block:: json diff --git a/docs/features/delegatinghandlers.rst b/docs/features/delegatinghandlers.rst index 80dfa16b..445a46fb 100644 --- a/docs/features/delegatinghandlers.rst +++ b/docs/features/delegatinghandlers.rst @@ -40,7 +40,7 @@ Or transient as below... .AddTransientDelegatingHandler() Both of these Add methods have a default parameter called global which is set to false. If it is false then the intent of -the DelegatingHandler is to be applied to specific ReRoutes via configuration.json (more on that later). If it is set to true +the DelegatingHandler is to be applied to specific ReRoutes via ocelot.json (more on that later). If it is set to true then it becomes a global handler and will be applied to all ReRoutes. e.g. @@ -58,7 +58,7 @@ Or transient as below... .AddTransientDelegatingHandler(true) Finally if you want ReRoute specific DelegatingHandlers or to order your specific and / or global (more on this later) DelegatingHandlers -then you must add the following json to the specific ReRoute in configuration.json. The names in the array must match the class names of your +then you must add the following json to the specific ReRoute in ocelot.json. The names in the array must match the class names of your DelegatingHandlers for Ocelot to match them together. .. code-block:: json @@ -70,8 +70,8 @@ DelegatingHandlers for Ocelot to match them together. You can have as many DelegatingHandlers as you want and they are run in the following order: -1. Any globals that are left in the order they were added to services and are not in the DelegatingHandlers array from configuration.json. -2. Any non global DelegatingHandlers plus any globals that were in the DelegatingHandlers array from configuration.json ordered as they are in the DelegatingHandlers array. +1. Any globals that are left in the order they were added to services and are not in the DelegatingHandlers array from ocelot.json. +2. Any non global DelegatingHandlers plus any globals that were in the DelegatingHandlers array from ocelot.json ordered as they are in the DelegatingHandlers array. 3. Tracing DelegatingHandler if enabled (see tracing docs). 4. QoS DelegatingHandler if enabled (see QoS docs). 5. The HttpClient sends the HttpRequestMessage. diff --git a/docs/features/headerstransformation.rst b/docs/features/headerstransformation.rst index 6a333141..06ece33a 100644 --- a/docs/features/headerstransformation.rst +++ b/docs/features/headerstransformation.rst @@ -9,7 +9,7 @@ Add to Response This feature was requested in `GitHub #280 `_. I have only implemented for responses but could add for requests in the future. -If you want to add a header to your downstream response please add the following to a ReRoute in configuration.json.. +If you want to add a header to your downstream response please add the following to a ReRoute in ocelot.json.. .. code-block:: json @@ -41,7 +41,7 @@ The key is "Test" and the value is "http://www.bbc.co.uk/, http://ocelot.com/". Pre Downstream Request ^^^^^^^^^^^^^^^^^^^^^^ -Add the following to a ReRoute in configuration.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This header will be changed before the request downstream and will be sent to the downstream server. +Add the following to a ReRoute in ocelot.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This header will be changed before the request downstream and will be sent to the downstream server. .. code-block:: json @@ -52,7 +52,7 @@ Add the following to a ReRoute in configuration.json in order to replace http:// Post Downstream Request ^^^^^^^^^^^^^^^^^^^^^^ -Add the following to a ReRoute in configuration.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This transformation will take place after Ocelot has received the response from the downstream service. +Add the following to a ReRoute in ocelot.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This transformation will take place after Ocelot has received the response from the downstream service. .. code-block:: json diff --git a/docs/features/loadbalancer.rst b/docs/features/loadbalancer.rst index f587aa22..c2cc0f26 100644 --- a/docs/features/loadbalancer.rst +++ b/docs/features/loadbalancer.rst @@ -16,7 +16,7 @@ You must choose in your configuration which load balancer to use. Configuration ^^^^^^^^^^^^^ -The following shows how to set up multiple downstream services for a ReRoute using configuration.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up. +The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up. .. code-block:: json diff --git a/docs/features/ratelimiting.rst b/docs/features/ratelimiting.rst index d929c905..7f9c0768 100644 --- a/docs/features/ratelimiting.rst +++ b/docs/features/ratelimiting.rst @@ -23,7 +23,7 @@ Period - This value specifies the period, such as 1s, 5m, 1h,1d and so on. PeriodTimespan - This value specifies that we can retry after a certain number of seconds. Limit - This value specifies the maximum number of requests that a client can make in a defined period. -You can also set the following in the GlobalConfiguration part of configuration.json +You can also set the following in the GlobalConfiguration part of ocelot.json .. code-block:: json diff --git a/docs/features/requestaggregation.rst b/docs/features/requestaggregation.rst index c3c7ad13..a5ca30f6 100644 --- a/docs/features/requestaggregation.rst +++ b/docs/features/requestaggregation.rst @@ -7,7 +7,7 @@ architecture with Ocelot. This feature was requested as part of `Issue 79 `_ and further improvements were made as part of `Issue 298 `_. -In order to set this up you must do something like the following in your configuration.json. Here we have specified two normal ReRoutes and each one has a Key property. +In order to set this up you must do something like the following in your ocelot.json. Here we have specified two normal ReRoutes and each one has a Key property. We then specify an Aggregate that composes the two ReRoutes using their keys in the ReRouteKeys list and says then we have the UpstreamPathTemplate which works like a normal ReRoute. Obviously you cannot have duplicate UpstreamPathTemplates between ReRoutes and Aggregates. You can use all of Ocelot's normal ReRoute options apart from RequestIdKey (explained in gotchas below). @@ -17,7 +17,7 @@ Advanced register your own Aggregators Ocelot started with just the basic request aggregation and since then we have added a more advanced method that let's the user take in the responses from the downstream services and then aggregate them into a response object. -The configuration.json setup is pretty much the same as the basic aggregation approach apart from you need to add an Aggregator property like below. +The ocelot.json setup is pretty much the same as the basic aggregation approach apart from you need to add an Aggregator property like below. .. code-block:: json diff --git a/docs/features/requestid.rst b/docs/features/requestid.rst index 6e4f239b..37752eda 100644 --- a/docs/features/requestid.rst +++ b/docs/features/requestid.rst @@ -12,7 +12,7 @@ In order to use the reques tid feature you have two options. *Global* -In your configuration.json set the following in the GlobalConfiguration section. This will be used for all requests into Ocelot. +In your ocelot.json set the following in the GlobalConfiguration section. This will be used for all requests into Ocelot. .. code-block:: json @@ -24,7 +24,7 @@ I reccomend using the GlobalConfiguration unless you really need it to be ReRout *ReRoute* -If you want to override this for a specific ReRoute add the following to configuration.json for the specific ReRoute. +If you want to override this for a specific ReRoute add the following to ocelot.json for the specific ReRoute. .. code-block:: json diff --git a/docs/features/routing.rst b/docs/features/routing.rst index b414d6c5..664021a6 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -145,9 +145,9 @@ Priority ^^^^^^^^ In `Issue 270 `_ I finally decided to expose the ReRoute priority in -configuration.json. This means you can decide in what order you want your ReRoutes to match the Upstream HttpRequest. +ocelot.json. This means you can decide in what order you want your ReRoutes to match the Upstream HttpRequest. -In order to get this working add the following to a ReRoute in configuration.json, 0 is just an example value here but will explain below. +In order to get this working add the following to a ReRoute in ocelot.json, 0 is just an example value here but will explain below. .. code-block:: json diff --git a/docs/features/tracing.rst b/docs/features/tracing.rst index 0a896d45..aa4b1fd3 100644 --- a/docs/features/tracing.rst +++ b/docs/features/tracing.rst @@ -20,7 +20,7 @@ In your ConfigureServices method option.Service = "Ocelot"; }); -Then in your configuration.json add the following to the ReRoute you want to trace.. +Then in your ocelot.json add the following to the ReRoute you want to trace.. .. code-block:: json diff --git a/docs/features/websockets.rst b/docs/features/websockets.rst index 828d1051..624f42a9 100644 --- a/docs/features/websockets.rst +++ b/docs/features/websockets.rst @@ -15,7 +15,7 @@ In your Configure method you need to tell your application to use WebSockets. app.UseOcelot().Wait(); }) -Then in your configuration.json add the following to proxy a ReRoute using websockets. +Then in your ocelot.json add the following to proxy a ReRoute using websockets. .. code-block:: json diff --git a/docs/introduction/gettingstarted.rst b/docs/introduction/gettingstarted.rst index 7f81cb51..0c11f40d 100644 --- a/docs/introduction/gettingstarted.rst +++ b/docs/introduction/gettingstarted.rst @@ -18,7 +18,7 @@ All versions can be found `here `_. **Configuration** -The following is a very basic configuration.json. It won't do anything but should get Ocelot starting. +The following is a very basic ocelot.json. It won't do anything but should get Ocelot starting. .. code-block:: json @@ -55,7 +55,7 @@ AddOcelot() (adds ocelot services), UseOcelot().Wait() (sets up all the Ocelot m .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("configuration.json") + .AddJsonFile("ocelot.json") .AddEnvironmentVariables(); }) .ConfigureServices(s => { @@ -87,7 +87,7 @@ All versions can be found `here `_. **Configuration** -The following is a very basic configuration.json. It won't do anything but should get Ocelot starting. +The following is a very basic ocelot.json. It won't do anything but should get Ocelot starting. .. code-block:: json @@ -135,7 +135,7 @@ An example startup using a json file for configuration can be seen below. .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddJsonFile("configuration.json") + .AddJsonFile("ocelot.json") .AddEnvironmentVariables(); Configuration = builder.Build(); diff --git a/docs/introduction/notsupported.rst b/docs/introduction/notsupported.rst index 37e5b5eb..e578bca2 100644 --- a/docs/introduction/notsupported.rst +++ b/docs/introduction/notsupported.rst @@ -7,7 +7,7 @@ Ocelot does not support... * Fowarding a host header - The host header that you send to Ocelot will not be forwarded to the downstream service. Obviously this would break everything :( -* Swagger - I have looked multiple times at building swagger.json out of the Ocelot configuration.json but it doesnt fit into the vision +* Swagger - I have looked multiple times at building swagger.json out of the Ocelot ocelot.json but it doesnt fit into the vision I have for Ocelot. If you would like to have Swagger in Ocelot then you must roll your own swagger.json and do the following in your Startup.cs or Program.cs. The code sample below registers a piece of middleware that loads your hand rolled swagger.json and returns it on /swagger/v1/swagger.json. It then registers the SwaggerUI middleware from Swashbuckle.AspNetCore @@ -28,8 +28,8 @@ it on /swagger/v1/swagger.json. It then registers the SwaggerUI middleware from app.UseOcelot().Wait(); -The main reasons why I don't think Swagger makes sense is we already hand roll our definition in configuration.json. -If we want people developing against Ocelot to be able to see what routes are available then either share the configuration.json +The main reasons why I don't think Swagger makes sense is we already hand roll our definition in ocelot.json. +If we want people developing against Ocelot to be able to see what routes are available then either share the ocelot.json with them (This should be as easy as granting access to a repo etc) or use the Ocelot administration API so that they can query Ocelot for the configuration. In addition to this many people will configure Ocelot to proxy all traffic like /products/{everything} to there product service @@ -40,4 +40,4 @@ package doesnt reload swagger.json if it changes during runtime. Ocelot's config information would not match. Unless I rolled my own Swagger implementation. If the user wants something to easily test against the Ocelot API then I suggest using Postman as a simple way to do this. It might -even be possible to write something that maps configuration.json to the postman json spec. However I don't intend to do this. \ No newline at end of file +even be possible to write something that maps ocelot.json to the postman json spec. However I don't intend to do this. \ No newline at end of file diff --git a/samples/OcelotGraphQL/OcelotGraphQL.csproj b/samples/OcelotGraphQL/OcelotGraphQL.csproj index 2f29e395..ec015d1b 100644 --- a/samples/OcelotGraphQL/OcelotGraphQL.csproj +++ b/samples/OcelotGraphQL/OcelotGraphQL.csproj @@ -3,7 +3,7 @@ netcoreapp2.0 - + PreserveNewest diff --git a/samples/OcelotGraphQL/Program.cs b/samples/OcelotGraphQL/Program.cs index 94e1295b..81938f9c 100644 --- a/samples/OcelotGraphQL/Program.cs +++ b/samples/OcelotGraphQL/Program.cs @@ -106,7 +106,7 @@ namespace OcelotGraphQL .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("configuration.json") + .AddJsonFile("ocelot.json") .AddEnvironmentVariables(); }) .ConfigureServices(s => { diff --git a/samples/OcelotGraphQL/README.md b/samples/OcelotGraphQL/README.md index 06ec668a..b2101d56 100644 --- a/samples/OcelotGraphQL/README.md +++ b/samples/OcelotGraphQL/README.md @@ -47,7 +47,7 @@ RESPONSE ## Notes -Please note this project never goes out to another service, it just gets the data for GraphQL in memory. You would need to add the details of your GraphQL server in configuration.json e.g. +Please note this project never goes out to another service, it just gets the data for GraphQL in memory. You would need to add the details of your GraphQL server in ocelot.json e.g. ```json { diff --git a/samples/OcelotGraphQL/configuration.json b/samples/OcelotGraphQL/ocelot.json similarity index 100% rename from samples/OcelotGraphQL/configuration.json rename to samples/OcelotGraphQL/ocelot.json diff --git a/samples/OcelotServiceFabric/.gitignore b/samples/OcelotServiceFabric/.gitignore index 733dbb07..84aa4b08 100644 --- a/samples/OcelotServiceFabric/.gitignore +++ b/samples/OcelotServiceFabric/.gitignore @@ -13,7 +13,7 @@ # Service fabric OcelotApplicationApiGatewayPkg/Code OcelotApplication/OcelotApplicationApiGatewayPkg/Code/appsettings.json -OcelotApplication/OcelotApplicationApiGatewayPkg/Code/configuration.json +OcelotApplication/OcelotApplicationApiGatewayPkg/Code/ocelot.json OcelotApplication/OcelotApplicationApiGatewayPkg/Code/runtimes/ OcelotApplicationServicePkg/Code OcelotApplication/OcelotApplicationApiGatewayPkg/Code/web.config diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj index 1ff69c09..08324cbe 100644 --- a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj @@ -8,7 +8,7 @@ OcelotApplicationApiGateway - + PreserveNewest diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs index 6685e11f..7d913cfa 100644 --- a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs @@ -67,7 +67,7 @@ namespace OcelotApplicationApiGateway .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("configuration.json") + .AddJsonFile("ocelot.json") .AddEnvironmentVariables(); }) .ConfigureLogging((hostingContext, logging) => diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/configuration.json b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ocelot.json similarity index 100% rename from samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/configuration.json rename to samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ocelot.json diff --git a/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs index fdcd949b..2d4cad8b 100644 --- a/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs @@ -1,7 +1,4 @@ -using System; -using System.IO; using System.Threading.Tasks; -using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.Configuration.Repository; using Ocelot.Responses; @@ -10,7 +7,7 @@ namespace Ocelot.Configuration.Provider { public class FileConfigurationProvider : IFileConfigurationProvider { - private IFileConfigurationRepository _repo; + private readonly IFileConfigurationRepository _repo; public FileConfigurationProvider(IFileConfigurationRepository repo) { @@ -23,4 +20,4 @@ namespace Ocelot.Configuration.Provider return new OkResponse(fileConfig.Data); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs index ff5e3876..9619984f 100644 --- a/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs @@ -12,10 +12,12 @@ namespace Ocelot.Configuration.Repository private readonly string _configFilePath; private static readonly object _lock = new object(); - + + private const string ConfigurationFileName = "ocelot"; + public FileConfigurationRepository(IHostingEnvironment hostingEnvironment) { - _configFilePath = $"{AppContext.BaseDirectory}/configuration{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json"; + _configFilePath = $"{AppContext.BaseDirectory}/{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json"; } public Task> Get() diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs index 56360ad5..c5c5ee4d 100644 --- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs +++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs @@ -7,7 +7,7 @@ namespace Ocelot.DependencyInjection { public static class ConfigurationBuilderExtensions { - [Obsolete("Please set BaseUrl in configuration.json GlobalConfiguration.BaseUrl")] + [Obsolete("Please set BaseUrl in ocelot.json GlobalConfiguration.BaseUrl")] public static IConfigurationBuilder AddOcelotBaseUrl(this IConfigurationBuilder builder, string baseUrl) { var memorySource = new MemoryConfigurationSource(); diff --git a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs index 341986fe..422348d0 100644 --- a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -26,7 +26,7 @@ namespace Ocelot.AcceptanceTests { _counter = 0; _steps = new Steps(); - _configurationPath = "configuration.json"; + _configurationPath = "ocelot.json"; } [Fact] diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 4f67b970..e1e16688 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -15,7 +15,7 @@ - + PreserveNewest diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index ba0da5d1..f5c186ca 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -66,7 +66,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureLogging((hostingContext, logging) => @@ -124,7 +124,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -152,7 +152,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -190,7 +190,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -221,7 +221,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -254,7 +254,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -287,7 +287,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -319,7 +319,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -358,7 +358,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -400,7 +400,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -437,7 +437,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -465,7 +465,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -525,7 +525,7 @@ namespace Ocelot.AcceptanceTests var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile("configuration.json") + .AddJsonFile("ocelot.json") .AddEnvironmentVariables(); var configuration = builder.Build(); diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index 5f29d1bd..6f5818ca 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -5,6 +5,6 @@ namespace Ocelot.AcceptanceTests { public static class TestConfiguration { - public static string ConfigurationPath => Path.Combine(AppContext.BaseDirectory, "configuration.json"); + public static string ConfigurationPath => Path.Combine(AppContext.BaseDirectory, "ocelot.json"); } } diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json deleted file mode 100755 index 027025b0..00000000 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "ReRoutes": [ - { - "DownstreamPathTemplate": "41879/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": "Get", - "AuthenticationOptions": { - "Provider": null, - "ProviderRootUrl": null, - "ApiName": null, - "RequireHttps": false, - "AllowedScopes": [], - "ApiSecret": 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 - }, - "AdministrationPath": null, - "RateLimitOptions": { - "ClientIdHeader": "ClientId", - "QuotaExceededMessage": null, - "RateLimitCounterPrefix": "ocelot", - "DisableRateLimitHeaders": false, - "HttpStatusCode": 429 - } - } -} \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 763d642c..84b0c0bb 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -461,7 +461,7 @@ namespace Ocelot.IntegrationTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(x => @@ -579,7 +579,7 @@ namespace Ocelot.IntegrationTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(x => { @@ -612,7 +612,7 @@ namespace Ocelot.IntegrationTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(x => @@ -652,7 +652,7 @@ namespace Ocelot.IntegrationTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(x => { @@ -675,7 +675,7 @@ namespace Ocelot.IntegrationTests private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) { - var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json"; + var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); @@ -688,7 +688,7 @@ namespace Ocelot.IntegrationTests var text = File.ReadAllText(configurationPath); - configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; if (File.Exists(configurationPath)) { diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj index 644d217b..5fc9947b 100644 --- a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj @@ -14,7 +14,7 @@ ..\..\codeanalysis.ruleset - + PreserveNewest diff --git a/test/Ocelot.IntegrationTests/RaftTests.cs b/test/Ocelot.IntegrationTests/RaftTests.cs index 3dc01bb9..baad5091 100644 --- a/test/Ocelot.IntegrationTests/RaftTests.cs +++ b/test/Ocelot.IntegrationTests/RaftTests.cs @@ -301,7 +301,7 @@ namespace Ocelot.IntegrationTests private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) { - var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json"; + var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); @@ -314,7 +314,7 @@ namespace Ocelot.IntegrationTests var text = File.ReadAllText(configurationPath); - configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; if (File.Exists(configurationPath)) { @@ -340,7 +340,7 @@ namespace Ocelot.IntegrationTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddJsonFile("peers.json", optional: true, reloadOnChange: true); #pragma warning disable CS0618 config.AddOcelotBaseUrl(url); diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index e61fa63b..ed125aa4 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -108,7 +108,7 @@ namespace Ocelot.IntegrationTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(x => @@ -138,7 +138,7 @@ namespace Ocelot.IntegrationTests private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) { - var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json"; + var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); @@ -151,7 +151,7 @@ namespace Ocelot.IntegrationTests var text = File.ReadAllText(configurationPath); - configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; if (File.Exists(configurationPath)) { diff --git a/test/Ocelot.IntegrationTests/configuration.json b/test/Ocelot.IntegrationTests/ocelot.json old mode 100755 new mode 100644 similarity index 100% rename from test/Ocelot.IntegrationTests/configuration.json rename to test/Ocelot.IntegrationTests/ocelot.json diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj index d2e3e7bc..a43e6b23 100644 --- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj +++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj @@ -16,7 +16,7 @@ - + PreserveNewest diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index 3bab21b7..966b1097 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -21,7 +21,7 @@ namespace Ocelot.ManualTest .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("configuration.json") + .AddJsonFile("ocelot.json") .AddEnvironmentVariables(); }) .ConfigureServices(s => { diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/ocelot.json similarity index 100% rename from test/Ocelot.ManualTest/configuration.json rename to test/Ocelot.ManualTest/ocelot.json diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs index 15dd3729..b27015e3 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs @@ -113,7 +113,7 @@ namespace Ocelot.UnitTests.Configuration private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) { - var configurationPath = $"{AppContext.BaseDirectory}/configuration{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; + var configurationPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); diff --git a/test/Ocelot.UnitTests/configuration.json b/test/Ocelot.UnitTests/ocelot.json old mode 100755 new mode 100644 similarity index 100% rename from test/Ocelot.UnitTests/configuration.json rename to test/Ocelot.UnitTests/ocelot.json From 698541eb699e8f373602858a22d403e219aa283c Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Fri, 13 Apr 2018 12:34:34 +0100 Subject: [PATCH 30/50] removed things we dont need for tests --- test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj | 2 +- test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj | 2 +- test/Ocelot.UnitTests/ocelot.json | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 test/Ocelot.UnitTests/ocelot.json diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index e1e16688..18ff45f1 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -15,7 +15,7 @@ - + PreserveNewest diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj index 5fc9947b..97c8782f 100644 --- a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj @@ -14,7 +14,7 @@ ..\..\codeanalysis.ruleset - + PreserveNewest diff --git a/test/Ocelot.UnitTests/ocelot.json b/test/Ocelot.UnitTests/ocelot.json deleted file mode 100644 index 618957b8..00000000 --- a/test/Ocelot.UnitTests/ocelot.json +++ /dev/null @@ -1 +0,0 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"/test/test/{test}","UpstreamPathTemplate":null,"UpstreamHttpMethod":null,"AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ApiName":null,"RequireHttps":false,"AllowedScopes":[],"ApiSecret":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 060dd1dc78df03d9bdef1cd47f59e8bb5ce680d7 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Fri, 13 Apr 2018 12:36:43 +0100 Subject: [PATCH 31/50] another file we dont need --- test/Ocelot.UnitTests/Ocelot.UnitTests.csproj | 6 ------ test/Ocelot.UnitTests/idsrv3test.pfx | Bin 3395 -> 0 bytes 2 files changed, 6 deletions(-) delete mode 100644 test/Ocelot.UnitTests/idsrv3test.pfx diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index 8df6245a..395dde67 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -28,12 +28,6 @@ - - - PreserveNewest - - - diff --git a/test/Ocelot.UnitTests/idsrv3test.pfx b/test/Ocelot.UnitTests/idsrv3test.pfx deleted file mode 100644 index 0247dea03f0cc23694291f21310f3ae88880e2bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3395 zcmY*ac{tQ<7yiu{V_$|rA%;e>y=E+9O_nU#g|hFBC`-tmWsqeoSyGW9LYA!AlQm0d zlqs?cuRUcMvVK$7_r34+{c)aipZh-Nxy~QY_1q{N(`7J-3WZ}lgwlyV(0Q=O1fl`u z;TYE;IL2iPy@0|&nf_0rK7rt<4^TL2G9|X44F8>Cqz8fXaF7!e4sw9vh0_0zrd-Yp zq5aB`c0pwf*#!pE3`1~`u};|qLmAL66%SqGD&c1ok7w*g=3CPGVk4GBqUnz5R$^lb z8Dv(rRpfX7yvJ$AZ8B=IukK|?oWq7THPW9AE8<%>%oONtPAOw&x8_?KHa0J|WVwA0 zIe9iq|#j@0h-r2z9#p>N7n4=mGfXBZdZv zm>}$|9($ZRdyt-g#VGBa?>B!qNzif-i+FE)kucwfM0uQ_?eH5E22H7{O&W(b9&xxe z%p<>vWCX)-exQO)Be=&=gf&-c#+j`(NUetfn}WVXG{= z^!3S{N|*XdJW@10Ikf3}LcuN>qA~Ixlg<}c;VO{NzpbcV)gX{XXMvCF$|Bihu8%Mj`v7 z@JI#bMy0mL?ntjDyu>tItFCrcM?2T4qxi{DAYXF4re+jt!0KM!4AX1-`m6J2B-j7$ ztQmXW9+nsyVA76pGD!SNDBJX7<=P3^TAtMP*S&|$8V_zcInNp6F})=P6L9WM3skx( zrU*k+zF?-S=hmjpL4Q3zv>!AS5ZdH` zP7@1%4o~2pGsTCkqHI#fTE9t6L}0I0RV#X80*5W8dQ!d^3i!EAcx!{g?Ymhx9_uH| z%5-;5L5^5@FPajHS9ShoBMyy!p(c{qxOAL#hI6ENh505_rZ0?SGHg>G?cH-JcX$bP zvvcygKZ|q33xcOvl0F>Lq;-3oT1}&U{+hFQhdrnZ&f3Cd?*G~+e;NZj-CLQ#d7u*d z-zLck*=~$_*oTD=7glD2s_n4ZBbndKCJM<*Y#U_RIHLGB-|y!WU`T^)1|P6xbeP|G zVeM+?bDY~u1~eh71YCS>5m|2W++)$^^VxHSdmxwhWqlh$#}_R*QJIE}!YhyC22(}y z-pGi)Mp$4isupi_SdyK1kwa|ypqYxDZM%%-W8XLUrq=uHuIVLfoLXn0Ft*+*&7DasMmP3gdi3$so3cjv zU3_I_!HIUJ-KLn$?yVs^q%Nt?{K4vH$8|KG-fP7I-JGh){ZkukKp&IeTFS zofK|@;`zesc<{wV&~=^Lpxwgq@1SZU!pFuL4xnXwJhXzpFXWPHqe5C^&F$XOKSyA*?hARwF^42%X)?En0pbR1|X1Ofs80A>9z2}c|9=>s8v zEFceP0#bk)B`W|LfCL~z!7_mQA0!RPQ8WpPf}*g$)hhsoqDlYhLQ^z_KfESzA7%UR z0wA<8pCMoXxBgEJg#e8I z^!ZaN7vLt~Loo#6Kiktl^Kj613iSpI0w}5OUj_7kE&%=Q0@7Z?>>U#@$=@yzfrG{o ztFTv(L~LX}xO!x0^EITtLxl@_o6uy5gghAR{hz9rAUI9X6qKa_Nw%q za~SdO27));Ss1O7WmAmU?z>@+sX7%|EH>F*@OZUVn!`%vFPjg13@;Tl|_JIFJuO?ibe+@(=CitY0KN zmhw8P&DGlJBqvEH_i~51(xCCqvU$O5a^w(gap!{;x$=mI;>(I{4_^3{xSVlt0*&Z-y38aD8;?f`*U1VzA?{YPa$fn^V7$cGLd)&c%khfmt-qvZ_d8X! z7hHsG8{dHEPrBwl**uN9qgJ5pDa-DS;*TkBvMr}WsGRp(tl&q zOLj#>q5fr!g3h>N*4Lo!^2f&yedb9`Kc@UII#(J*#=~mQpg7_^@Qad_`7&Rw^Q13P zmkj26C2^Lfg&(Un^M{l&&Z~Al#>~&po-IRgbH;zV|EZU6sq2W4r<`>`jAnHJX0F#X zoYLuTJJ&S__HOHM}CU)!}{mUnHM4&H-PJ zDgU|rTaFE6VJ^#8$-7}h}^b=$AFm^Ju%|Irt#Xm@y!x8ht)nP}yX zak6LD=XrWjz}YIk=NKi;Oyzuyhr4N#>$;BIHeVmO7CwR&BH~$h($R>lxm#|jH)hMo z7Cl?fME$4w@i!`TUwnfzepq`tb2MXQ>vjOez4DO&G+ zwbxqf;c;Lz7e^2GJN4&pn)*n036&#X{M)L}3jNt9WQoG#Ltw0 zBSd@4uASn_19~vFMd|jhEOlmOnzg#t-W`Y8`{ihls#Ej*@-YyvQR5@XB{Zgn*UU@bPjBb)ma-dM*TyAY#Qr-I?}ssTqWiQUU~9nVL8urj8g zB=?6~(E%Bt>5<*!OPB%-9y0pkl!uu8}JyuP^C{VwK-!6&8CcOsFR z#AD|e+mNE9i#41w#l(h}rbw&h^*Xp8>93ZTvg}r-DJps1W6hRpeV*HGw|(EWnX7>t zi;7~9X)yDN{8DJzLpxCoH*tL3SHK!$Z}tQc<%NTk$t)S*4<=4>wFvMd!y)pV_liw) z7Z+8=AXg^QgwL(&DRsQU5*({(LDt{G-4Rx#dhx6AP+_msH%Jue6QCy=B0w?y#4k$7;> z=5ttmpV&vFVv}ZY>6NE%#+W))M)nU;WMS%-mtLT!)&4oAMhnY2Hb@dJUGXLb^4wIex}=co7n{7tD1N!| zw63xzN%ImPTf3iZ?X@yq6*F$jX5my$Q%SSyOrlD)y}jkyw`e{y&l34ahp)821A!iS z4-;-p@j6Gn!f>FJQ2ZzwD76?f6_^_WN5dA?3G%E0bF79+L#MT|(Yv~t5ct?-mV0Fj V%$88{h~I%@Xjg7x^oQR@_8&Ry9S;Bi From f88e1f65ef10f959cee004882129e6a1b3d27eba Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Fri, 13 Apr 2018 17:16:43 +0100 Subject: [PATCH 32/50] removed some async we dont need --- .../Provider/IOcelotConfigurationProvider.cs | 6 ++---- .../Provider/OcelotConfigurationProvider.cs | 10 ++++------ .../ConsulFileConfigurationRepository.cs | 19 +++++++++++++------ .../IOcelotConfigurationRepository.cs | 10 ++++------ .../InMemoryOcelotConfigurationRepository.cs | 8 ++++---- .../Setter/FileConfigurationSetter.cs | 8 +++++--- .../DependencyInjection/OcelotBuilder.cs | 9 ++------- .../DownstreamRouteFinderMiddleware.cs | 2 +- .../Middleware/ExceptionHandlerMiddleware.cs | 9 +++------ .../Middleware/OcelotMiddlewareExtensions.cs | 11 +++++------ src/Ocelot/Raft/FilePeersProvider.cs | 3 +-- test/Ocelot.ManualTest/Program.cs | 6 ++++++ .../InMemoryConfigurationRepositoryTests.cs | 4 ++-- .../OcelotConfigurationProviderTests.cs | 4 ++-- .../DownstreamRouteFinderMiddlewareTests.cs | 2 +- .../Errors/ExceptionHandlerMiddlewareTests.cs | 6 +++--- 16 files changed, 58 insertions(+), 59 deletions(-) diff --git a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs index 80f4583b..e7885c98 100644 --- a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs +++ b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs @@ -1,11 +1,9 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -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 ed72a60e..d4c87992 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.File; -using Ocelot.Configuration.Repository; +using Ocelot.Configuration.Repository; using Ocelot.Responses; namespace Ocelot.Configuration.Provider @@ -17,9 +15,9 @@ namespace Ocelot.Configuration.Provider _config = repo; } - public async Task> Get() + public Response Get() { - var repoConfig = await _config.Get(); + var repoConfig = _config.Get(); if (repoConfig.IsError) { @@ -29,4 +27,4 @@ namespace Ocelot.Configuration.Provider return new OkResponse(repoConfig.Data); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs index 7db04a09..a30e6ae2 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs @@ -5,6 +5,7 @@ using Consul; using Newtonsoft.Json; using Ocelot.Configuration.File; using Ocelot.Infrastructure.Consul; +using Ocelot.Logging; using Ocelot.Responses; using Ocelot.ServiceDiscovery.Configuration; @@ -15,16 +16,22 @@ namespace Ocelot.Configuration.Repository private readonly ConsulClient _consul; private const string OcelotConfiguration = "OcelotConfiguration"; private readonly Cache.IOcelotCache _cache; + private readonly IOcelotLogger _logger; public ConsulFileConfigurationRepository( - Cache.IOcelotCache cache, - ServiceProviderConfiguration serviceProviderConfig, - IConsulClientFactory factory) + Cache.IOcelotCache cache, + ServiceProviderConfiguration serviceProviderConfiguration, + IConsulClientFactory factory, + IOcelotLoggerFactory loggerFactory) { - var consulHost = string.IsNullOrEmpty(serviceProviderConfig?.Host) ? "localhost" : serviceProviderConfig?.Host; - var consulPort = serviceProviderConfig?.Port ?? 8500; - var config = new ConsulRegistryConfiguration(consulHost, consulPort, OcelotConfiguration, serviceProviderConfig?.Token); + _logger = loggerFactory.CreateLogger(); _cache = cache; + + var consulHost = string.IsNullOrEmpty(serviceProviderConfiguration?.Host) ? "localhost" : serviceProviderConfiguration?.Host; + var consulPort = serviceProviderConfiguration?.Port ?? 8500; + var token = serviceProviderConfiguration?.Token; + var config = new ConsulRegistryConfiguration(consulHost, consulPort, OcelotConfiguration, token); + _consul = factory.Get(config); } diff --git a/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs index 16b386a1..d4a7ab13 100644 --- a/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs @@ -1,12 +1,10 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Responses; +using Ocelot.Responses; namespace Ocelot.Configuration.Repository { public interface IOcelotConfigurationRepository { - Task> Get(); - Task AddOrReplace(IOcelotConfiguration ocelotConfiguration); + Response Get(); + Response AddOrReplace(IOcelotConfiguration ocelotConfiguration); } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs index 9ce50ba6..19985ca5 100644 --- a/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs @@ -12,19 +12,19 @@ namespace Ocelot.Configuration.Repository private IOcelotConfiguration _ocelotConfiguration; - public Task> Get() + public Response Get() { - return Task.FromResult>(new OkResponse(_ocelotConfiguration)); + return new OkResponse(_ocelotConfiguration); } - public Task AddOrReplace(IOcelotConfiguration ocelotConfiguration) + public Response AddOrReplace(IOcelotConfiguration ocelotConfiguration) { lock (LockObject) { _ocelotConfiguration = ocelotConfiguration; } - return Task.FromResult(new OkResponse()); + return new OkResponse(); } } } diff --git a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs index 38a7c1cb..27e88e7c 100644 --- a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs +++ b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs @@ -12,8 +12,10 @@ namespace Ocelot.Configuration.Setter private readonly IOcelotConfigurationCreator _configCreator; private readonly IFileConfigurationRepository _repo; - public FileConfigurationSetter(IOcelotConfigurationRepository configRepo, - IOcelotConfigurationCreator configCreator, IFileConfigurationRepository repo) + public FileConfigurationSetter( + IOcelotConfigurationRepository configRepo, + IOcelotConfigurationCreator configCreator, + IFileConfigurationRepository repo) { _configRepo = configRepo; _configCreator = configCreator; @@ -33,7 +35,7 @@ namespace Ocelot.Configuration.Setter if(!config.IsError) { - await _configRepo.AddOrReplace(config.Data); + _configRepo.AddOrReplace(config.Data); } return new ErrorResponse(config.Errors); diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 96e084c1..d3fa993c 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -1,7 +1,3 @@ -using Butterfly.Client.Tracing; -using Microsoft.Extensions.Options; -using Ocelot.Middleware.Multiplexer; - namespace Ocelot.DependencyInjection { using CacheManager.Core; @@ -22,7 +18,6 @@ namespace Ocelot.DependencyInjection 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; @@ -44,16 +39,16 @@ namespace Ocelot.DependencyInjection using System.Security.Cryptography.X509Certificates; using IdentityServer4.AccessTokenValidation; using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider; using Microsoft.Extensions.DependencyInjection.Extensions; - using System.Linq; using System.Net.Http; using Butterfly.Client.AspNetCore; using Ocelot.Infrastructure; using Ocelot.Infrastructure.Consul; + using Butterfly.Client.Tracing; + using Ocelot.Middleware.Multiplexer; public class OcelotBuilder : IOcelotBuilder { diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index 70e4a4e2..ad91a8d2 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -36,7 +36,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware var upstreamHost = context.HttpContext.Request.Headers["Host"]; - var configuration = await _configProvider.Get(); + var configuration = _configProvider.Get(); if (configuration.IsError) { diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs index 236e2e8d..46905502 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs @@ -1,10 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; using Ocelot.Configuration.Provider; -using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Infrastructure.Extensions; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; @@ -36,7 +33,7 @@ namespace Ocelot.Errors.Middleware { try { - await TrySetGlobalRequestId(context); + TrySetGlobalRequestId(context); Logger.LogDebug("ocelot pipeline started"); @@ -56,12 +53,12 @@ namespace Ocelot.Errors.Middleware Logger.LogDebug("ocelot pipeline finished"); } - private async Task TrySetGlobalRequestId(DownstreamContext context) + private void TrySetGlobalRequestId(DownstreamContext context) { //try and get the global request id and set it for logs... //should this basically be immutable per request...i guess it should! //first thing is get config - var configuration = await _provider.Get(); + var configuration = _provider.Get(); if(configuration.IsError) { diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 9d345dc2..7e061654 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using System.Diagnostics; using Microsoft.AspNetCore.Builder; @@ -89,7 +88,7 @@ { var deps = GetDependencies(builder); - var ocelotConfiguration = await deps.provider.Get(); + var ocelotConfiguration = deps.provider.Get(); if (ConfigurationNotSetUp(ocelotConfiguration)) { @@ -101,7 +100,7 @@ } } - return await GetOcelotConfigAndReturn(deps.provider); + return GetOcelotConfigAndReturn(deps.provider); } private static async Task SetConfig(IApplicationBuilder builder, IOptions fileConfiguration, IFileConfigurationSetter setter, IOcelotConfigurationProvider provider, IFileConfigurationRepository repo) @@ -137,9 +136,9 @@ return (fileConfiguration, setter, provider, repo); } - private static async Task GetOcelotConfigAndReturn(IOcelotConfigurationProvider provider) + private static IOcelotConfiguration GetOcelotConfigAndReturn(IOcelotConfigurationProvider provider) { - var ocelotConfiguration = await provider.Get(); + var ocelotConfiguration = provider.Get(); if(ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError) { @@ -187,7 +186,7 @@ return new ErrorResponse(ocelotConfig.Errors); } - config = await ocelotConfigurationRepository.AddOrReplace(ocelotConfig.Data); + config = ocelotConfigurationRepository.AddOrReplace(ocelotConfig.Data); if (config.IsError) { diff --git a/src/Ocelot/Raft/FilePeersProvider.cs b/src/Ocelot/Raft/FilePeersProvider.cs index d31dc2ca..20b3620d 100644 --- a/src/Ocelot/Raft/FilePeersProvider.cs +++ b/src/Ocelot/Raft/FilePeersProvider.cs @@ -28,8 +28,7 @@ namespace Ocelot.Raft _options = options; _peers = new List(); - //todo - sort out async nonsense.. - var config = _provider.Get().GetAwaiter().GetResult(); + var config = _provider.Get(); foreach (var item in _options.Value.Peers) { var httpClient = new HttpClient(); diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index 966b1097..59a9a45f 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -22,6 +22,12 @@ namespace Ocelot.ManualTest .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile("ocelot.json") + + //.AddOcelot(); + //load all the ocelot.xxx.json files that are not environments from asp.net core + //merge them into megaconfig + //save megaconfig to disk as ocelot.json + //then add to asp.net config stuff.. .AddEnvironmentVariables(); }) .ConfigureServices(s => { diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index cdc3f94d..6216e24b 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -49,7 +49,7 @@ namespace Ocelot.UnitTests.Configuration private void WhenIGetTheConfiguration() { - _getResult = _repo.Get().Result; + _getResult = _repo.Get(); } private void GivenThereIsASavedConfiguration() @@ -65,7 +65,7 @@ namespace Ocelot.UnitTests.Configuration private void WhenIAddOrReplaceTheConfig() { - _result = _repo.AddOrReplace(_config).Result; + _result = _repo.AddOrReplace(_config); } private void ThenNoErrorsAreReturned() diff --git a/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs index 982fe091..24000ea4 100644 --- a/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs +++ b/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs @@ -56,12 +56,12 @@ namespace Ocelot.UnitTests.Configuration { _configurationRepository .Setup(x => x.Get()) - .ReturnsAsync(config); + .Returns(config); } private void WhenIGetTheConfig() { - _result = _ocelotConfigurationProvider.Get().Result; + _result = _ocelotConfigurationProvider.Get(); } private void TheFollowingIsReturned(Response expected) diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index d3363b00..7361c862 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -79,7 +79,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _config = config; _provider .Setup(x => x.Get()) - .ReturnsAsync(new OkResponse(_config)); + .Returns(new OkResponse(_config)); } private void GivenTheDownStreamRouteFinderReturns(DownstreamRoute downstreamRoute) diff --git a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs index 6b52a1c5..2d905035 100644 --- a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs @@ -134,7 +134,7 @@ namespace Ocelot.UnitTests.Errors { var ex = new Exception("outer", new Exception("inner")); _provider - .Setup(x => x.Get()).ThrowsAsync(ex); + .Setup(x => x.Get()).Throws(ex); } private void ThenAnExceptionIsThrown() @@ -146,7 +146,7 @@ namespace Ocelot.UnitTests.Errors { var response = new Responses.ErrorResponse(new FakeError()); _provider - .Setup(x => x.Get()).ReturnsAsync(response); + .Setup(x => x.Get()).Returns(response); } private void TheRequestIdIsSet(string key, string value) @@ -158,7 +158,7 @@ namespace Ocelot.UnitTests.Errors { var response = new Responses.OkResponse(config); _provider - .Setup(x => x.Get()).ReturnsAsync(response); + .Setup(x => x.Get()).Returns(response); } private void GivenAnExceptionWillNotBeThrownDownstream() From fe5662f9545f9cb49b50abe417c28c696f96c064 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Fri, 13 Apr 2018 22:47:50 +0100 Subject: [PATCH 33/50] refactoring to consolidate configuration code --- ...cs => FileInternalConfigurationCreator.cs} | 460 +++++++++--------- .../Creator/IInternalConfigurationCreator.cs | 11 + .../Creator/IOcelotConfigurationCreator.cs | 11 - .../IIdentityServerConfiguration.cs | 30 +- ...iguration.cs => IInternalConfiguration.cs} | 24 +- .../IdentityServerConfiguration.cs | 62 ++- ...figuration.cs => InternalConfiguration.cs} | 40 +- .../Provider/IOcelotConfigurationProvider.cs | 9 - .../Provider/OcelotConfigurationProvider.cs | 30 -- .../ConsulFileConfigurationRepository.cs | 42 +- .../IInternalConfigurationRepository.cs | 10 + .../IOcelotConfigurationRepository.cs | 10 - ...InMemoryInternalConfigurationRepository.cs | 30 ++ .../InMemoryOcelotConfigurationRepository.cs | 30 -- .../Setter/FileConfigurationSetter.cs | 8 +- .../DependencyInjection/OcelotBuilder.cs | 28 +- .../Finder/DownstreamRouteFinder.cs | 2 +- .../Finder/IDownstreamRouteFinder.cs | 2 +- .../DownstreamRouteFinderMiddleware.cs | 9 +- .../Middleware/ExceptionHandlerMiddleware.cs | 12 +- .../Middleware/OcelotMiddlewareExtensions.cs | 160 +++--- src/Ocelot/Raft/FilePeersProvider.cs | 11 +- src/Ocelot/Raft/HttpPeer.cs | 4 +- .../ConfigurationInConsulTests.cs | 6 +- .../FileConfigurationCreatorTests.cs | 8 +- .../FileConfigurationSetterTests.cs | 18 +- .../InMemoryConfigurationRepositoryTests.cs | 14 +- .../OcelotConfigurationProviderTests.cs | 80 --- .../DependencyInjection/OcelotBuilderTests.cs | 4 +- .../DownstreamRouteFinderMiddlewareTests.cs | 31 +- .../DownstreamRouteFinderTests.cs | 4 +- .../Errors/ExceptionHandlerMiddlewareTests.cs | 28 +- 32 files changed, 552 insertions(+), 676 deletions(-) rename src/Ocelot/Configuration/Creator/{FileOcelotConfigurationCreator.cs => FileInternalConfigurationCreator.cs} (91%) create mode 100644 src/Ocelot/Configuration/Creator/IInternalConfigurationCreator.cs delete mode 100644 src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs rename src/Ocelot/Configuration/{Provider => }/IIdentityServerConfiguration.cs (67%) rename src/Ocelot/Configuration/{IOcelotConfiguration.cs => IInternalConfiguration.cs} (83%) rename src/Ocelot/Configuration/{Provider => }/IdentityServerConfiguration.cs (66%) rename src/Ocelot/Configuration/{OcelotConfiguration.cs => InternalConfiguration.cs} (67%) delete mode 100644 src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs delete mode 100644 src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs create mode 100644 src/Ocelot/Configuration/Repository/IInternalConfigurationRepository.cs delete mode 100644 src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs create mode 100644 src/Ocelot/Configuration/Repository/InMemoryInternalConfigurationRepository.cs delete mode 100644 src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs delete mode 100644 test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs similarity index 91% rename from src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs rename to src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs index 821c0ed7..46590f67 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs @@ -1,230 +1,230 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.Options; -using Ocelot.Cache; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Validator; -using Ocelot.DependencyInjection; -using Ocelot.Logging; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Creator -{ - /// - /// Register as singleton - /// - public class FileOcelotConfigurationCreator : IOcelotConfigurationCreator - { - private readonly IOptions _options; - private readonly IConfigurationValidator _configurationValidator; - private readonly IOcelotLogger _logger; - private readonly IClaimsToThingCreator _claimsToThingCreator; - private readonly IAuthenticationOptionsCreator _authOptionsCreator; - private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; - private readonly IRequestIdKeyCreator _requestIdKeyCreator; - private readonly IServiceProviderConfigurationCreator _serviceProviderConfigCreator; - private readonly IQoSOptionsCreator _qosOptionsCreator; - private readonly IReRouteOptionsCreator _fileReRouteOptionsCreator; - private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator; - private readonly IRegionCreator _regionCreator; - private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; - private readonly IAdministrationPath _adminPath; - private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator; - private readonly IDownstreamAddressesCreator _downstreamAddressesCreator; - - public FileOcelotConfigurationCreator( - IOptions options, - IConfigurationValidator configurationValidator, - IOcelotLoggerFactory loggerFactory, - IClaimsToThingCreator claimsToThingCreator, - IAuthenticationOptionsCreator authOptionsCreator, - IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator, - IRequestIdKeyCreator requestIdKeyCreator, - IServiceProviderConfigurationCreator serviceProviderConfigCreator, - IQoSOptionsCreator qosOptionsCreator, - IReRouteOptionsCreator fileReRouteOptionsCreator, - IRateLimitOptionsCreator rateLimitOptionsCreator, - IRegionCreator regionCreator, - IHttpHandlerOptionsCreator httpHandlerOptionsCreator, - IAdministrationPath adminPath, - IHeaderFindAndReplaceCreator headerFAndRCreator, - IDownstreamAddressesCreator downstreamAddressesCreator - ) - { - _downstreamAddressesCreator = downstreamAddressesCreator; - _headerFAndRCreator = headerFAndRCreator; - _adminPath = adminPath; - _regionCreator = regionCreator; - _rateLimitOptionsCreator = rateLimitOptionsCreator; - _requestIdKeyCreator = requestIdKeyCreator; - _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; - _authOptionsCreator = authOptionsCreator; - _options = options; - _configurationValidator = configurationValidator; - _logger = loggerFactory.CreateLogger(); - _claimsToThingCreator = claimsToThingCreator; - _serviceProviderConfigCreator = serviceProviderConfigCreator; - _qosOptionsCreator = qosOptionsCreator; - _fileReRouteOptionsCreator = fileReRouteOptionsCreator; - _httpHandlerOptionsCreator = httpHandlerOptionsCreator; - } - - public async Task> Create(FileConfiguration fileConfiguration) - { - var config = await SetUpConfiguration(fileConfiguration); - return config; - } - - private async Task> SetUpConfiguration(FileConfiguration fileConfiguration) - { - var response = await _configurationValidator.IsValid(fileConfiguration); - - if (response.Data.IsError) - { - return new ErrorResponse(response.Data.Errors); - } - - var reRoutes = new List(); - - foreach (var reRoute in fileConfiguration.ReRoutes) - { - var downstreamReRoute = SetUpDownstreamReRoute(reRoute, fileConfiguration.GlobalConfiguration); - - var ocelotReRoute = SetUpReRoute(reRoute, downstreamReRoute); - - reRoutes.Add(ocelotReRoute); - } - - foreach (var aggregate in fileConfiguration.Aggregates) - { - var ocelotReRoute = SetUpAggregateReRoute(reRoutes, aggregate, fileConfiguration.GlobalConfiguration); - reRoutes.Add(ocelotReRoute); - } - - var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration); - - var config = new OcelotConfiguration(reRoutes, _adminPath.Path, serviceProviderConfiguration, fileConfiguration.GlobalConfiguration.RequestIdKey); - - return new OkResponse(config); - } - - public ReRoute SetUpAggregateReRoute(List reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) - { - var applicableReRoutes = reRoutes - .SelectMany(x => x.DownstreamReRoute) - .Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key)) - .ToList(); - - if(applicableReRoutes.Count != aggregateReRoute.ReRouteKeys.Count) - { - //todo - log or throw or return error whatever? - } - - //make another re route out of these - var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(aggregateReRoute); - - var reRoute = new ReRouteBuilder() - .WithUpstreamPathTemplate(aggregateReRoute.UpstreamPathTemplate) - .WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod) - .WithUpstreamTemplatePattern(upstreamTemplatePattern) - .WithDownstreamReRoutes(applicableReRoutes) - .WithUpstreamHost(aggregateReRoute.UpstreamHost) - .WithAggregator(aggregateReRoute.Aggregator) - .Build(); - - return reRoute; - } - - private ReRoute SetUpReRoute(FileReRoute fileReRoute, DownstreamReRoute downstreamReRoutes) - { - var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); - - var reRoute = new ReRouteBuilder() - .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) - .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) - .WithUpstreamTemplatePattern(upstreamTemplatePattern) - .WithDownstreamReRoute(downstreamReRoutes) - .WithUpstreamHost(fileReRoute.UpstreamHost) - .Build(); - - return reRoute; - } - - private DownstreamReRoute SetUpDownstreamReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) - { - var fileReRouteOptions = _fileReRouteOptionsCreator.Create(fileReRoute); - - var requestIdKey = _requestIdKeyCreator.Create(fileReRoute, globalConfiguration); - - var reRouteKey = CreateReRouteKey(fileReRoute); - - var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); - - var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute); - - var claimsToHeaders = _claimsToThingCreator.Create(fileReRoute.AddHeadersToRequest); - - var claimsToClaims = _claimsToThingCreator.Create(fileReRoute.AddClaimsToRequest); - - var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest); - - var qosOptions = _qosOptionsCreator.Create(fileReRoute); - - var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting); - - var region = _regionCreator.Create(fileReRoute); - - var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileReRoute); - - var hAndRs = _headerFAndRCreator.Create(fileReRoute); - - var downstreamAddresses = _downstreamAddressesCreator.Create(fileReRoute); - - var reRoute = new DownstreamReRouteBuilder() - .WithKey(fileReRoute.Key) - .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) - .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) - .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) - .WithUpstreamTemplatePattern(upstreamTemplatePattern) - .WithIsAuthenticated(fileReRouteOptions.IsAuthenticated) - .WithAuthenticationOptions(authOptionsForRoute) - .WithClaimsToHeaders(claimsToHeaders) - .WithClaimsToClaims(claimsToClaims) - .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) - .WithIsAuthorised(fileReRouteOptions.IsAuthorised) - .WithClaimsToQueries(claimsToQueries) - .WithRequestIdKey(requestIdKey) - .WithIsCached(fileReRouteOptions.IsCached) - .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region)) - .WithDownstreamScheme(fileReRoute.DownstreamScheme) - .WithLoadBalancer(fileReRoute.LoadBalancer) - .WithDownstreamAddresses(downstreamAddresses) - .WithReRouteKey(reRouteKey) - .WithIsQos(fileReRouteOptions.IsQos) - .WithQosOptions(qosOptions) - .WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting) - .WithRateLimitOptions(rateLimitOption) - .WithHttpHandlerOptions(httpHandlerOptions) - .WithServiceName(fileReRoute.ServiceName) - .WithUseServiceDiscovery(fileReRoute.UseServiceDiscovery) - .WithUpstreamHeaderFindAndReplace(hAndRs.Upstream) - .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) - .WithUpstreamHost(fileReRoute.UpstreamHost) - .WithDelegatingHandlers(fileReRoute.DelegatingHandlers) - .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) - .Build(); - - return reRoute; - } - - private string CreateReRouteKey(FileReRoute fileReRoute) - { - //note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain - var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}|{string.Join(",", fileReRoute.UpstreamHttpMethod)}"; - return loadBalancerKey; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Ocelot.Cache; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Validator; +using Ocelot.DependencyInjection; +using Ocelot.Logging; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Creator +{ + /// + /// Register as singleton + /// + public class FileInternalConfigurationCreator : IInternalConfigurationCreator + { + private readonly IOptions _options; + private readonly IConfigurationValidator _configurationValidator; + private readonly IOcelotLogger _logger; + private readonly IClaimsToThingCreator _claimsToThingCreator; + private readonly IAuthenticationOptionsCreator _authOptionsCreator; + private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; + private readonly IRequestIdKeyCreator _requestIdKeyCreator; + private readonly IServiceProviderConfigurationCreator _serviceProviderConfigCreator; + private readonly IQoSOptionsCreator _qosOptionsCreator; + private readonly IReRouteOptionsCreator _fileReRouteOptionsCreator; + private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator; + private readonly IRegionCreator _regionCreator; + private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; + private readonly IAdministrationPath _adminPath; + private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator; + private readonly IDownstreamAddressesCreator _downstreamAddressesCreator; + + public FileInternalConfigurationCreator( + IOptions options, + IConfigurationValidator configurationValidator, + IOcelotLoggerFactory loggerFactory, + IClaimsToThingCreator claimsToThingCreator, + IAuthenticationOptionsCreator authOptionsCreator, + IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator, + IRequestIdKeyCreator requestIdKeyCreator, + IServiceProviderConfigurationCreator serviceProviderConfigCreator, + IQoSOptionsCreator qosOptionsCreator, + IReRouteOptionsCreator fileReRouteOptionsCreator, + IRateLimitOptionsCreator rateLimitOptionsCreator, + IRegionCreator regionCreator, + IHttpHandlerOptionsCreator httpHandlerOptionsCreator, + IAdministrationPath adminPath, + IHeaderFindAndReplaceCreator headerFAndRCreator, + IDownstreamAddressesCreator downstreamAddressesCreator + ) + { + _downstreamAddressesCreator = downstreamAddressesCreator; + _headerFAndRCreator = headerFAndRCreator; + _adminPath = adminPath; + _regionCreator = regionCreator; + _rateLimitOptionsCreator = rateLimitOptionsCreator; + _requestIdKeyCreator = requestIdKeyCreator; + _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; + _authOptionsCreator = authOptionsCreator; + _options = options; + _configurationValidator = configurationValidator; + _logger = loggerFactory.CreateLogger(); + _claimsToThingCreator = claimsToThingCreator; + _serviceProviderConfigCreator = serviceProviderConfigCreator; + _qosOptionsCreator = qosOptionsCreator; + _fileReRouteOptionsCreator = fileReRouteOptionsCreator; + _httpHandlerOptionsCreator = httpHandlerOptionsCreator; + } + + public async Task> Create(FileConfiguration fileConfiguration) + { + var config = await SetUpConfiguration(fileConfiguration); + return config; + } + + private async Task> SetUpConfiguration(FileConfiguration fileConfiguration) + { + var response = await _configurationValidator.IsValid(fileConfiguration); + + if (response.Data.IsError) + { + return new ErrorResponse(response.Data.Errors); + } + + var reRoutes = new List(); + + foreach (var reRoute in fileConfiguration.ReRoutes) + { + var downstreamReRoute = SetUpDownstreamReRoute(reRoute, fileConfiguration.GlobalConfiguration); + + var ocelotReRoute = SetUpReRoute(reRoute, downstreamReRoute); + + reRoutes.Add(ocelotReRoute); + } + + foreach (var aggregate in fileConfiguration.Aggregates) + { + var ocelotReRoute = SetUpAggregateReRoute(reRoutes, aggregate, fileConfiguration.GlobalConfiguration); + reRoutes.Add(ocelotReRoute); + } + + var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration); + + var config = new InternalConfiguration(reRoutes, _adminPath.Path, serviceProviderConfiguration, fileConfiguration.GlobalConfiguration.RequestIdKey); + + return new OkResponse(config); + } + + public ReRoute SetUpAggregateReRoute(List reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) + { + var applicableReRoutes = reRoutes + .SelectMany(x => x.DownstreamReRoute) + .Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key)) + .ToList(); + + if(applicableReRoutes.Count != aggregateReRoute.ReRouteKeys.Count) + { + //todo - log or throw or return error whatever? + } + + //make another re route out of these + var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(aggregateReRoute); + + var reRoute = new ReRouteBuilder() + .WithUpstreamPathTemplate(aggregateReRoute.UpstreamPathTemplate) + .WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod) + .WithUpstreamTemplatePattern(upstreamTemplatePattern) + .WithDownstreamReRoutes(applicableReRoutes) + .WithUpstreamHost(aggregateReRoute.UpstreamHost) + .WithAggregator(aggregateReRoute.Aggregator) + .Build(); + + return reRoute; + } + + private ReRoute SetUpReRoute(FileReRoute fileReRoute, DownstreamReRoute downstreamReRoutes) + { + var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); + + var reRoute = new ReRouteBuilder() + .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) + .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) + .WithUpstreamTemplatePattern(upstreamTemplatePattern) + .WithDownstreamReRoute(downstreamReRoutes) + .WithUpstreamHost(fileReRoute.UpstreamHost) + .Build(); + + return reRoute; + } + + private DownstreamReRoute SetUpDownstreamReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) + { + var fileReRouteOptions = _fileReRouteOptionsCreator.Create(fileReRoute); + + var requestIdKey = _requestIdKeyCreator.Create(fileReRoute, globalConfiguration); + + var reRouteKey = CreateReRouteKey(fileReRoute); + + var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); + + var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute); + + var claimsToHeaders = _claimsToThingCreator.Create(fileReRoute.AddHeadersToRequest); + + var claimsToClaims = _claimsToThingCreator.Create(fileReRoute.AddClaimsToRequest); + + var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest); + + var qosOptions = _qosOptionsCreator.Create(fileReRoute); + + var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting); + + var region = _regionCreator.Create(fileReRoute); + + var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileReRoute); + + var hAndRs = _headerFAndRCreator.Create(fileReRoute); + + var downstreamAddresses = _downstreamAddressesCreator.Create(fileReRoute); + + var reRoute = new DownstreamReRouteBuilder() + .WithKey(fileReRoute.Key) + .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) + .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) + .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) + .WithUpstreamTemplatePattern(upstreamTemplatePattern) + .WithIsAuthenticated(fileReRouteOptions.IsAuthenticated) + .WithAuthenticationOptions(authOptionsForRoute) + .WithClaimsToHeaders(claimsToHeaders) + .WithClaimsToClaims(claimsToClaims) + .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) + .WithIsAuthorised(fileReRouteOptions.IsAuthorised) + .WithClaimsToQueries(claimsToQueries) + .WithRequestIdKey(requestIdKey) + .WithIsCached(fileReRouteOptions.IsCached) + .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region)) + .WithDownstreamScheme(fileReRoute.DownstreamScheme) + .WithLoadBalancer(fileReRoute.LoadBalancer) + .WithDownstreamAddresses(downstreamAddresses) + .WithReRouteKey(reRouteKey) + .WithIsQos(fileReRouteOptions.IsQos) + .WithQosOptions(qosOptions) + .WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting) + .WithRateLimitOptions(rateLimitOption) + .WithHttpHandlerOptions(httpHandlerOptions) + .WithServiceName(fileReRoute.ServiceName) + .WithUseServiceDiscovery(fileReRoute.UseServiceDiscovery) + .WithUpstreamHeaderFindAndReplace(hAndRs.Upstream) + .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) + .WithUpstreamHost(fileReRoute.UpstreamHost) + .WithDelegatingHandlers(fileReRoute.DelegatingHandlers) + .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) + .Build(); + + return reRoute; + } + + private string CreateReRouteKey(FileReRoute fileReRoute) + { + //note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain + var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}|{string.Join(",", fileReRoute.UpstreamHttpMethod)}"; + return loadBalancerKey; + } + } +} diff --git a/src/Ocelot/Configuration/Creator/IInternalConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IInternalConfigurationCreator.cs new file mode 100644 index 00000000..a0f3cb42 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IInternalConfigurationCreator.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Creator +{ + public interface IInternalConfigurationCreator + { + Task> Create(FileConfiguration fileConfiguration); + } +} diff --git a/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs deleted file mode 100644 index 4b431701..00000000 --- a/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Creator -{ - public interface IOcelotConfigurationCreator - { - Task> Create(FileConfiguration fileConfiguration); - } -} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs b/src/Ocelot/Configuration/IIdentityServerConfiguration.cs similarity index 67% rename from src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs rename to src/Ocelot/Configuration/IIdentityServerConfiguration.cs index 8a76eb9f..0eb70347 100644 --- a/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs +++ b/src/Ocelot/Configuration/IIdentityServerConfiguration.cs @@ -1,16 +1,14 @@ -using System.Collections.Generic; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; - -namespace Ocelot.Configuration.Provider -{ - public interface IIdentityServerConfiguration - { - string ApiName { get; } - string ApiSecret { get; } - bool RequireHttps { get; } - List AllowedScopes { get; } - string CredentialsSigningCertificateLocation { get; } - string CredentialsSigningCertificatePassword { get; } - } -} \ No newline at end of file +namespace Ocelot.Configuration +{ + using System.Collections.Generic; + + public interface IIdentityServerConfiguration + { + string ApiName { get; } + string ApiSecret { get; } + bool RequireHttps { get; } + List AllowedScopes { get; } + string CredentialsSigningCertificateLocation { get; } + string CredentialsSigningCertificatePassword { get; } + } +} diff --git a/src/Ocelot/Configuration/IOcelotConfiguration.cs b/src/Ocelot/Configuration/IInternalConfiguration.cs similarity index 83% rename from src/Ocelot/Configuration/IOcelotConfiguration.cs rename to src/Ocelot/Configuration/IInternalConfiguration.cs index 2353cb85..c1781c48 100644 --- a/src/Ocelot/Configuration/IOcelotConfiguration.cs +++ b/src/Ocelot/Configuration/IInternalConfiguration.cs @@ -1,12 +1,12 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration -{ - public interface IOcelotConfiguration - { - List ReRoutes { get; } - string AdministrationPath {get;} - ServiceProviderConfiguration ServiceProviderConfiguration {get;} - string RequestId {get;} - } -} +using System.Collections.Generic; + +namespace Ocelot.Configuration +{ + public interface IInternalConfiguration + { + List ReRoutes { get; } + string AdministrationPath {get;} + ServiceProviderConfiguration ServiceProviderConfiguration {get;} + string RequestId {get;} + } +} diff --git a/src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs b/src/Ocelot/Configuration/IdentityServerConfiguration.cs similarity index 66% rename from src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs rename to src/Ocelot/Configuration/IdentityServerConfiguration.cs index 795e6994..b8b00ea2 100644 --- a/src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs +++ b/src/Ocelot/Configuration/IdentityServerConfiguration.cs @@ -1,32 +1,30 @@ -using System.Collections.Generic; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; - -namespace Ocelot.Configuration.Provider -{ - public class IdentityServerConfiguration : IIdentityServerConfiguration - { - public IdentityServerConfiguration( - string apiName, - bool requireHttps, - string apiSecret, - List allowedScopes, - string credentialsSigningCertificateLocation, - string credentialsSigningCertificatePassword) - { - ApiName = apiName; - RequireHttps = requireHttps; - ApiSecret = apiSecret; - AllowedScopes = allowedScopes; - CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation; - CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword; - } - - public string ApiName { get; private set; } - public bool RequireHttps { get; private set; } - public List AllowedScopes { get; private set; } - public string ApiSecret { get; private set; } - public string CredentialsSigningCertificateLocation { get; private set; } - public string CredentialsSigningCertificatePassword { get; private set; } - } -} \ No newline at end of file +namespace Ocelot.Configuration +{ + using System.Collections.Generic; + + public class IdentityServerConfiguration : IIdentityServerConfiguration + { + public IdentityServerConfiguration( + string apiName, + bool requireHttps, + string apiSecret, + List allowedScopes, + string credentialsSigningCertificateLocation, + string credentialsSigningCertificatePassword) + { + ApiName = apiName; + RequireHttps = requireHttps; + ApiSecret = apiSecret; + AllowedScopes = allowedScopes; + CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation; + CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword; + } + + public string ApiName { get; } + public bool RequireHttps { get; } + public List AllowedScopes { get; } + public string ApiSecret { get; } + public string CredentialsSigningCertificateLocation { get; } + public string CredentialsSigningCertificatePassword { get; } + } +} diff --git a/src/Ocelot/Configuration/OcelotConfiguration.cs b/src/Ocelot/Configuration/InternalConfiguration.cs similarity index 67% rename from src/Ocelot/Configuration/OcelotConfiguration.cs rename to src/Ocelot/Configuration/InternalConfiguration.cs index 1ab73b87..429bb9c0 100644 --- a/src/Ocelot/Configuration/OcelotConfiguration.cs +++ b/src/Ocelot/Configuration/InternalConfiguration.cs @@ -1,20 +1,20 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration -{ - public class OcelotConfiguration : IOcelotConfiguration - { - public OcelotConfiguration(List reRoutes, string administrationPath, ServiceProviderConfiguration serviceProviderConfiguration, string requestId) - { - ReRoutes = reRoutes; - AdministrationPath = administrationPath; - ServiceProviderConfiguration = serviceProviderConfiguration; - RequestId = requestId; - } - - public List ReRoutes { get; } - public string AdministrationPath {get;} - public ServiceProviderConfiguration ServiceProviderConfiguration {get;} - public string RequestId {get;} - } -} +using System.Collections.Generic; + +namespace Ocelot.Configuration +{ + public class InternalConfiguration : IInternalConfiguration + { + public InternalConfiguration(List reRoutes, string administrationPath, ServiceProviderConfiguration serviceProviderConfiguration, string requestId) + { + ReRoutes = reRoutes; + AdministrationPath = administrationPath; + ServiceProviderConfiguration = serviceProviderConfiguration; + RequestId = requestId; + } + + public List ReRoutes { get; } + public string AdministrationPath {get;} + public ServiceProviderConfiguration ServiceProviderConfiguration {get;} + public string RequestId {get;} + } +} diff --git a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs deleted file mode 100644 index e7885c98..00000000 --- a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Ocelot.Responses; - -namespace Ocelot.Configuration.Provider -{ - public interface IOcelotConfigurationProvider - { - Response Get(); - } -} diff --git a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs deleted file mode 100644 index d4c87992..00000000 --- a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Ocelot.Configuration.Repository; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Provider -{ - /// - /// Register as singleton - /// - public class OcelotConfigurationProvider : IOcelotConfigurationProvider - { - private readonly IOcelotConfigurationRepository _config; - - public OcelotConfigurationProvider(IOcelotConfigurationRepository repo) - { - _config = repo; - } - - public Response Get() - { - var repoConfig = _config.Get(); - - if (repoConfig.IsError) - { - return new ErrorResponse(repoConfig.Errors); - } - - return new OkResponse(repoConfig.Data); - } - } -} diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs index a30e6ae2..21216168 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs @@ -1,35 +1,45 @@ -using System; -using System.Text; -using System.Threading.Tasks; -using Consul; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.Infrastructure.Consul; -using Ocelot.Logging; -using Ocelot.Responses; -using Ocelot.ServiceDiscovery.Configuration; - namespace Ocelot.Configuration.Repository { + using System; + using System.Text; + using System.Threading.Tasks; + using Consul; + using Newtonsoft.Json; + using Ocelot.Configuration.File; + using Ocelot.Infrastructure.Consul; + using Ocelot.Logging; + using Ocelot.Responses; + using Ocelot.ServiceDiscovery.Configuration; + public class ConsulFileConfigurationRepository : IFileConfigurationRepository { private readonly ConsulClient _consul; - private const string OcelotConfiguration = "OcelotConfiguration"; + private const string OcelotConfiguration = "InternalConfiguration"; private readonly Cache.IOcelotCache _cache; private readonly IOcelotLogger _logger; public ConsulFileConfigurationRepository( Cache.IOcelotCache cache, - ServiceProviderConfiguration serviceProviderConfiguration, + IInternalConfigurationRepository repo, IConsulClientFactory factory, IOcelotLoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger(); _cache = cache; - var consulHost = string.IsNullOrEmpty(serviceProviderConfiguration?.Host) ? "localhost" : serviceProviderConfiguration?.Host; - var consulPort = serviceProviderConfiguration?.Port ?? 8500; - var token = serviceProviderConfiguration?.Token; + var internalConfig = repo.Get(); + + var consulHost = "localhost"; + var consulPort = 8500; + string token = null; + + if (!internalConfig.IsError) + { + consulHost = string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration?.Host) ? consulHost : internalConfig.Data.ServiceProviderConfiguration?.Host; + consulPort = internalConfig.Data.ServiceProviderConfiguration?.Port ?? consulPort; + token = internalConfig.Data.ServiceProviderConfiguration?.Token; + } + var config = new ConsulRegistryConfiguration(consulHost, consulPort, OcelotConfiguration, token); _consul = factory.Get(config); diff --git a/src/Ocelot/Configuration/Repository/IInternalConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/IInternalConfigurationRepository.cs new file mode 100644 index 00000000..5db4adb5 --- /dev/null +++ b/src/Ocelot/Configuration/Repository/IInternalConfigurationRepository.cs @@ -0,0 +1,10 @@ +using Ocelot.Responses; + +namespace Ocelot.Configuration.Repository +{ + public interface IInternalConfigurationRepository + { + Response Get(); + Response AddOrReplace(IInternalConfiguration internalConfiguration); + } +} diff --git a/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs deleted file mode 100644 index d4a7ab13..00000000 --- a/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Ocelot.Responses; - -namespace Ocelot.Configuration.Repository -{ - public interface IOcelotConfigurationRepository - { - Response Get(); - Response AddOrReplace(IOcelotConfiguration ocelotConfiguration); - } -} diff --git a/src/Ocelot/Configuration/Repository/InMemoryInternalConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/InMemoryInternalConfigurationRepository.cs new file mode 100644 index 00000000..fcb3baa9 --- /dev/null +++ b/src/Ocelot/Configuration/Repository/InMemoryInternalConfigurationRepository.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Repository +{ + /// + /// Register as singleton + /// + public class InMemoryInternalConfigurationRepository : IInternalConfigurationRepository + { + private static readonly object LockObject = new object(); + + private IInternalConfiguration _internalConfiguration; + + public Response Get() + { + return new OkResponse(_internalConfiguration); + } + + public Response AddOrReplace(IInternalConfiguration internalConfiguration) + { + lock (LockObject) + { + _internalConfiguration = internalConfiguration; + } + + return new OkResponse(); + } + } +} diff --git a/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs deleted file mode 100644 index 19985ca5..00000000 --- a/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Threading.Tasks; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Repository -{ - /// - /// Register as singleton - /// - public class InMemoryOcelotConfigurationRepository : IOcelotConfigurationRepository - { - private static readonly object LockObject = new object(); - - private IOcelotConfiguration _ocelotConfiguration; - - public Response Get() - { - return new OkResponse(_ocelotConfiguration); - } - - public Response AddOrReplace(IOcelotConfiguration ocelotConfiguration) - { - lock (LockObject) - { - _ocelotConfiguration = ocelotConfiguration; - } - - return new OkResponse(); - } - } -} diff --git a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs index 27e88e7c..ba7b5ce9 100644 --- a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs +++ b/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs @@ -8,13 +8,13 @@ namespace Ocelot.Configuration.Setter { public class FileConfigurationSetter : IFileConfigurationSetter { - private readonly IOcelotConfigurationRepository _configRepo; - private readonly IOcelotConfigurationCreator _configCreator; + private readonly IInternalConfigurationRepository _configRepo; + private readonly IInternalConfigurationCreator _configCreator; private readonly IFileConfigurationRepository _repo; public FileConfigurationSetter( - IOcelotConfigurationRepository configRepo, - IOcelotConfigurationCreator configCreator, + IInternalConfigurationRepository configRepo, + IInternalConfigurationCreator configCreator, IFileConfigurationRepository repo) { _configRepo = configRepo; diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index d3fa993c..cd70b581 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -73,8 +73,8 @@ namespace Ocelot.DependencyInjection _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); @@ -96,7 +96,6 @@ namespace Ocelot.DependencyInjection _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); - _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); @@ -247,17 +246,6 @@ namespace Ocelot.DependencyInjection public IOcelotBuilder AddStoreOcelotConfigurationInConsul() { - var serviceDiscoveryPort = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Port", 0); - var serviceDiscoveryHost = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Host", string.Empty); - var serviceDiscoveryToken = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Token", string.Empty); - - var config = new ServiceProviderConfigurationBuilder() - .WithPort(serviceDiscoveryPort) - .WithHost(serviceDiscoveryHost) - .WithToken(serviceDiscoveryToken) - .Build(); - - _services.AddSingleton(config); _services.AddSingleton(); _services.AddSingleton(); return this; @@ -273,12 +261,12 @@ namespace Ocelot.DependencyInjection _services.AddSingleton>(cacheManagerOutputCache); _services.AddSingleton>(ocelotOutputCacheManager); - var ocelotConfigCacheManagerOutputCache = CacheFactory.Build("OcelotConfigurationCache", settings); - var ocelotConfigCacheManager = new OcelotCacheManagerCache(ocelotConfigCacheManagerOutputCache); - _services.RemoveAll(typeof(ICacheManager)); - _services.RemoveAll(typeof(IOcelotCache)); - _services.AddSingleton>(ocelotConfigCacheManagerOutputCache); - _services.AddSingleton>(ocelotConfigCacheManager); + var ocelotConfigCacheManagerOutputCache = CacheFactory.Build("OcelotConfigurationCache", settings); + var ocelotConfigCacheManager = new OcelotCacheManagerCache(ocelotConfigCacheManagerOutputCache); + _services.RemoveAll(typeof(ICacheManager)); + _services.RemoveAll(typeof(IOcelotCache)); + _services.AddSingleton>(ocelotConfigCacheManagerOutputCache); + _services.AddSingleton>(ocelotConfigCacheManager); var fileConfigCacheManagerOutputCache = CacheFactory.Build("FileConfigurationCache", settings); var fileConfigCacheManager = new OcelotCacheManagerCache(fileConfigCacheManagerOutputCache); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 35b6d92d..df70b0b3 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -18,7 +18,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder _placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; } - public Response FindDownstreamRoute(string path, string httpMethod, IOcelotConfiguration configuration, string upstreamHost) + public Response FindDownstreamRoute(string path, string httpMethod, IInternalConfiguration configuration, string upstreamHost) { var downstreamRoutes = new List(); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs index 81901231..1d8fae15 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs @@ -6,6 +6,6 @@ namespace Ocelot.DownstreamRouteFinder.Finder { public interface IDownstreamRouteFinder { - Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod, IOcelotConfiguration configuration, string upstreamHost); + Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost); } } diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index ad91a8d2..ab920fef 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using System.Linq; using Ocelot.Configuration; using Ocelot.Configuration.Provider; +using Ocelot.Configuration.Repository; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.Infrastructure.Extensions; using Ocelot.Logging; @@ -14,17 +15,17 @@ namespace Ocelot.DownstreamRouteFinder.Middleware { private readonly OcelotRequestDelegate _next; private readonly IDownstreamRouteFinder _downstreamRouteFinder; - private readonly IOcelotConfigurationProvider _configProvider; + private readonly IInternalConfigurationRepository _repo; private readonly IMultiplexer _multiplexer; public DownstreamRouteFinderMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, IDownstreamRouteFinder downstreamRouteFinder, - IOcelotConfigurationProvider configProvider, + IInternalConfigurationRepository repo, IMultiplexer multiplexer) :base(loggerFactory.CreateLogger()) { - _configProvider = configProvider; + _repo = repo; _multiplexer = multiplexer; _next = next; _downstreamRouteFinder = downstreamRouteFinder; @@ -36,7 +37,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware var upstreamHost = context.HttpContext.Request.Headers["Host"]; - var configuration = _configProvider.Get(); + var configuration = _repo.Get(); if (configuration.IsError) { diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs index 46905502..41b0b83d 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs @@ -1,7 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; -using Ocelot.Configuration.Provider; +using Ocelot.Configuration.Repository; using Ocelot.Infrastructure.Extensions; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; @@ -15,16 +15,16 @@ namespace Ocelot.Errors.Middleware public class ExceptionHandlerMiddleware : OcelotMiddleware { private readonly OcelotRequestDelegate _next; - private readonly IOcelotConfigurationProvider _provider; + private readonly IInternalConfigurationRepository _configRepo; private readonly IRequestScopedDataRepository _repo; public ExceptionHandlerMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IOcelotConfigurationProvider provider, + IOcelotLoggerFactory loggerFactory, + IInternalConfigurationRepository configRepo, IRequestScopedDataRepository repo) : base(loggerFactory.CreateLogger()) { - _provider = provider; + _configRepo = configRepo; _repo = repo; _next = next; } @@ -58,7 +58,7 @@ namespace Ocelot.Errors.Middleware //try and get the global request id and set it for logs... //should this basically be immutable per request...i guess it should! //first thing is get config - var configuration = _provider.Get(); + var configuration = _configRepo.Get(); if(configuration.IsError) { diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 7e061654..25d818c5 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -10,7 +10,6 @@ using Ocelot.Configuration; using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; - using Ocelot.Configuration.Provider; using Ocelot.Configuration.Repository; using Ocelot.Configuration.Setter; using Ocelot.Responses; @@ -84,63 +83,108 @@ node.Start(nodeId.Id); } - private static async Task CreateConfiguration(IApplicationBuilder builder) + private static async Task CreateConfiguration(IApplicationBuilder builder) { - var deps = GetDependencies(builder); + // make configuration from file system? + // earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this + var fileConfig = (IOptions)builder.ApplicationServices.GetService(typeof(IOptions)); + + // now create the config + var internalConfigCreator = (IInternalConfigurationCreator)builder.ApplicationServices.GetService(typeof(IInternalConfigurationCreator)); + var internalConfig = await internalConfigCreator.Create(fileConfig.Value); - var ocelotConfiguration = deps.provider.Get(); + // now save it in memory + var internalConfigRepo = (IInternalConfigurationRepository)builder.ApplicationServices.GetService(typeof(IInternalConfigurationRepository)); + internalConfigRepo.AddOrReplace(internalConfig.Data); - if (ConfigurationNotSetUp(ocelotConfiguration)) + var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); + + var fileConfigRepo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository)); + + if (UsingConsul(fileConfigRepo)) { - var response = await SetConfig(builder, deps.fileConfiguration, deps.setter, deps.provider, deps.repo); - - if (UnableToSetConfig(response)) + await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo); + } + else + { + await SetFileConfig(fileConfigSetter, fileConfig); + } + + return GetOcelotConfigAndReturn(internalConfigRepo); + } + + private static async Task SetFileConfigInConsul(IApplicationBuilder builder, + IFileConfigurationRepository fileConfigRepo, IOptions fileConfig, + IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo) + { + // get the config from consul. + var fileConfigFromConsul = await fileConfigRepo.Get(); + + if (IsError(fileConfigFromConsul)) + { + ThrowToStopOcelotStarting(fileConfigFromConsul); + } + else if (ConfigNotStoredInConsul(fileConfigFromConsul)) + { + //there was no config in consul set the file in config in consul + await fileConfigRepo.Set(fileConfig.Value); + } + else + { + // create the internal config from consul data + var internalConfig = await internalConfigCreator.Create(fileConfigFromConsul.Data); + + if (IsError(internalConfig)) { - ThrowToStopOcelotStarting(response); + ThrowToStopOcelotStarting(internalConfig); + } + else + { + // add the internal config to the internal repo + var response = internalConfigRepo.AddOrReplace(internalConfig.Data); + + if (IsError(response)) + { + ThrowToStopOcelotStarting(response); + } + } + + if (IsError(internalConfig)) + { + ThrowToStopOcelotStarting(internalConfig); } } - return GetOcelotConfigAndReturn(deps.provider); + //todo - this starts the poller if it has been registered...please this is so bad. + var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller)); } - private static async Task SetConfig(IApplicationBuilder builder, IOptions fileConfiguration, IFileConfigurationSetter setter, IOcelotConfigurationProvider provider, IFileConfigurationRepository repo) + private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptions fileConfig) { - if (UsingConsul(repo)) + Response response; + response = await fileConfigSetter.Set(fileConfig.Value); + + if (IsError(response)) { - return await SetUpConfigFromConsul(builder, repo, setter, fileConfiguration); + ThrowToStopOcelotStarting(response); } - - return await setter.Set(fileConfiguration.Value); } - private static bool UnableToSetConfig(Response response) + private static bool ConfigNotStoredInConsul(Responses.Response fileConfigFromConsul) + { + return fileConfigFromConsul.Data == null; + } + + private static bool IsError(Response response) { return response == null || response.IsError; } - private static bool ConfigurationNotSetUp(Ocelot.Responses.Response ocelotConfiguration) - { - return ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError; - } - - private static (IOptions fileConfiguration, IFileConfigurationSetter setter, IOcelotConfigurationProvider provider, IFileConfigurationRepository repo) GetDependencies(IApplicationBuilder builder) - { - var fileConfiguration = (IOptions)builder.ApplicationServices.GetService(typeof(IOptions)); - - var setter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); - - var provider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider)); - - var repo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository)); - - return (fileConfiguration, setter, provider, repo); - } - - private static IOcelotConfiguration GetOcelotConfigAndReturn(IOcelotConfigurationProvider provider) + private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider) { var ocelotConfiguration = provider.Get(); - if(ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError) + if(ocelotConfiguration?.Data == null || ocelotConfiguration.IsError) { ThrowToStopOcelotStarting(ocelotConfiguration); } @@ -158,49 +202,7 @@ return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository); } - private static async Task SetUpConfigFromConsul(IApplicationBuilder builder, IFileConfigurationRepository consulFileConfigRepo, IFileConfigurationSetter setter, IOptions fileConfig) - { - Response config = null; - - var ocelotConfigurationRepository = - (IOcelotConfigurationRepository) builder.ApplicationServices.GetService( - typeof(IOcelotConfigurationRepository)); - - var ocelotConfigurationCreator = - (IOcelotConfigurationCreator) builder.ApplicationServices.GetService( - typeof(IOcelotConfigurationCreator)); - - var fileConfigFromConsul = await consulFileConfigRepo.Get(); - - if (fileConfigFromConsul.Data == null) - { - config = await setter.Set(fileConfig.Value); - var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller)); - } - else - { - var ocelotConfig = await ocelotConfigurationCreator.Create(fileConfigFromConsul.Data); - - if(ocelotConfig.IsError) - { - return new ErrorResponse(ocelotConfig.Errors); - } - - config = ocelotConfigurationRepository.AddOrReplace(ocelotConfig.Data); - - if (config.IsError) - { - return new ErrorResponse(config.Errors); - } - - //todo - this starts the poller if it has been registered...please this is so bad. - var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller)); - } - - return new OkResponse(); - } - - private static void CreateAdministrationArea(IApplicationBuilder builder, IOcelotConfiguration configuration) + private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration) { if(!string.IsNullOrEmpty(configuration.AdministrationPath)) { diff --git a/src/Ocelot/Raft/FilePeersProvider.cs b/src/Ocelot/Raft/FilePeersProvider.cs index 20b3620d..7852e80a 100644 --- a/src/Ocelot/Raft/FilePeersProvider.cs +++ b/src/Ocelot/Raft/FilePeersProvider.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Options; using Ocelot.Configuration; using Ocelot.Configuration.Provider; +using Ocelot.Configuration.Repository; using Ocelot.Middleware; using Rafty.Concensus; using Rafty.Infrastructure; @@ -15,20 +16,20 @@ namespace Ocelot.Raft public class FilePeersProvider : IPeersProvider { private readonly IOptions _options; - private List _peers; + private readonly List _peers; private IBaseUrlFinder _finder; - private IOcelotConfigurationProvider _provider; + private IInternalConfigurationRepository _repo; private IIdentityServerConfiguration _identityServerConfig; - public FilePeersProvider(IOptions options, IBaseUrlFinder finder, IOcelotConfigurationProvider provider, IIdentityServerConfiguration identityServerConfig) + public FilePeersProvider(IOptions options, IBaseUrlFinder finder, IInternalConfigurationRepository repo, IIdentityServerConfiguration identityServerConfig) { _identityServerConfig = identityServerConfig; - _provider = provider; + _repo = repo; _finder = finder; _options = options; _peers = new List(); - var config = _provider.Get(); + var config = _repo.Get(); foreach (var item in _options.Value.Peers) { var httpClient = new HttpClient(); diff --git a/src/Ocelot/Raft/HttpPeer.cs b/src/Ocelot/Raft/HttpPeer.cs index cd5ceef5..dfc580d7 100644 --- a/src/Ocelot/Raft/HttpPeer.cs +++ b/src/Ocelot/Raft/HttpPeer.cs @@ -21,10 +21,10 @@ namespace Ocelot.Raft private JsonSerializerSettings _jsonSerializerSettings; private string _baseSchemeUrlAndPort; private BearerToken _token; - private IOcelotConfiguration _config; + private IInternalConfiguration _config; private IIdentityServerConfiguration _identityServerConfiguration; - public HttpPeer(string hostAndPort, HttpClient httpClient, IBaseUrlFinder finder, IOcelotConfiguration config, IIdentityServerConfiguration identityServerConfiguration) + public HttpPeer(string hostAndPort, HttpClient httpClient, IBaseUrlFinder finder, IInternalConfiguration config, IIdentityServerConfiguration identityServerConfiguration) { _identityServerConfiguration = identityServerConfiguration; _config = config; diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index 797f340c..5a5cce82 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -303,7 +303,7 @@ namespace Ocelot.AcceptanceTests { app.Run(async context => { - if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/OcelotConfiguration") + if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") { var json = JsonConvert.SerializeObject(_config); @@ -315,7 +315,7 @@ namespace Ocelot.AcceptanceTests await context.Response.WriteJsonAsync(new FakeConsulGetResponse[] { kvp }); } - else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/OcelotConfiguration") + else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") { try { @@ -352,7 +352,7 @@ namespace Ocelot.AcceptanceTests public int CreateIndex => 100; public int ModifyIndex => 200; public int LockIndex => 200; - public string Key => "OcelotConfiguration"; + public string Key => "InternalConfiguration"; public int Flags => 0; public string Value { get; private set; } public string Session => "adf4238a-882b-9ddc-4a9d-5b6758e4159e"; diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 04dcc0c1..9581d348 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -26,10 +26,10 @@ namespace Ocelot.UnitTests.Configuration { private readonly Mock> _fileConfig; private readonly Mock _validator; - private Response _config; + private Response _config; private FileConfiguration _fileConfiguration; private readonly Mock _logger; - private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator; + private readonly FileInternalConfigurationCreator _internalConfigurationCreator; private Mock _claimsToThingCreator; private Mock _authOptionsCreator; private Mock _upstreamTemplatePatternCreator; @@ -63,7 +63,7 @@ namespace Ocelot.UnitTests.Configuration _headerFindAndReplaceCreator = new Mock(); _downstreamAddressesCreator = new Mock(); - _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( + _internalConfigurationCreator = new FileInternalConfigurationCreator( _fileConfig.Object, _validator.Object, _logger.Object, @@ -807,7 +807,7 @@ namespace Ocelot.UnitTests.Configuration private void WhenICreateTheConfig() { - _config = _ocelotConfigurationCreator.Create(_fileConfiguration).Result; + _config = _internalConfigurationCreator.Create(_fileConfiguration).Result; } private void ThenTheReRoutesAre(List expectedReRoutes) diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs index e16148f2..49a52db2 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs @@ -19,17 +19,17 @@ namespace Ocelot.UnitTests.Configuration { private FileConfiguration _fileConfiguration; private FileConfigurationSetter _configSetter; - private Mock _configRepo; - private Mock _configCreator; - private Response _configuration; + private Mock _configRepo; + private Mock _configCreator; + private Response _configuration; private object _result; private Mock _repo; public FileConfigurationSetterTests() { _repo = new Mock(); - _configRepo = new Mock(); - _configCreator = new Mock(); + _configRepo = new Mock(); + _configCreator = new Mock(); _configSetter = new FileConfigurationSetter(_configRepo.Object, _configCreator.Object, _repo.Object); } @@ -38,11 +38,11 @@ namespace Ocelot.UnitTests.Configuration { var fileConfig = new FileConfiguration(); var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - var config = new OcelotConfiguration(new List(), string.Empty, serviceProviderConfig, "asdf"); + var config = new InternalConfiguration(new List(), string.Empty, serviceProviderConfig, "asdf"); this.Given(x => GivenTheFollowingConfiguration(fileConfig)) .And(x => GivenTheRepoReturns(new OkResponse())) - .And(x => GivenTheCreatorReturns(new OkResponse(config))) + .And(x => GivenTheCreatorReturns(new OkResponse(config))) .When(x => WhenISetTheConfiguration()) .Then(x => ThenTheConfigurationRepositoryIsCalledCorrectly()) .BDDfy(); @@ -67,7 +67,7 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => GivenTheFollowingConfiguration(fileConfig)) .And(x => GivenTheRepoReturns(new OkResponse())) - .And(x => GivenTheCreatorReturns(new ErrorResponse(It.IsAny()))) + .And(x => GivenTheCreatorReturns(new ErrorResponse(It.IsAny()))) .When(x => WhenISetTheConfiguration()) .And(x => ThenAnErrorResponseIsReturned()) .BDDfy(); @@ -85,7 +85,7 @@ namespace Ocelot.UnitTests.Configuration _result.ShouldBeOfType(); } - private void GivenTheCreatorReturns(Response configuration) + private void GivenTheCreatorReturns(Response configuration) { _configuration = configuration; _configCreator diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index 6216e24b..50bf1f85 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Configuration.Repository; @@ -14,14 +12,14 @@ namespace Ocelot.UnitTests.Configuration { public class InMemoryConfigurationRepositoryTests { - private readonly InMemoryOcelotConfigurationRepository _repo; - private IOcelotConfiguration _config; + private readonly InMemoryInternalConfigurationRepository _repo; + private IInternalConfiguration _config; private Response _result; - private Response _getResult; + private Response _getResult; public InMemoryConfigurationRepositoryTests() { - _repo = new InMemoryOcelotConfigurationRepository(); + _repo = new InMemoryInternalConfigurationRepository(); } [Fact] @@ -58,7 +56,7 @@ namespace Ocelot.UnitTests.Configuration WhenIAddOrReplaceTheConfig(); } - private void GivenTheConfigurationIs(IOcelotConfiguration config) + private void GivenTheConfigurationIs(IInternalConfiguration config) { _config = config; } @@ -73,7 +71,7 @@ namespace Ocelot.UnitTests.Configuration _result.IsError.ShouldBeFalse(); } - class FakeConfig : IOcelotConfiguration + class FakeConfig : IInternalConfiguration { private readonly string _downstreamTemplatePath; diff --git a/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs deleted file mode 100644 index 24000ea4..00000000 --- a/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.Collections.Generic; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -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() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenTheRepoReturns(new OkResponse(new OcelotConfiguration(new List(), string.Empty, serviceProviderConfig, "")))) - .When(x => x.WhenIGetTheConfig()) - .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List(), string.Empty, serviceProviderConfig, "")))) - .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/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 0b13954a..b295a564 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -325,8 +325,8 @@ namespace Ocelot.UnitTests.DependencyInjection var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); var outputCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); var instance = (ICacheManager)outputCacheManager.ImplementationInstance; - var ocelotConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); - var ocelotConfigCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); + var ocelotConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); + var ocelotConfigCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); var fileConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); var fileConfigCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 7361c862..95bcaf47 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -1,7 +1,4 @@ -using Ocelot.Middleware; -using Ocelot.Middleware.Multiplexer; - -namespace Ocelot.UnitTests.DownstreamRouteFinder +namespace Ocelot.UnitTests.DownstreamRouteFinder { using System.Collections.Generic; using System.Threading.Tasks; @@ -9,7 +6,6 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; - using Ocelot.Configuration.Provider; using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.Middleware; @@ -19,23 +15,26 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder using Shouldly; using TestStack.BDDfy; using Xunit; + using Ocelot.Configuration.Repository; + using Ocelot.Middleware; + using Ocelot.Middleware.Multiplexer; public class DownstreamRouteFinderMiddlewareTests { private readonly Mock _finder; - private readonly Mock _provider; + private readonly Mock _repo; private Response _downstreamRoute; - private IOcelotConfiguration _config; + private IInternalConfiguration _config; private Mock _loggerFactory; private Mock _logger; - private DownstreamRouteFinderMiddleware _middleware; - private DownstreamContext _downstreamContext; + private readonly DownstreamRouteFinderMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; private OcelotRequestDelegate _next; private readonly Mock _multiplexer; public DownstreamRouteFinderMiddlewareTests() { - _provider = new Mock(); + _repo = new Mock(); _finder = new Mock(); _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _loggerFactory = new Mock(); @@ -43,13 +42,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; _multiplexer = new Mock(); - _middleware = new DownstreamRouteFinderMiddleware(_next, _loggerFactory.Object, _finder.Object, _provider.Object, _multiplexer.Object); + _middleware = new DownstreamRouteFinderMiddleware(_next, _loggerFactory.Object, _finder.Object, _repo.Object, _multiplexer.Object); } [Fact] public void should_call_scoped_data_repository_correctly() { - var config = new OcelotConfiguration(null, null, new ServiceProviderConfigurationBuilder().Build(), ""); + var config = new InternalConfiguration(null, null, new ServiceProviderConfigurationBuilder().Build(), ""); var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("any old string") @@ -74,19 +73,19 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _middleware.Invoke(_downstreamContext).GetAwaiter().GetType(); } - private void GivenTheFollowingConfig(IOcelotConfiguration config) + private void GivenTheFollowingConfig(IInternalConfiguration config) { _config = config; - _provider + _repo .Setup(x => x.Get()) - .Returns(new OkResponse(_config)); + .Returns(new OkResponse(_config)); } private void GivenTheDownStreamRouteFinderReturns(DownstreamRoute downstreamRoute) { _downstreamRoute = new OkResponse(downstreamRoute); _finder - .Setup(x => x.FindDownstreamRoute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.FindDownstreamRoute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(_downstreamRoute); } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index cb5c2a67..c17b27d7 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -23,7 +23,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private string _upstreamUrlPath; private Response _result; private List _reRoutesConfig; - private OcelotConfiguration _config; + private InternalConfiguration _config; private Response _match; private string _upstreamHttpMethod; private string _upstreamHost; @@ -711,7 +711,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void GivenTheConfigurationIs(List reRoutesConfig, string adminPath, ServiceProviderConfiguration serviceProviderConfig) { _reRoutesConfig = reRoutesConfig; - _config = new OcelotConfiguration(_reRoutesConfig, adminPath, serviceProviderConfig, ""); + _config = new InternalConfiguration(_reRoutesConfig, adminPath, serviceProviderConfig, ""); } private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) diff --git a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs index 2d905035..8802a5bc 100644 --- a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs @@ -9,17 +9,17 @@ namespace Ocelot.UnitTests.Errors using TestStack.BDDfy; using Xunit; using Microsoft.AspNetCore.Http; - using Ocelot.Configuration.Provider; using Moq; using Ocelot.Configuration; using Ocelot.Errors; using Ocelot.Infrastructure.RequestData; using Ocelot.Middleware; + using Ocelot.Configuration.Repository; public class ExceptionHandlerMiddlewareTests { bool _shouldThrowAnException; - private readonly Mock _provider; + private readonly Mock _configRepo; private readonly Mock _repo; private Mock _loggerFactory; private Mock _logger; @@ -29,7 +29,7 @@ namespace Ocelot.UnitTests.Errors public ExceptionHandlerMiddlewareTests() { - _provider = new Mock(); + _configRepo = new Mock(); _repo = new Mock(); _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _loggerFactory = new Mock(); @@ -45,13 +45,13 @@ namespace Ocelot.UnitTests.Errors context.HttpContext.Response.StatusCode = (int)HttpStatusCode.OK; }; - _middleware = new ExceptionHandlerMiddleware(_next, _loggerFactory.Object, _provider.Object, _repo.Object); + _middleware = new ExceptionHandlerMiddleware(_next, _loggerFactory.Object, _configRepo.Object, _repo.Object); } [Fact] public void NoDownstreamException() { - var config = new OcelotConfiguration(null, null, null, null); + var config = new InternalConfiguration(null, null, null, null); this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) .And(_ => GivenTheConfigurationIs(config)) @@ -64,7 +64,7 @@ namespace Ocelot.UnitTests.Errors [Fact] public void DownstreamException() { - var config = new OcelotConfiguration(null, null, null, null); + var config = new InternalConfiguration(null, null, null, null); this.Given(_ => GivenAnExceptionWillBeThrownDownstream()) .And(_ => GivenTheConfigurationIs(config)) @@ -76,7 +76,7 @@ namespace Ocelot.UnitTests.Errors [Fact] public void ShouldSetRequestId() { - var config = new OcelotConfiguration(null, null, null, "requestidkey"); + var config = new InternalConfiguration(null, null, null, "requestidkey"); this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) .And(_ => GivenTheConfigurationIs(config)) @@ -89,7 +89,7 @@ namespace Ocelot.UnitTests.Errors [Fact] public void ShouldSetAspDotNetRequestId() { - var config = new OcelotConfiguration(null, null, null, null); + var config = new InternalConfiguration(null, null, null, null); this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) .And(_ => GivenTheConfigurationIs(config)) @@ -133,7 +133,7 @@ namespace Ocelot.UnitTests.Errors private void GivenTheConfigThrows() { var ex = new Exception("outer", new Exception("inner")); - _provider + _configRepo .Setup(x => x.Get()).Throws(ex); } @@ -144,8 +144,8 @@ namespace Ocelot.UnitTests.Errors private void GivenTheConfigReturnsError() { - var response = new Responses.ErrorResponse(new FakeError()); - _provider + var response = new Responses.ErrorResponse(new FakeError()); + _configRepo .Setup(x => x.Get()).Returns(response); } @@ -154,10 +154,10 @@ namespace Ocelot.UnitTests.Errors _repo.Verify(x => x.Add(key, value), Times.Once); } - private void GivenTheConfigurationIs(IOcelotConfiguration config) + private void GivenTheConfigurationIs(IInternalConfiguration config) { - var response = new Responses.OkResponse(config); - _provider + var response = new Responses.OkResponse(config); + _configRepo .Setup(x => x.Get()).Returns(response); } From fe9bca7b77811a47b27d94e3e7f12b3c17670138 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 14 Apr 2018 06:32:00 +0100 Subject: [PATCH 34/50] removed another pointless abstraction --- src/Ocelot/Cache/OutputCacheController.cs | 8 +- .../Authentication/HashMatcher.cs | 22 ---- .../Authentication/IHashMatcher.cs | 7 -- .../IdentityServerConfigurationCreator.cs | 3 - .../FileConfigurationController.cs | 24 ++-- .../Provider/FileConfigurationProvider.cs | 23 ---- .../Provider/IFileConfigurationProvider.cs | 11 -- ....cs => DiskFileConfigurationRepository.cs} | 108 +++++++++--------- ...InMemoryInternalConfigurationRepository.cs | 3 +- ... => FileAndInternalConfigurationSetter.cs} | 88 +++++++------- .../DependencyInjection/OcelotBuilder.cs | 10 +- .../DownstreamRouteFinderMiddleware.cs | 2 - src/Ocelot/Raft/FilePeersProvider.cs | 3 - src/Ocelot/Raft/HttpPeer.cs | 16 +-- .../FileConfigurationProviderTests.cs | 60 ---------- .../FileConfigurationRepositoryTests.cs | 4 +- .../FileConfigurationSetterTests.cs | 4 +- .../Configuration/HashMatcherTests.cs | 76 ------------ .../FileConfigurationControllerTests.cs | 28 +++-- .../DownstreamRouteFinderTests.cs | 2 - 20 files changed, 138 insertions(+), 364 deletions(-) delete mode 100644 src/Ocelot/Configuration/Authentication/HashMatcher.cs delete mode 100644 src/Ocelot/Configuration/Authentication/IHashMatcher.cs delete mode 100644 src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs delete mode 100644 src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs rename src/Ocelot/Configuration/Repository/{FileConfigurationRepository.cs => DiskFileConfigurationRepository.cs} (88%) rename src/Ocelot/Configuration/Setter/{FileConfigurationSetter.cs => FileAndInternalConfigurationSetter.cs} (88%) delete mode 100644 test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs delete mode 100644 test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs diff --git a/src/Ocelot/Cache/OutputCacheController.cs b/src/Ocelot/Cache/OutputCacheController.cs index 32b9fa7c..cc33e8aa 100644 --- a/src/Ocelot/Cache/OutputCacheController.cs +++ b/src/Ocelot/Cache/OutputCacheController.cs @@ -1,9 +1,5 @@ -using System.Net.Http; -using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Ocelot.Cache; -using Ocelot.Configuration.Provider; namespace Ocelot.Cache { @@ -11,7 +7,7 @@ namespace Ocelot.Cache [Route("outputcache")] public class OutputCacheController : Controller { - private IOcelotCache _cache; + private readonly IOcelotCache _cache; public OutputCacheController(IOcelotCache cache) { @@ -26,4 +22,4 @@ namespace Ocelot.Cache return new NoContentResult(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/Authentication/HashMatcher.cs b/src/Ocelot/Configuration/Authentication/HashMatcher.cs deleted file mode 100644 index 5f17362f..00000000 --- a/src/Ocelot/Configuration/Authentication/HashMatcher.cs +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 629bf008..00000000 --- a/src/Ocelot/Configuration/Authentication/IHashMatcher.cs +++ /dev/null @@ -1,7 +0,0 @@ -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/Creator/IdentityServerConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs index 5e7fcd37..8569001e 100644 --- a/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; -using Ocelot.Configuration.Provider; namespace Ocelot.Configuration.Creator { diff --git a/src/Ocelot/Configuration/FileConfigurationController.cs b/src/Ocelot/Configuration/FileConfigurationController.cs index 7bdf4926..707eb61d 100644 --- a/src/Ocelot/Configuration/FileConfigurationController.cs +++ b/src/Ocelot/Configuration/FileConfigurationController.cs @@ -2,34 +2,34 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; using Ocelot.Configuration.File; -using Ocelot.Configuration.Provider; using Ocelot.Configuration.Setter; using Ocelot.Raft; using Rafty.Concensus; namespace Ocelot.Configuration { + using Repository; + [Authorize] [Route("configuration")] public class FileConfigurationController : Controller { - private readonly IFileConfigurationProvider _configGetter; - private readonly IFileConfigurationSetter _configSetter; - private readonly IServiceProvider _serviceProvider; + private readonly IFileConfigurationRepository _repo; + private readonly IFileConfigurationSetter _setter; + private readonly IServiceProvider _provider; - public FileConfigurationController(IFileConfigurationProvider getFileConfig, IFileConfigurationSetter configSetter, IServiceProvider serviceProvider) + public FileConfigurationController(IFileConfigurationRepository repo, IFileConfigurationSetter setter, IServiceProvider provider) { - _configGetter = getFileConfig; - _configSetter = configSetter; - _serviceProvider = serviceProvider; + _repo = repo; + _setter = setter; + _provider = provider; } [HttpGet] public async Task Get() { - var response = await _configGetter.Get(); + var response = await _repo.Get(); if(response.IsError) { @@ -43,7 +43,7 @@ namespace Ocelot.Configuration public async Task Post([FromBody]FileConfiguration fileConfiguration) { //todo - this code is a bit shit sort it out.. - var test = _serviceProvider.GetService(typeof(INode)); + var test = _provider.GetService(typeof(INode)); if (test != null) { var node = (INode)test; @@ -56,7 +56,7 @@ namespace Ocelot.Configuration return new OkObjectResult(result.Command.Configuration); } - var response = await _configSetter.Set(fileConfiguration); + var response = await _setter.Set(fileConfiguration); if (response.IsError) { diff --git a/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs deleted file mode 100644 index 2d4cad8b..00000000 --- a/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Repository; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Provider -{ - public class FileConfigurationProvider : IFileConfigurationProvider - { - private readonly IFileConfigurationRepository _repo; - - public FileConfigurationProvider(IFileConfigurationRepository repo) - { - _repo = repo; - } - - public async Task> Get() - { - var fileConfig = await _repo.Get(); - return new OkResponse(fileConfig.Data); - } - } -} diff --git a/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs deleted file mode 100644 index c2ab51cb..00000000 --- a/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Provider -{ - public interface IFileConfigurationProvider - { - Task> Get(); - } -} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs similarity index 88% rename from src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs rename to src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs index 9619984f..5fb871f4 100644 --- a/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs @@ -1,54 +1,54 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Repository -{ - public class FileConfigurationRepository : IFileConfigurationRepository - { - private readonly string _configFilePath; - - private static readonly object _lock = new object(); - - private const string ConfigurationFileName = "ocelot"; - - public FileConfigurationRepository(IHostingEnvironment hostingEnvironment) - { - _configFilePath = $"{AppContext.BaseDirectory}/{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json"; - } - - public Task> Get() - { - string jsonConfiguration; - - lock(_lock) - { - jsonConfiguration = System.IO.File.ReadAllText(_configFilePath); - } - - var fileConfiguration = JsonConvert.DeserializeObject(jsonConfiguration); - - return Task.FromResult>(new OkResponse(fileConfiguration)); - } - - public Task Set(FileConfiguration fileConfiguration) - { - string jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - lock(_lock) - { - if (System.IO.File.Exists(_configFilePath)) - { - System.IO.File.Delete(_configFilePath); - } - - System.IO.File.WriteAllText(_configFilePath, jsonConfiguration); - } - - return Task.FromResult(new OkResponse()); - } - } -} +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Repository +{ + public class DiskFileConfigurationRepository : IFileConfigurationRepository + { + private readonly string _configFilePath; + + private static readonly object _lock = new object(); + + private const string ConfigurationFileName = "ocelot"; + + public DiskFileConfigurationRepository(IHostingEnvironment hostingEnvironment) + { + _configFilePath = $"{AppContext.BaseDirectory}/{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json"; + } + + public Task> Get() + { + string jsonConfiguration; + + lock(_lock) + { + jsonConfiguration = System.IO.File.ReadAllText(_configFilePath); + } + + var fileConfiguration = JsonConvert.DeserializeObject(jsonConfiguration); + + return Task.FromResult>(new OkResponse(fileConfiguration)); + } + + public Task Set(FileConfiguration fileConfiguration) + { + string jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + lock(_lock) + { + if (System.IO.File.Exists(_configFilePath)) + { + System.IO.File.Delete(_configFilePath); + } + + System.IO.File.WriteAllText(_configFilePath, jsonConfiguration); + } + + return Task.FromResult(new OkResponse()); + } + } +} diff --git a/src/Ocelot/Configuration/Repository/InMemoryInternalConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/InMemoryInternalConfigurationRepository.cs index fcb3baa9..2ac954e3 100644 --- a/src/Ocelot/Configuration/Repository/InMemoryInternalConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/InMemoryInternalConfigurationRepository.cs @@ -1,5 +1,4 @@ -using System.Threading.Tasks; -using Ocelot.Responses; +using Ocelot.Responses; namespace Ocelot.Configuration.Repository { diff --git a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs similarity index 88% rename from src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs rename to src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs index ba7b5ce9..6821553e 100644 --- a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs +++ b/src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs @@ -1,44 +1,44 @@ -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 IInternalConfigurationRepository _configRepo; - private readonly IInternalConfigurationCreator _configCreator; - private readonly IFileConfigurationRepository _repo; - - public FileConfigurationSetter( - IInternalConfigurationRepository configRepo, - IInternalConfigurationCreator configCreator, - IFileConfigurationRepository repo) - { - _configRepo = configRepo; - _configCreator = configCreator; - _repo = repo; - } - - public async Task Set(FileConfiguration fileConfig) - { - var response = await _repo.Set(fileConfig); - - if(response.IsError) - { - return new ErrorResponse(response.Errors); - } - - var config = await _configCreator.Create(fileConfig); - - if(!config.IsError) - { - _configRepo.AddOrReplace(config.Data); - } - - return new ErrorResponse(config.Errors); - } - } -} +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 FileAndInternalConfigurationSetter : IFileConfigurationSetter + { + private readonly IInternalConfigurationRepository _configRepo; + private readonly IInternalConfigurationCreator _configCreator; + private readonly IFileConfigurationRepository _repo; + + public FileAndInternalConfigurationSetter( + IInternalConfigurationRepository configRepo, + IInternalConfigurationCreator configCreator, + IFileConfigurationRepository repo) + { + _configRepo = configRepo; + _configCreator = configCreator; + _repo = repo; + } + + public async Task Set(FileConfiguration fileConfig) + { + var response = await _repo.Set(fileConfig); + + if(response.IsError) + { + return new ErrorResponse(response.Errors); + } + + var config = await _configCreator.Create(fileConfig); + + if(!config.IsError) + { + _configRepo.AddOrReplace(config.Data); + } + + return new ErrorResponse(config.Errors); + } + } +} diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index cd70b581..5373cc1b 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -8,11 +8,9 @@ namespace Ocelot.DependencyInjection 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; - using Ocelot.Configuration.Provider; using Ocelot.Configuration.Repository; using Ocelot.Configuration.Setter; using Ocelot.Configuration.Validator; @@ -40,8 +38,6 @@ namespace Ocelot.DependencyInjection using IdentityServer4.AccessTokenValidation; using Microsoft.AspNetCore.Builder; using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider; using Microsoft.Extensions.DependencyInjection.Extensions; using System.Net.Http; using Butterfly.Client.AspNetCore; @@ -86,9 +82,8 @@ namespace Ocelot.DependencyInjection _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); @@ -287,7 +282,6 @@ namespace Ocelot.DependencyInjection private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath) { _services.TryAddSingleton(identityServerConfiguration); - _services.TryAddSingleton(); var identityServerBuilder = _services .AddIdentityServer(o => { o.IssuerUri = "Ocelot"; diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index ab920fef..970636d3 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -1,7 +1,5 @@ using System.Threading.Tasks; using System.Linq; -using Ocelot.Configuration; -using Ocelot.Configuration.Provider; using Ocelot.Configuration.Repository; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.Infrastructure.Extensions; diff --git a/src/Ocelot/Raft/FilePeersProvider.cs b/src/Ocelot/Raft/FilePeersProvider.cs index 7852e80a..52b877df 100644 --- a/src/Ocelot/Raft/FilePeersProvider.cs +++ b/src/Ocelot/Raft/FilePeersProvider.cs @@ -1,10 +1,7 @@ -using System; using System.Collections.Generic; using System.Net.Http; -using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Options; using Ocelot.Configuration; -using Ocelot.Configuration.Provider; using Ocelot.Configuration.Repository; using Ocelot.Middleware; using Rafty.Concensus; diff --git a/src/Ocelot/Raft/HttpPeer.cs b/src/Ocelot/Raft/HttpPeer.cs index dfc580d7..fcec77ea 100644 --- a/src/Ocelot/Raft/HttpPeer.cs +++ b/src/Ocelot/Raft/HttpPeer.cs @@ -1,12 +1,8 @@ using System; using System.Collections.Generic; using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; using Newtonsoft.Json; -using Ocelot.Authentication; using Ocelot.Configuration; -using Ocelot.Configuration.Provider; using Ocelot.Middleware; using Rafty.Concensus; using Rafty.FiniteStateMachine; @@ -16,13 +12,13 @@ namespace Ocelot.Raft [ExcludeFromCoverage] public class HttpPeer : IPeer { - private string _hostAndPort; - private HttpClient _httpClient; - private JsonSerializerSettings _jsonSerializerSettings; - private string _baseSchemeUrlAndPort; + private readonly string _hostAndPort; + private readonly HttpClient _httpClient; + private readonly JsonSerializerSettings _jsonSerializerSettings; + private readonly string _baseSchemeUrlAndPort; private BearerToken _token; - private IInternalConfiguration _config; - private IIdentityServerConfiguration _identityServerConfiguration; + private readonly IInternalConfiguration _config; + private readonly IIdentityServerConfiguration _identityServerConfiguration; public HttpPeer(string hostAndPort, HttpClient httpClient, IBaseUrlFinder finder, IInternalConfiguration config, IIdentityServerConfiguration identityServerConfiguration) { diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs deleted file mode 100644 index 506da50c..00000000 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using Moq; -using Ocelot.Configuration; -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 IFileConfigurationProvider _provider; - private Mock _repo; - private FileConfiguration _result; - private FileConfiguration _fileConfiguration; - - public FileConfigurationProviderTests() - { - _repo = new Mock(); - _provider = new FileConfigurationProvider(_repo.Object); - } - - [Fact] - public void should_return_file_configuration() - { - var config = new FileConfiguration(); - - this.Given(x => x.GivenTheConfigurationIs(config)) - .When(x => x.WhenIGetTheReRoutes()) - .Then(x => x.ThenTheRepoIsCalledCorrectly()) - .BDDfy(); - } - - private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) - { - _fileConfiguration = fileConfiguration; - _repo - .Setup(x => x.Get()) - .ReturnsAsync(new OkResponse(fileConfiguration)); - } - - private void WhenIGetTheReRoutes() - { - _result = _provider.Get().Result.Data; - } - - private void ThenTheRepoIsCalledCorrectly() - { - _repo - .Verify(x => x.Get(), Times.Once); - } - } -} diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs index b27015e3..c492af44 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs @@ -23,7 +23,7 @@ namespace Ocelot.UnitTests.Configuration public FileConfigurationRepositoryTests() { _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); - _repo = new FileConfigurationRepository(_hostingEnvironment.Object); + _repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); } [Fact] @@ -75,7 +75,7 @@ namespace Ocelot.UnitTests.Configuration { _environmentName = null; _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); - _repo = new FileConfigurationRepository(_hostingEnvironment.Object); + _repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); } private void GivenIHaveAConfiguration(FileConfiguration fileConfiguration) diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs index 49a52db2..ff33872c 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs @@ -18,7 +18,7 @@ namespace Ocelot.UnitTests.Configuration public class FileConfigurationSetterTests { private FileConfiguration _fileConfiguration; - private FileConfigurationSetter _configSetter; + private FileAndInternalConfigurationSetter _configSetter; private Mock _configRepo; private Mock _configCreator; private Response _configuration; @@ -30,7 +30,7 @@ namespace Ocelot.UnitTests.Configuration _repo = new Mock(); _configRepo = new Mock(); _configCreator = new Mock(); - _configSetter = new FileConfigurationSetter(_configRepo.Object, _configCreator.Object, _repo.Object); + _configSetter = new FileAndInternalConfigurationSetter(_configRepo.Object, _configCreator.Object, _repo.Object); } [Fact] diff --git a/test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs b/test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs deleted file mode 100644 index 55c5edc9..00000000 --- a/test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -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/Controllers/FileConfigurationControllerTests.cs b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs index c0ae8b87..520d8274 100644 --- a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs @@ -8,32 +8,30 @@ using Ocelot.Responses; using TestStack.BDDfy; using Xunit; using Shouldly; -using Ocelot.Configuration.Provider; -using Microsoft.Extensions.DependencyInjection; using Ocelot.Raft; using Rafty.Concensus; -using Newtonsoft.Json; -using Rafty.FiniteStateMachine; using Ocelot.Configuration; namespace Ocelot.UnitTests.Controllers { + using Ocelot.Configuration.Repository; + public class FileConfigurationControllerTests { - private FileConfigurationController _controller; - private Mock _configGetter; - private Mock _configSetter; + private readonly FileConfigurationController _controller; + private readonly Mock _repo; + private readonly Mock _setter; private IActionResult _result; private FileConfiguration _fileConfiguration; - private Mock _provider; + private readonly Mock _provider; private Mock _node; public FileConfigurationControllerTests() { _provider = new Mock(); - _configGetter = new Mock(); - _configSetter = new Mock(); - _controller = new FileConfigurationController(_configGetter.Object, _configSetter.Object, _provider.Object); + _repo = new Mock(); + _setter = new Mock(); + _controller = new FileConfigurationController(_repo.Object, _setter.Object, _provider.Object); } [Fact] @@ -140,14 +138,14 @@ namespace Ocelot.UnitTests.Controllers private void GivenTheConfigSetterReturns(Response response) { - _configSetter + _setter .Setup(x => x.Set(It.IsAny())) .ReturnsAsync(response); } private void ThenTheConfigrationSetterIsCalledCorrectly() { - _configSetter + _setter .Verify(x => x.Set(_fileConfiguration), Times.Once); } @@ -168,7 +166,7 @@ namespace Ocelot.UnitTests.Controllers private void GivenTheGetConfigurationReturns(Ocelot.Responses.Response fileConfiguration) { - _configGetter + _repo .Setup(x => x.Get()) .ReturnsAsync(fileConfiguration); } @@ -180,7 +178,7 @@ namespace Ocelot.UnitTests.Controllers private void TheTheGetFileConfigurationIsCalledCorrectly() { - _configGetter + _repo .Verify(x => x.Get(), Times.Once); } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index c17b27d7..7406b1c5 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -2,8 +2,6 @@ using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.Provider; using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.UrlMatcher; From fa09e4cf7ac2377548176f00470235a6782d9755 Mon Sep 17 00:00:00 2001 From: Felix Boers Date: Sat, 14 Apr 2018 07:41:12 +0200 Subject: [PATCH 35/50] Support adding custom plaintext headers to downstream requests (#314) --- docs/features/headerstransformation.rst | 26 +- .../Builder/DownstreamReRouteBuilder.cs | 11 +- .../Creator/FileOcelotConfigurationCreator.cs | 3 +- .../Creator/HeaderFindAndReplaceCreator.cs | 20 +- .../Creator/HeaderTransformations.cs | 7 +- src/Ocelot/Configuration/DownstreamReRoute.cs | 5 +- src/Ocelot/Headers/AddHeadersToRequest.cs | 17 +- src/Ocelot/Headers/IAddHeadersToRequest.cs | 5 +- .../HttpHeadersTransformationMiddleware.cs | 13 +- .../FileConfigurationCreatorTests.cs | 3 +- .../HeaderFindAndReplaceCreatorTests.cs | 49 ++- ...> AddHeadersToRequestClaimToThingTests.cs} | 302 +++++++++--------- .../Headers/AddHeadersToRequestPlainTests.cs | 75 +++++ ...ttpHeadersTransformationMiddlewareTests.cs | 23 +- 14 files changed, 376 insertions(+), 183 deletions(-) rename test/Ocelot.UnitTests/Headers/{AddHeadersToRequestTests.cs => AddHeadersToRequestClaimToThingTests.cs} (95%) create mode 100644 test/Ocelot.UnitTests/Headers/AddHeadersToRequestPlainTests.cs diff --git a/docs/features/headerstransformation.rst b/docs/features/headerstransformation.rst index 6a333141..544ae2b0 100644 --- a/docs/features/headerstransformation.rst +++ b/docs/features/headerstransformation.rst @@ -1,15 +1,31 @@ Headers Transformation -===================== +====================== Ocelot allows the user to transform headers pre and post downstream request. At the moment Ocelot only supports find and replace. This feature was requested `GitHub #190 `_ and I decided that it was going to be useful in various ways. +Add to Request +^^^^^^^^^^^^^^ + +This feature was requestes in `GitHub #313 `_. + +If you want to add a header to your upstream request please add the following to a ReRoute in your configuration.json: + +.. code-block:: json + + "UpstreamHeaderTransform": { + "Uncle": "Bob" + } + +In the example above a header with the key Uncle and value Bob would be send to to the upstream service. + +Placeholders are supported too (see below). + Add to Response ^^^^^^^^^^^^^^^ -This feature was requested in `GitHub #280 `_. I have only implemented -for responses but could add for requests in the future. +This feature was requested in `GitHub #280 `_. -If you want to add a header to your downstream response please add the following to a ReRoute in configuration.json.. +If you want to add a header to your downstream response please add the following to a ReRoute in configuration.json. .. code-block:: json @@ -50,7 +66,7 @@ Add the following to a ReRoute in configuration.json in order to replace http:// }, Post Downstream Request -^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^ Add the following to a ReRoute in configuration.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This transformation will take place after Ocelot has received the response from the downstream service. diff --git a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs index d6dc0f34..49931421 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs @@ -39,12 +39,14 @@ namespace Ocelot.Configuration.Builder private string _key; private List _delegatingHandlers; private List _addHeadersToDownstream; + private List _addHeadersToUpstream; public DownstreamReRouteBuilder() { _downstreamAddresses = new List(); _delegatingHandlers = new List(); _addHeadersToDownstream = new List(); + _addHeadersToUpstream = new List(); } public DownstreamReRouteBuilder WithDownstreamAddresses(List downstreamAddresses) @@ -233,6 +235,12 @@ namespace Ocelot.Configuration.Builder return this; } + public DownstreamReRouteBuilder WithAddHeadersToUpstream(List addHeadersToUpstream) + { + _addHeadersToUpstream = addHeadersToUpstream; + return this; + } + public DownstreamReRoute Build() { return new DownstreamReRoute( @@ -263,7 +271,8 @@ namespace Ocelot.Configuration.Builder new PathTemplate(_downstreamPathTemplate), _reRouteKey, _delegatingHandlers, - _addHeadersToDownstream); + _addHeadersToDownstream, + _addHeadersToUpstream); } } } diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 821c0ed7..cee03813 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -214,7 +214,8 @@ namespace Ocelot.Configuration.Creator .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) .WithUpstreamHost(fileReRoute.UpstreamHost) .WithDelegatingHandlers(fileReRoute.DelegatingHandlers) - .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) + .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) + .WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream) .Build(); return reRoute; diff --git a/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs b/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs index 7bfc6d8e..1a5f1b6a 100644 --- a/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs +++ b/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs @@ -22,23 +22,31 @@ namespace Ocelot.Configuration.Creator public HeaderTransformations Create(FileReRoute fileReRoute) { var upstream = new List(); + var addHeadersToUpstream = new List(); foreach(var input in fileReRoute.UpstreamHeaderTransform) { - var hAndr = Map(input); - if(!hAndr.IsError) + if (input.Value.Contains(",")) { - upstream.Add(hAndr.Data); + var hAndr = Map(input); + if (!hAndr.IsError) + { + upstream.Add(hAndr.Data); + } + else + { + _logger.LogWarning($"Unable to add UpstreamHeaderTransform {input.Key}: {input.Value}"); + } } else { - _logger.LogWarning($"Unable to add UpstreamHeaderTransform {input.Key}: {input.Value}"); + addHeadersToUpstream.Add(new AddHeader(input.Key, input.Value)); } } var downstream = new List(); var addHeadersToDownstream = new List(); - + foreach(var input in fileReRoute.DownstreamHeaderTransform) { if(input.Value.Contains(",")) @@ -59,7 +67,7 @@ namespace Ocelot.Configuration.Creator } } - return new HeaderTransformations(upstream, downstream, addHeadersToDownstream); + return new HeaderTransformations(upstream, downstream, addHeadersToDownstream, addHeadersToUpstream); } private Response Map(KeyValuePair input) diff --git a/src/Ocelot/Configuration/Creator/HeaderTransformations.cs b/src/Ocelot/Configuration/Creator/HeaderTransformations.cs index 461e5c35..72b6e1f6 100644 --- a/src/Ocelot/Configuration/Creator/HeaderTransformations.cs +++ b/src/Ocelot/Configuration/Creator/HeaderTransformations.cs @@ -7,9 +7,11 @@ namespace Ocelot.Configuration.Creator public HeaderTransformations( List upstream, List downstream, - List addHeader) + List addHeaderToDownstream, + List addHeaderToUpstream) { - AddHeadersToDownstream = addHeader; + AddHeadersToDownstream = addHeaderToDownstream; + AddHeadersToUpstream = addHeaderToUpstream; Upstream = upstream; Downstream = downstream; } @@ -19,5 +21,6 @@ namespace Ocelot.Configuration.Creator public List Downstream { get; } public List AddHeadersToDownstream { get; } + public List AddHeadersToUpstream { get; } } } diff --git a/src/Ocelot/Configuration/DownstreamReRoute.cs b/src/Ocelot/Configuration/DownstreamReRoute.cs index 00accc07..90e96ec5 100644 --- a/src/Ocelot/Configuration/DownstreamReRoute.cs +++ b/src/Ocelot/Configuration/DownstreamReRoute.cs @@ -34,7 +34,8 @@ namespace Ocelot.Configuration PathTemplate downstreamPathTemplate, string reRouteKey, List delegatingHandlers, - List addHeadersToDownstream) + List addHeadersToDownstream, + List addHeadersToUpstream) { AddHeadersToDownstream = addHeadersToDownstream; DelegatingHandlers = delegatingHandlers; @@ -64,6 +65,7 @@ namespace Ocelot.Configuration AuthenticationOptions = authenticationOptions; DownstreamPathTemplate = downstreamPathTemplate; ReRouteKey = reRouteKey; + AddHeadersToUpstream = addHeadersToUpstream; } public string Key { get; private set; } @@ -94,5 +96,6 @@ namespace Ocelot.Configuration public string ReRouteKey { get; private set; } public List DelegatingHandlers {get;private set;} public List AddHeadersToDownstream {get;private set;} + public List AddHeadersToUpstream { get; private set; } } } diff --git a/src/Ocelot/Headers/AddHeadersToRequest.cs b/src/Ocelot/Headers/AddHeadersToRequest.cs index 1a71935a..a7b5b689 100644 --- a/src/Ocelot/Headers/AddHeadersToRequest.cs +++ b/src/Ocelot/Headers/AddHeadersToRequest.cs @@ -4,6 +4,7 @@ using Ocelot.Configuration; using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Responses; using System.Net.Http; +using Microsoft.AspNetCore.Http; using Ocelot.Configuration.Creator; using Ocelot.Request.Middleware; @@ -41,5 +42,19 @@ namespace Ocelot.Headers return new OkResponse(); } + + public void SetHeadersOnDownstreamRequest(IEnumerable headers, HttpContext context) + { + var requestHeader = context.Request.Headers; + foreach (var header in headers) + { + if (requestHeader.ContainsKey(header.Key)) + { + requestHeader.Remove(header.Key); + } + + requestHeader.Add(header.Key, header.Value); + } + } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Headers/IAddHeadersToRequest.cs b/src/Ocelot/Headers/IAddHeadersToRequest.cs index c8b1c967..a0afb0c0 100644 --- a/src/Ocelot/Headers/IAddHeadersToRequest.cs +++ b/src/Ocelot/Headers/IAddHeadersToRequest.cs @@ -1,4 +1,6 @@ -namespace Ocelot.Headers +using Microsoft.AspNetCore.Http; + +namespace Ocelot.Headers { using System.Collections.Generic; using System.Net.Http; @@ -12,5 +14,6 @@ public interface IAddHeadersToRequest { Response SetHeadersOnDownstreamRequest(List claimsToThings, IEnumerable claims, DownstreamRequest downstreamRequest); + void SetHeadersOnDownstreamRequest(IEnumerable headers, HttpContext context); } } diff --git a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs index 9544fe7f..9dfe3d64 100644 --- a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs @@ -9,16 +9,19 @@ namespace Ocelot.Headers.Middleware private readonly OcelotRequestDelegate _next; private readonly IHttpContextRequestHeaderReplacer _preReplacer; private readonly IHttpResponseHeaderReplacer _postReplacer; - private readonly IAddHeadersToResponse _addHeaders; + private readonly IAddHeadersToResponse _addHeadersToResponse; + private readonly IAddHeadersToRequest _addHeadersToRequest; public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, IHttpContextRequestHeaderReplacer preReplacer, IHttpResponseHeaderReplacer postReplacer, - IAddHeadersToResponse addHeaders) + IAddHeadersToResponse addHeadersToResponse, + IAddHeadersToRequest addHeadersToRequest) :base(loggerFactory.CreateLogger()) { - _addHeaders = addHeaders; + _addHeadersToResponse = addHeadersToResponse; + _addHeadersToRequest = addHeadersToRequest; _next = next; _postReplacer = postReplacer; _preReplacer = preReplacer; @@ -31,13 +34,15 @@ namespace Ocelot.Headers.Middleware //todo - this should be on httprequestmessage not httpcontext? _preReplacer.Replace(context.HttpContext, preFAndRs); + _addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.AddHeadersToUpstream, context.HttpContext); + await _next.Invoke(context); var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace; _postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest); - _addHeaders.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse); + _addHeadersToResponse.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse); } } } diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 04dcc0c1..479c3e28 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -829,6 +829,7 @@ namespace Ocelot.UnitTests.Configuration result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey); result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers); result.DownstreamReRoute[0].AddHeadersToDownstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToDownstream); + result.DownstreamReRoute[0].AddHeadersToUpstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToUpstream, "AddHeadersToUpstream should be set"); } } @@ -911,7 +912,7 @@ namespace Ocelot.UnitTests.Configuration private void GivenTheHeaderFindAndReplaceCreatorReturns() { - _headerFindAndReplaceCreator.Setup(x => x.Create(It.IsAny())).Returns(new HeaderTransformations(new List(), new List(), new List())); + _headerFindAndReplaceCreator.Setup(x => x.Create(It.IsAny())).Returns(new HeaderTransformations(new List(), new List(), new List(), new List())); } private void GivenTheFollowingIsReturned(ServiceProviderConfiguration serviceProviderConfiguration) diff --git a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs index 1ec251cf..df37ca8b 100644 --- a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs @@ -149,7 +149,6 @@ namespace Ocelot.UnitTests.Configuration .Then(x => ThenTheFollowingDownstreamIsReturned(downstream)) .BDDfy(); } - [Fact] public void should_add_trace_id_header() { @@ -166,7 +165,45 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => GivenTheReRoute(reRoute)) .And(x => GivenTheBaseUrlIs("http://ocelot.com/")) .When(x => WhenICreate()) - .Then(x => ThenTheFollowingAddHeaderIsReturned(expected)) + .Then(x => ThenTheFollowingAddHeaderToDownstreamIsReturned(expected)) + .BDDfy(); + } + + [Fact] + public void should_add_downstream_header_as_is_when_no_replacement_is_given() + { + var reRoute = new FileReRoute + { + DownstreamHeaderTransform = new Dictionary + { + {"X-Custom-Header", "Value"}, + } + }; + + var expected = new AddHeader("X-Custom-Header", "Value"); + + this.Given(x => GivenTheReRoute(reRoute)) + .And(x => WhenICreate()) + .Then(x => x.ThenTheFollowingAddHeaderToDownstreamIsReturned(expected)) + .BDDfy(); + } + + [Fact] + public void should_add_upstream_header_as_is_when_no_replacement_is_given() + { + var reRoute = new FileReRoute + { + UpstreamHeaderTransform = new Dictionary + { + {"X-Custom-Header", "Value"}, + } + }; + + var expected = new AddHeader("X-Custom-Header", "Value"); + + this.Given(x => GivenTheReRoute(reRoute)) + .And(x => WhenICreate()) + .Then(x => x.ThenTheFollowingAddHeaderToUpstreamIsReturned(expected)) .BDDfy(); } @@ -180,11 +217,17 @@ namespace Ocelot.UnitTests.Configuration _placeholders.Setup(x => x.Get(It.IsAny())).Returns(new ErrorResponse(new AnyError())); } - private void ThenTheFollowingAddHeaderIsReturned(AddHeader addHeader) + private void ThenTheFollowingAddHeaderToDownstreamIsReturned(AddHeader addHeader) { _result.AddHeadersToDownstream[0].Key.ShouldBe(addHeader.Key); _result.AddHeadersToDownstream[0].Value.ShouldBe(addHeader.Value); } + + private void ThenTheFollowingAddHeaderToUpstreamIsReturned(AddHeader addHeader) + { + _result.AddHeadersToUpstream[0].Key.ShouldBe(addHeader.Key); + _result.AddHeadersToUpstream[0].Value.ShouldBe(addHeader.Value); + } private void ThenTheFollowingDownstreamIsReturned(List downstream) { diff --git a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestTests.cs b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestClaimToThingTests.cs similarity index 95% rename from test/Ocelot.UnitTests/Headers/AddHeadersToRequestTests.cs rename to test/Ocelot.UnitTests/Headers/AddHeadersToRequestClaimToThingTests.cs index 8e323bb2..2ad59ca5 100644 --- a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestTests.cs +++ b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestClaimToThingTests.cs @@ -1,151 +1,151 @@ -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using Moq; -using Ocelot.Configuration; -using Ocelot.Errors; -using Ocelot.Headers; -using Ocelot.Infrastructure.Claims.Parser; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using System.Net.Http; -using Ocelot.Request.Middleware; - -namespace Ocelot.UnitTests.Headers -{ - public class AddHeadersToRequestTests - { - private readonly AddHeadersToRequest _addHeadersToRequest; - private readonly Mock _parser; - private readonly DownstreamRequest _downstreamRequest; - private List _claims; - private List _configuration; - private Response _result; - private Response _claimValue; - - public AddHeadersToRequestTests() - { - _parser = new Mock(); - _addHeadersToRequest = new AddHeadersToRequest(_parser.Object); - _downstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); - } - - [Fact] - public void should_add_headers_to_downstreamRequest() - { - var claims = new List - { - new Claim("test", "data") - }; - - this.Given( - x => x.GivenConfigurationHeaderExtractorProperties(new List - { - new ClaimToThing("header-key", "", "", 0) - })) - .Given(x => x.GivenClaims(claims)) - .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) - .When(x => x.WhenIAddHeadersToTheRequest()) - .Then(x => x.ThenTheResultIsSuccess()) - .And(x => x.ThenTheHeaderIsAdded()) - .BDDfy(); - } - - [Fact] - public void should_replace_existing_headers_on_request() - { - this.Given( - x => x.GivenConfigurationHeaderExtractorProperties(new List - { - new ClaimToThing("header-key", "", "", 0) - })) - .Given(x => x.GivenClaims(new List - { - new Claim("test", "data") - })) - .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) - .And(x => x.GivenThatTheRequestContainsHeader("header-key", "initial")) - .When(x => x.WhenIAddHeadersToTheRequest()) - .Then(x => x.ThenTheResultIsSuccess()) - .And(x => x.ThenTheHeaderIsAdded()) - .BDDfy(); - } - - [Fact] - public void should_return_error() - { - this.Given( - x => x.GivenConfigurationHeaderExtractorProperties(new List - { - new ClaimToThing("", "", "", 0) - })) - .Given(x => x.GivenClaims(new List())) - .And(x => x.GivenTheClaimParserReturns(new ErrorResponse(new List - { - new AnyError() - }))) - .When(x => x.WhenIAddHeadersToTheRequest()) - .Then(x => x.ThenTheResultIsError()) - .BDDfy(); - } - - private void GivenClaims(List claims) - { - _claims = claims; - } - - private void GivenConfigurationHeaderExtractorProperties(List configuration) - { - _configuration = configuration; - } - - private void GivenThatTheRequestContainsHeader(string key, string value) - { - _downstreamRequest.Headers.Add(key, value); - } - - private void GivenTheClaimParserReturns(Response claimValue) - { - _claimValue = claimValue; - _parser - .Setup( - x => - x.GetValue(It.IsAny>(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns(_claimValue); - } - - private void WhenIAddHeadersToTheRequest() - { - _result = _addHeadersToRequest.SetHeadersOnDownstreamRequest(_configuration, _claims, _downstreamRequest); - } - - private void ThenTheResultIsSuccess() - { - _result.IsError.ShouldBe(false); - } - - private void ThenTheResultIsError() - { - _result.IsError.ShouldBe(true); - } - - private void ThenTheHeaderIsAdded() - { - var header = _downstreamRequest.Headers.First(x => x.Key == "header-key"); - header.Value.First().ShouldBe(_claimValue.Data); - } - - class AnyError : Error - { - public AnyError() - : base("blahh", OcelotErrorCode.UnknownError) - { - } - } - } -} +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using Moq; +using Ocelot.Configuration; +using Ocelot.Errors; +using Ocelot.Headers; +using Ocelot.Infrastructure.Claims.Parser; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using System.Net.Http; +using Ocelot.Request.Middleware; + +namespace Ocelot.UnitTests.Headers +{ + public class AddHeadersToRequestClaimToThingTests + { + private readonly AddHeadersToRequest _addHeadersToRequest; + private readonly Mock _parser; + private readonly DownstreamRequest _downstreamRequest; + private List _claims; + private List _configuration; + private Response _result; + private Response _claimValue; + + public AddHeadersToRequestClaimToThingTests() + { + _parser = new Mock(); + _addHeadersToRequest = new AddHeadersToRequest(_parser.Object); + _downstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); + } + + [Fact] + public void should_add_headers_to_downstreamRequest() + { + var claims = new List + { + new Claim("test", "data") + }; + + this.Given( + x => x.GivenConfigurationHeaderExtractorProperties(new List + { + new ClaimToThing("header-key", "", "", 0) + })) + .Given(x => x.GivenClaims(claims)) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .When(x => x.WhenIAddHeadersToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .And(x => x.ThenTheHeaderIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_replace_existing_headers_on_request() + { + this.Given( + x => x.GivenConfigurationHeaderExtractorProperties(new List + { + new ClaimToThing("header-key", "", "", 0) + })) + .Given(x => x.GivenClaims(new List + { + new Claim("test", "data") + })) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .And(x => x.GivenThatTheRequestContainsHeader("header-key", "initial")) + .When(x => x.WhenIAddHeadersToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .And(x => x.ThenTheHeaderIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_return_error() + { + this.Given( + x => x.GivenConfigurationHeaderExtractorProperties(new List + { + new ClaimToThing("", "", "", 0) + })) + .Given(x => x.GivenClaims(new List())) + .And(x => x.GivenTheClaimParserReturns(new ErrorResponse(new List + { + new AnyError() + }))) + .When(x => x.WhenIAddHeadersToTheRequest()) + .Then(x => x.ThenTheResultIsError()) + .BDDfy(); + } + + private void GivenClaims(List claims) + { + _claims = claims; + } + + private void GivenConfigurationHeaderExtractorProperties(List configuration) + { + _configuration = configuration; + } + + private void GivenThatTheRequestContainsHeader(string key, string value) + { + _downstreamRequest.Headers.Add(key, value); + } + + private void GivenTheClaimParserReturns(Response claimValue) + { + _claimValue = claimValue; + _parser + .Setup( + x => + x.GetValue(It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(_claimValue); + } + + private void WhenIAddHeadersToTheRequest() + { + _result = _addHeadersToRequest.SetHeadersOnDownstreamRequest(_configuration, _claims, _downstreamRequest); + } + + private void ThenTheResultIsSuccess() + { + _result.IsError.ShouldBe(false); + } + + private void ThenTheResultIsError() + { + _result.IsError.ShouldBe(true); + } + + private void ThenTheHeaderIsAdded() + { + var header = _downstreamRequest.Headers.First(x => x.Key == "header-key"); + header.Value.First().ShouldBe(_claimValue.Data); + } + + class AnyError : Error + { + public AnyError() + : base("blahh", OcelotErrorCode.UnknownError) + { + } + } + } +} diff --git a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestPlainTests.cs b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestPlainTests.cs new file mode 100644 index 00000000..69fd4f69 --- /dev/null +++ b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestPlainTests.cs @@ -0,0 +1,75 @@ +using Microsoft.AspNetCore.Http; +using Moq; +using Ocelot.Configuration.Creator; +using Ocelot.Headers; +using Ocelot.Infrastructure.Claims.Parser; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Headers +{ + public class AddHeadersToRequestPlainTests + { + private readonly AddHeadersToRequest _addHeadersToRequest; + private HttpContext _context; + private AddHeader _addedHeader; + + public AddHeadersToRequestPlainTests() + { + _addHeadersToRequest = new AddHeadersToRequest(Mock.Of()); + } + + [Fact] + public void should_add_plain_text_header_to_downstream_request() + { + this.Given(_ => GivenHttpRequestWithoutHeaders()) + .When(_ => WhenAddingHeader("X-Custom-Header", "PlainValue")) + .Then(_ => ThenTheHeaderGetsTakenOverToTheRequestHeaders()) + .BDDfy(); + } + + [Fact] + public void should_overwrite_existing_header_with_added_header() + { + this.Given(_ => GivenHttpRequestWithHeader("X-Custom-Header", "This should get overwritten")) + .When(_ => WhenAddingHeader("X-Custom-Header", "PlainValue")) + .Then(_ => ThenTheHeaderGetsTakenOverToTheRequestHeaders()) + .BDDfy(); + } + + private void GivenHttpRequestWithoutHeaders() + { + _context = new DefaultHttpContext(); + } + + private void GivenHttpRequestWithHeader(string headerKey, string headerValue) + { + _context = new DefaultHttpContext + { + Request = + { + Headers = + { + { headerKey, headerValue } + } + } + }; + } + + private void WhenAddingHeader(string headerKey, string headerValue) + { + _addedHeader = new AddHeader(headerKey, headerValue); + _addHeadersToRequest.SetHeadersOnDownstreamRequest(new[] { _addedHeader }, _context); + } + + private void ThenTheHeaderGetsTakenOverToTheRequestHeaders() + { + var requestHeaders = _context.Request.Headers; + requestHeaders.ContainsKey(_addedHeader.Key).ShouldBeTrue($"Header {_addedHeader.Key} was expected but not there."); + var value = requestHeaders[_addedHeader.Key]; + value.ShouldNotBeNull($"Value of header {_addedHeader.Key} was expected to not be null."); + value.ToString().ShouldBe(_addedHeader.Value); + } + } +} diff --git a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs index cc65624c..7b13cc6d 100644 --- a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs @@ -27,7 +27,8 @@ namespace Ocelot.UnitTests.Headers private readonly HttpHeadersTransformationMiddleware _middleware; private readonly DownstreamContext _downstreamContext; private OcelotRequestDelegate _next; - private readonly Mock _addHeaders; + private readonly Mock _addHeadersToResponse; + private readonly Mock _addHeadersToRequest; public HttpHeadersTransformationMiddlewareTests() { @@ -38,8 +39,11 @@ namespace Ocelot.UnitTests.Headers _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; - _addHeaders = new Mock(); - _middleware = new HttpHeadersTransformationMiddleware(_next, _loggerFactory.Object, _preReplacer.Object, _postReplacer.Object, _addHeaders.Object); + _addHeadersToResponse = new Mock(); + _addHeadersToRequest = new Mock(); + _middleware = new HttpHeadersTransformationMiddleware( + _next, _loggerFactory.Object, _preReplacer.Object, + _postReplacer.Object, _addHeadersToResponse.Object, _addHeadersToRequest.Object); } [Fact] @@ -51,17 +55,24 @@ namespace Ocelot.UnitTests.Headers .And(x => GivenTheHttpResponseMessageIs()) .When(x => WhenICallTheMiddleware()) .Then(x => ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly()) + .Then(x => ThenAddHeadersToRequestIsCalledCorrectly()) .And(x => ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly()) - .And(x => ThenAddHeadersIsCalledCorrectly()) + .And(x => ThenAddHeadersToResponseIsCalledCorrectly()) .BDDfy(); } - private void ThenAddHeadersIsCalledCorrectly() + private void ThenAddHeadersToResponseIsCalledCorrectly() { - _addHeaders + _addHeadersToResponse .Verify(x => x.Add(_downstreamContext.DownstreamReRoute.AddHeadersToDownstream, _downstreamContext.DownstreamResponse), Times.Once); } + private void ThenAddHeadersToRequestIsCalledCorrectly() + { + _addHeadersToRequest + .Verify(x => x.SetHeadersOnDownstreamRequest(_downstreamContext.DownstreamReRoute.AddHeadersToUpstream, _downstreamContext.HttpContext), Times.Once); + } + private void WhenICallTheMiddleware() { _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); From 7ca828836a3dbcc531cb294df038b6ce34ab2bc4 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sat, 14 Apr 2018 07:02:17 +0100 Subject: [PATCH 36/50] #296 started writing merge code --- .../FileInternalConfigurationCreator.cs | 3 - .../ConfigurationBuilderExtensions.cs | 9 +++ .../FileConfigurationCreatorTests.cs | 64 ++++++++----------- .../ConfigurationBuilderExtensionsTests.cs | 60 +++++++++++++++++ 4 files changed, 96 insertions(+), 40 deletions(-) diff --git a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs index 46590f67..fa77402b 100644 --- a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs @@ -18,7 +18,6 @@ namespace Ocelot.Configuration.Creator ///
public class FileInternalConfigurationCreator : IInternalConfigurationCreator { - private readonly IOptions _options; private readonly IConfigurationValidator _configurationValidator; private readonly IOcelotLogger _logger; private readonly IClaimsToThingCreator _claimsToThingCreator; @@ -36,7 +35,6 @@ namespace Ocelot.Configuration.Creator private readonly IDownstreamAddressesCreator _downstreamAddressesCreator; public FileInternalConfigurationCreator( - IOptions options, IConfigurationValidator configurationValidator, IOcelotLoggerFactory loggerFactory, IClaimsToThingCreator claimsToThingCreator, @@ -62,7 +60,6 @@ namespace Ocelot.Configuration.Creator _requestIdKeyCreator = requestIdKeyCreator; _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; _authOptionsCreator = authOptionsCreator; - _options = options; _configurationValidator = configurationValidator; _logger = loggerFactory.CreateLogger(); _claimsToThingCreator = claimsToThingCreator; diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs index c5c5ee4d..807ba0f3 100644 --- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs +++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs @@ -18,5 +18,14 @@ namespace Ocelot.DependencyInjection builder.Add(memorySource); return builder; } + + public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder) + { + //var load all files with ocelot*.json + //merge these files into one + //save it as ocelot.json + builder.AddJsonFile("ocelot.json"); + return builder; + } } } diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 9581d348..5ff5e165 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -1,22 +1,18 @@ -using System.Collections.Generic; -using Castle.Components.DictionaryAdapter; -using Microsoft.Extensions.Options; -using Moq; -using Ocelot.Cache; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Validator; -using Ocelot.Logging; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration +namespace Ocelot.UnitTests.Configuration { - using System; + using System.Collections.Generic; + using Moq; + using Ocelot.Cache; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.Configuration.Creator; + using Ocelot.Configuration.File; + using Ocelot.Configuration.Validator; + using Ocelot.Logging; + using Ocelot.Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; using Ocelot.DependencyInjection; using Ocelot.Errors; using Ocelot.UnitTests.TestData; @@ -24,23 +20,22 @@ namespace Ocelot.UnitTests.Configuration public class FileConfigurationCreatorTests { - private readonly Mock> _fileConfig; private readonly Mock _validator; private Response _config; private FileConfiguration _fileConfiguration; private readonly Mock _logger; private readonly FileInternalConfigurationCreator _internalConfigurationCreator; - private Mock _claimsToThingCreator; - private Mock _authOptionsCreator; - private Mock _upstreamTemplatePatternCreator; - private Mock _requestIdKeyCreator; - private Mock _serviceProviderConfigCreator; - private Mock _qosOptionsCreator; - private Mock _fileReRouteOptionsCreator; - private Mock _rateLimitOptions; - private Mock _regionCreator; - private Mock _httpHandlerOptionsCreator; - private Mock _adminPath; + private readonly Mock _claimsToThingCreator; + private readonly Mock _authOptionsCreator; + private readonly Mock _upstreamTemplatePatternCreator; + private readonly Mock _requestIdKeyCreator; + private readonly Mock _serviceProviderConfigCreator; + private readonly Mock _qosOptionsCreator; + private readonly Mock _fileReRouteOptionsCreator; + private readonly Mock _rateLimitOptions; + private readonly Mock _regionCreator; + private readonly Mock _httpHandlerOptionsCreator; + private readonly Mock _adminPath; private readonly Mock _headerFindAndReplaceCreator; private readonly Mock _downstreamAddressesCreator; @@ -48,7 +43,6 @@ namespace Ocelot.UnitTests.Configuration { _logger = new Mock(); _validator = new Mock(); - _fileConfig = new Mock>(); _claimsToThingCreator = new Mock(); _authOptionsCreator = new Mock(); _upstreamTemplatePatternCreator = new Mock(); @@ -64,7 +58,6 @@ namespace Ocelot.UnitTests.Configuration _downstreamAddressesCreator = new Mock(); _internalConfigurationCreator = new FileInternalConfigurationCreator( - _fileConfig.Object, _validator.Object, _logger.Object, _claimsToThingCreator.Object, @@ -262,7 +255,7 @@ namespace Ocelot.UnitTests.Configuration .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheFollowingRegionIsReturned("region")) .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheRegionCreatorIsCalledCorrectly("region")) + .Then(x => x.ThenTheRegionCreatorIsCalledCorrectly()) .And(x => x.ThenTheHeaderFindAndReplaceCreatorIsCalledCorrectly()) .BDDfy(); } @@ -800,9 +793,6 @@ namespace Ocelot.UnitTests.Configuration private void GivenTheConfigIs(FileConfiguration fileConfiguration) { _fileConfiguration = fileConfiguration; - _fileConfig - .Setup(x => x.Value) - .Returns(_fileConfiguration); } private void WhenICreateTheConfig() @@ -927,7 +917,7 @@ namespace Ocelot.UnitTests.Configuration .Returns(region); } - private void ThenTheRegionCreatorIsCalledCorrectly(string expected) + private void ThenTheRegionCreatorIsCalledCorrectly() { _regionCreator .Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once); diff --git a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs index 5d32b1ed..ab20c941 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs @@ -6,6 +6,11 @@ using Xunit; namespace Ocelot.UnitTests.DependencyInjection { + using System.Collections.Generic; + using System.IO; + using Newtonsoft.Json; + using Ocelot.Configuration.File; + public class ConfigurationBuilderExtensionsTests { private IConfigurationRoot _configuration; @@ -20,6 +25,61 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } + [Fact] + public void should_merge_files() + { + var globalConfig = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + BaseUrl = "BaseUrl", + RateLimitOptions = new FileRateLimitOptions + { + HttpStatusCode = 500, + ClientIdHeader = "ClientIdHeader", + DisableRateLimitHeaders = true, + QuotaExceededMessage = "QuotaExceededMessage", + RateLimitCounterPrefix = "RateLimitCounterPrefix" + } + } + }; + + var reRoute = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamScheme = "DownstreamScheme", + Key = "Key", + UpstreamHost = "UpstreamHost", + UpstreamHttpMethod = new List + { + "UpstreamHttpMethod" + }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "Host", + Port = 80 + } + } + } + } + }; + + var globalJson = JsonConvert.SerializeObject(globalConfig); + //File.WriteAllText("ocelot.global.json", globalJson); + + var reRouteJson = JsonConvert.SerializeObject(reRoute); + //File.WriteAllText("ocelot.reRoute.json", reRouteJson); + + IConfigurationBuilder builder = new ConfigurationBuilder(); + //builder.AddOcelot(); + + } + private void GivenTheBaseUrl(string baseUrl) { #pragma warning disable CS0618 From 3ae2b286ab3fe97d87e718e33d70698bcb21dc11 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 15 Apr 2018 09:20:52 +0100 Subject: [PATCH 37/50] #296 coming up with ideas for this config merging --- .../ConfigurationBuilderExtensions.cs | 21 +++++++++++++++++++ .../ConfigurationBuilderExtensionsTests.cs | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs index 807ba0f3..31bced5e 100644 --- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs +++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs @@ -5,6 +5,12 @@ using Microsoft.Extensions.Configuration.Memory; namespace Ocelot.DependencyInjection { + using System.IO; + using System.Linq; + using System.Text.RegularExpressions; + using Configuration.File; + using Newtonsoft.Json; + public static class ConfigurationBuilderExtensions { [Obsolete("Please set BaseUrl in ocelot.json GlobalConfiguration.BaseUrl")] @@ -21,6 +27,21 @@ namespace Ocelot.DependencyInjection public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder) { + const string pattern = "(?i)ocelot(.*).json$"; + + var reg = new Regex(pattern); + + var files = Directory.GetFiles(".") + .Where(path => reg.IsMatch(path)) + .ToList(); + + foreach (var file in files) + { + var lines = File.ReadAllText(file); + var config = JsonConvert.DeserializeObject(lines); + + } + //var load all files with ocelot*.json //merge these files into one //save it as ocelot.json diff --git a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs index ab20c941..694a3224 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs @@ -70,13 +70,13 @@ namespace Ocelot.UnitTests.DependencyInjection }; var globalJson = JsonConvert.SerializeObject(globalConfig); - //File.WriteAllText("ocelot.global.json", globalJson); + File.WriteAllText("ocelot.global.json", globalJson); var reRouteJson = JsonConvert.SerializeObject(reRoute); - //File.WriteAllText("ocelot.reRoute.json", reRouteJson); + File.WriteAllText("ocelot.reRoute.json", reRouteJson); IConfigurationBuilder builder = new ConfigurationBuilder(); - //builder.AddOcelot(); + builder.AddOcelot(); } From c5aa11f7fb455da76d7ee4ad94ca52b4cbd2e8ed Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Sun, 15 Apr 2018 18:06:03 +0100 Subject: [PATCH 38/50] #296 still hacking this idea around --- .../ConfigurationBuilderExtensions.cs | 21 +++++++++++++++---- .../ConfigurationBuilderExtensionsTests.cs | 4 ++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs index 31bced5e..da3a0589 100644 --- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs +++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs @@ -32,19 +32,32 @@ namespace Ocelot.DependencyInjection var reg = new Regex(pattern); var files = Directory.GetFiles(".") - .Where(path => reg.IsMatch(path)) + .Where(path => reg.IsMatch(path)).Where(x => x.Count(s => s == '.') == 3) .ToList(); + FileConfiguration ocelotConfig = new FileConfiguration(); + foreach (var file in files) { + if(files.Count > 1 && file == "./ocelot.json") + { + continue; + } + var lines = File.ReadAllText(file); var config = JsonConvert.DeserializeObject(lines); + if(file == "./ocelot.global.json") + { + ocelotConfig.GlobalConfiguration = config.GlobalConfiguration; + } + + ocelotConfig.Aggregates.AddRange(config.Aggregates); + ocelotConfig.ReRoutes.AddRange(config.ReRoutes); } - //var load all files with ocelot*.json - //merge these files into one - //save it as ocelot.json + var json = JsonConvert.SerializeObject(ocelotConfig); + File.WriteAllText("ocelot.json", json); builder.AddJsonFile("ocelot.json"); return builder; } diff --git a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs index 694a3224..8c42b0dc 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs @@ -73,11 +73,11 @@ namespace Ocelot.UnitTests.DependencyInjection File.WriteAllText("ocelot.global.json", globalJson); var reRouteJson = JsonConvert.SerializeObject(reRoute); - File.WriteAllText("ocelot.reRoute.json", reRouteJson); + File.WriteAllText("ocelot.reRoutes.json", reRouteJson); IConfigurationBuilder builder = new ConfigurationBuilder(); builder.AddOcelot(); - + var configRoot = builder.Build(); } private void GivenTheBaseUrl(string baseUrl) From 2ed37cbc83b301290e855210c96a8648733dca6a Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Mon, 16 Apr 2018 20:28:15 +0100 Subject: [PATCH 39/50] #296 will now do a crappy merge on the configuration --- docs/features/configuration.rst | 59 +++++-- .../ConfigurationBuilderExtensions.cs | 4 + test/Ocelot.ManualTest/Program.cs | 6 - .../ConfigurationBuilderExtensionsTests.cs | 154 +++++++++++++++++- 4 files changed, 191 insertions(+), 32 deletions(-) diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 6bc05f66..0306c563 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -69,22 +69,6 @@ Here is an example ReRoute configuration, You don't need to set all of these thi More information on how to use these options is below.. -Follow Redirects / Use CookieContainer -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior: - -1. AllowAutoRedirect is a value that indicates whether the request should follow redirection responses. Set it true if the request should automatically -follow redirection responses from the Downstream resource; otherwise false. The default value is false. -2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer -property to store server cookies and uses these cookies when sending requests. The default value is false. Please note -that if you are using the CookieContainer Ocelot caches the HttpClient for each downstream service. This means that all requests -to that DownstreamService will share the same cookies. `Issue 274 `_ was created because a user -noticed that the cookies were being shared. I tried to think of a nice way to handle this but I think it is impossible. If you don't cache the clients -that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight -requests. This would also mean that subsequent requests dont use the cookies from the previous response! All in all not a great situation. I would avoid setting -UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request! - Multiple environments ^^^^^^^^^^^^^^^^^^^^^ @@ -104,10 +88,35 @@ to you .AddEnvironmentVariables(); }) -Ocelot should now use the environment specific configuration and fall back to ocelot.json if there isnt one. +Ocelot will now use the environment specific configuration and fall back to ocelot.json if there isnt one. You also need to set the corresponding environment variable which is ASPNETCORE_ENVIRONMENT. More info on this can be found in the `asp.net core docs `_. +Merging configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This feature was requested in `Issue 296 `_ and allows users to have multiple configuration files to make managing large configurations easier. + +Instead of adding the configuration directly e.g. AddJsonFile("ocelot.json") you can call AddOcelot() like below. + +.. code-block:: csharp + + .ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddOcelot() + .AddEnvironmentVariables(); + }) + +In this scenario Ocelot will look for any files that match the pattern ocleot.*.json and then merge these together. If you want to set the GlobalConfiguration property you must have a file called ocelot.global.json. + +The way Ocelot merges the files is basically load them, loop over them, add any ReRoutes, add any AggregateReRoutes and if the file is called ocelot.global.json add the GlobalConfiguration aswell as any ReRoutes or AggregateReRoutes. Ocelot will then save the merged configuration to a file called ocelot.json and this will be used as the source of truth while ocelot is running. + +At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration. This is something to be aware of when you are investigating problems. I would advise always checking what is in ocelot.json if you have any problems. + Store configuration in consul ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -135,3 +144,19 @@ I decided to create this feature after working on the raft consensus algorithm a I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now. This feature has a 3 second ttl cache before making a new request to your local consul agent. + +Follow Redirects / Use CookieContainer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior: + +1. AllowAutoRedirect is a value that indicates whether the request should follow redirection responses. Set it true if the request should automatically +follow redirection responses from the Downstream resource; otherwise false. The default value is false. +2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer +property to store server cookies and uses these cookies when sending requests. The default value is false. Please note +that if you are using the CookieContainer Ocelot caches the HttpClient for each downstream service. This means that all requests +to that DownstreamService will share the same cookies. `Issue 274 `_ was created because a user +noticed that the cookies were being shared. I tried to think of a nice way to handle this but I think it is impossible. If you don't cache the clients +that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight +requests. This would also mean that subsequent requests dont use the cookies from the previous response! All in all not a great situation. I would avoid setting +UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request! diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs index da3a0589..daa7b113 100644 --- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs +++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs @@ -45,6 +45,7 @@ namespace Ocelot.DependencyInjection } var lines = File.ReadAllText(file); + var config = JsonConvert.DeserializeObject(lines); if(file == "./ocelot.global.json") @@ -57,8 +58,11 @@ namespace Ocelot.DependencyInjection } var json = JsonConvert.SerializeObject(ocelotConfig); + File.WriteAllText("ocelot.json", json); + builder.AddJsonFile("ocelot.json"); + return builder; } } diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index 59a9a45f..966b1097 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -22,12 +22,6 @@ namespace Ocelot.ManualTest .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile("ocelot.json") - - //.AddOcelot(); - //load all the ocelot.xxx.json files that are not environments from asp.net core - //merge them into megaconfig - //save megaconfig to disk as ocelot.json - //then add to asp.net config stuff.. .AddEnvironmentVariables(); }) .ConfigureServices(s => { diff --git a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs index 8c42b0dc..67a1feab 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs @@ -15,20 +15,33 @@ namespace Ocelot.UnitTests.DependencyInjection { private IConfigurationRoot _configuration; private string _result; + private IConfigurationRoot _configRoot; + private FileConfiguration _globalConfig; + private FileConfiguration _reRoute; + private FileConfiguration _reRouteB; + private FileConfiguration _aggregate; [Fact] public void should_add_base_url_to_config() { - this.Given(x => GivenTheBaseUrl("test")) - .When(x => WhenIGet("BaseUrl")) - .Then(x => ThenTheResultIs("test")) + this.Given(_ => GivenTheBaseUrl("test")) + .When(_ => WhenIGet("BaseUrl")) + .Then(_ => ThenTheResultIs("test")) .BDDfy(); } [Fact] public void should_merge_files() { - var globalConfig = new FileConfiguration + this.Given(_ => GivenMultipleConfigurationFiles()) + .When(_ => WhenIAddOcelotConfiguration()) + .Then(_ => ThenTheConfigsAreMerged()) + .BDDfy(); + } + + private void GivenMultipleConfigurationFiles() + { + _globalConfig = new FileConfiguration { GlobalConfiguration = new FileGlobalConfiguration { @@ -40,17 +53,25 @@ namespace Ocelot.UnitTests.DependencyInjection DisableRateLimitHeaders = true, QuotaExceededMessage = "QuotaExceededMessage", RateLimitCounterPrefix = "RateLimitCounterPrefix" - } + }, + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "Host", + Port = 80, + Type = "Type" + }, + RequestIdKey = "RequestIdKey" } }; - var reRoute = new FileConfiguration + _reRoute = new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamScheme = "DownstreamScheme", + DownstreamPathTemplate = "DownstreamPathTemplate", Key = "Key", UpstreamHost = "UpstreamHost", UpstreamHttpMethod = new List @@ -69,15 +90,130 @@ namespace Ocelot.UnitTests.DependencyInjection } }; - var globalJson = JsonConvert.SerializeObject(globalConfig); + _reRouteB = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamScheme = "DownstreamSchemeB", + DownstreamPathTemplate = "DownstreamPathTemplateB", + Key = "KeyB", + UpstreamHost = "UpstreamHostB", + UpstreamHttpMethod = new List + { + "UpstreamHttpMethodB" + }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "HostB", + Port = 80 + } + } + }, + new FileReRoute + { + DownstreamScheme = "DownstreamSchemeBB", + DownstreamPathTemplate = "DownstreamPathTemplateBB", + Key = "KeyBB", + UpstreamHost = "UpstreamHostBB", + UpstreamHttpMethod = new List + { + "UpstreamHttpMethodBB" + }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "HostBB", + Port = 80 + } + } + } + } + }; + + _aggregate = new FileConfiguration + { + Aggregates = new List + { + new FileAggregateReRoute + { + ReRouteKeys = new List + { + "KeyB", + "KeyBB" + }, + UpstreamPathTemplate = "UpstreamPathTemplate", + }, + new FileAggregateReRoute + { + ReRouteKeys = new List + { + "KeyB", + "KeyBB" + }, + UpstreamPathTemplate = "UpstreamPathTemplate", + } + } + }; + + var globalJson = JsonConvert.SerializeObject(_globalConfig); File.WriteAllText("ocelot.global.json", globalJson); - var reRouteJson = JsonConvert.SerializeObject(reRoute); + var reRouteJson = JsonConvert.SerializeObject(_reRoute); File.WriteAllText("ocelot.reRoutes.json", reRouteJson); + var reRouteJsonB = JsonConvert.SerializeObject(_reRouteB); + File.WriteAllText("ocelot.reRoutesB.json", reRouteJsonB); + + var aggregates = JsonConvert.SerializeObject(_aggregate); + File.WriteAllText("ocelot.aggregates.json", aggregates); + } + + private void WhenIAddOcelotConfiguration() + { IConfigurationBuilder builder = new ConfigurationBuilder(); builder.AddOcelot(); - var configRoot = builder.Build(); + _configRoot = builder.Build(); + } + + private void ThenTheConfigsAreMerged() + { + var fc = (FileConfiguration)_configRoot.Get(typeof(FileConfiguration)); + + fc.GlobalConfiguration.BaseUrl.ShouldBe(_globalConfig.GlobalConfiguration.BaseUrl); + fc.GlobalConfiguration.RateLimitOptions.ClientIdHeader.ShouldBe(_globalConfig.GlobalConfiguration.RateLimitOptions.ClientIdHeader); + fc.GlobalConfiguration.RateLimitOptions.DisableRateLimitHeaders.ShouldBe(_globalConfig.GlobalConfiguration.RateLimitOptions.DisableRateLimitHeaders); + fc.GlobalConfiguration.RateLimitOptions.HttpStatusCode.ShouldBe(_globalConfig.GlobalConfiguration.RateLimitOptions.HttpStatusCode); + fc.GlobalConfiguration.RateLimitOptions.QuotaExceededMessage.ShouldBe(_globalConfig.GlobalConfiguration.RateLimitOptions.QuotaExceededMessage); + fc.GlobalConfiguration.RateLimitOptions.RateLimitCounterPrefix.ShouldBe(_globalConfig.GlobalConfiguration.RateLimitOptions.RateLimitCounterPrefix); + fc.GlobalConfiguration.RequestIdKey.ShouldBe(_globalConfig.GlobalConfiguration.RequestIdKey); + fc.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(_globalConfig.GlobalConfiguration.ServiceDiscoveryProvider.Host); + fc.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(_globalConfig.GlobalConfiguration.ServiceDiscoveryProvider.Port); + fc.GlobalConfiguration.ServiceDiscoveryProvider.Type.ShouldBe(_globalConfig.GlobalConfiguration.ServiceDiscoveryProvider.Type); + + fc.ReRoutes.Count.ShouldBe(_reRoute.ReRoutes.Count + _reRouteB.ReRoutes.Count); + + fc.ReRoutes.ShouldContain(x => x.DownstreamPathTemplate == _reRoute.ReRoutes[0].DownstreamPathTemplate); + fc.ReRoutes.ShouldContain(x => x.DownstreamPathTemplate == _reRouteB.ReRoutes[0].DownstreamPathTemplate); + fc.ReRoutes.ShouldContain(x => x.DownstreamPathTemplate == _reRouteB.ReRoutes[1].DownstreamPathTemplate); + + fc.ReRoutes.ShouldContain(x => x.DownstreamScheme == _reRoute.ReRoutes[0].DownstreamScheme); + fc.ReRoutes.ShouldContain(x => x.DownstreamScheme == _reRouteB.ReRoutes[0].DownstreamScheme); + fc.ReRoutes.ShouldContain(x => x.DownstreamScheme == _reRouteB.ReRoutes[1].DownstreamScheme); + + fc.ReRoutes.ShouldContain(x => x.Key == _reRoute.ReRoutes[0].Key); + fc.ReRoutes.ShouldContain(x => x.Key == _reRouteB.ReRoutes[0].Key); + fc.ReRoutes.ShouldContain(x => x.Key == _reRouteB.ReRoutes[1].Key); + + fc.ReRoutes.ShouldContain(x => x.UpstreamHost == _reRoute.ReRoutes[0].UpstreamHost); + fc.ReRoutes.ShouldContain(x => x.UpstreamHost == _reRouteB.ReRoutes[0].UpstreamHost); + fc.ReRoutes.ShouldContain(x => x.UpstreamHost == _reRouteB.ReRoutes[1].UpstreamHost); + + fc.Aggregates.Count.ShouldBe(_aggregate.Aggregates.Count); } private void GivenTheBaseUrl(string baseUrl) From 3607c0867e6159849ce235c0b6b3c461c3dddf1c Mon Sep 17 00:00:00 2001 From: Louis B Date: Tue, 17 Apr 2018 20:27:16 +0200 Subject: [PATCH 40/50] Update middlewareinjection.rst (#317) Class name "OcelotMiddlewareConfiguration" does not exist and "UseOcelot" extension method expects a "OcelotPipelineConfiguration" so i think this documentaiton is outdated. --- docs/features/middlewareinjection.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/features/middlewareinjection.rst b/docs/features/middlewareinjection.rst index f25cfa26..44f44fb3 100644 --- a/docs/features/middlewareinjection.rst +++ b/docs/features/middlewareinjection.rst @@ -9,7 +9,7 @@ and override middleware. This is done as follos. .. code-block:: csharp - var configuration = new OcelotMiddlewareConfiguration + var configuration = new OcelotPipelineConfiguration { PreErrorResponderMiddleware = async (ctx, next) => { @@ -38,4 +38,4 @@ The user can set functions against the following. * PreQueryStringBuilderMiddleware - This alows the user to manipulate the query string on the http request before it is passed to Ocelots request creator. Obviously you can just add middleware as normal before the call to app.UseOcelot() It cannot be added -after as Ocelot does not call the next middleware. \ No newline at end of file +after as Ocelot does not call the next middleware. From e94df4749c316692f6c279243c4ad289b155ce63 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Tue, 17 Apr 2018 21:51:41 +0100 Subject: [PATCH 41/50] #296 change so tests pass on windows --- .../ConfigurationBuilderExtensions.cs | 35 +++++++++++-------- .../FileConfigurationRepositoryTests.cs | 11 ++---- .../HeaderFindAndReplaceCreatorTests.cs | 1 + .../ConfigurationBuilderExtensionsTests.cs | 14 ++++---- 4 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs index daa7b113..00cb0578 100644 --- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs +++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs @@ -1,10 +1,9 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Configuration.Memory; - namespace Ocelot.DependencyInjection { + using System; + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Configuration.Memory; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -16,12 +15,16 @@ namespace Ocelot.DependencyInjection [Obsolete("Please set BaseUrl in ocelot.json GlobalConfiguration.BaseUrl")] public static IConfigurationBuilder AddOcelotBaseUrl(this IConfigurationBuilder builder, string baseUrl) { - var memorySource = new MemoryConfigurationSource(); - memorySource.InitialData = new List> + var memorySource = new MemoryConfigurationSource { - new KeyValuePair("BaseUrl", baseUrl) + InitialData = new List> + { + new KeyValuePair("BaseUrl", baseUrl) + } }; + builder.Add(memorySource); + return builder; } @@ -35,11 +38,12 @@ namespace Ocelot.DependencyInjection .Where(path => reg.IsMatch(path)).Where(x => x.Count(s => s == '.') == 3) .ToList(); - FileConfiguration ocelotConfig = new FileConfiguration(); + var fileConfiguration = new FileConfiguration(); foreach (var file in files) { - if(files.Count > 1 && file == "./ocelot.json") + // windows and unix sigh... + if(files.Count > 1 && (file == "./ocelot.json" || file == ".\\ocelot.json")) { continue; } @@ -48,16 +52,17 @@ namespace Ocelot.DependencyInjection var config = JsonConvert.DeserializeObject(lines); - if(file == "./ocelot.global.json") + // windows and unix sigh... + if (file == "./ocelot.global.json" || file == ".\\ocelot.global.json") { - ocelotConfig.GlobalConfiguration = config.GlobalConfiguration; + fileConfiguration.GlobalConfiguration = config.GlobalConfiguration; } - ocelotConfig.Aggregates.AddRange(config.Aggregates); - ocelotConfig.ReRoutes.AddRange(config.ReRoutes); + fileConfiguration.Aggregates.AddRange(config.Aggregates); + fileConfiguration.ReRoutes.AddRange(config.ReRoutes); } - var json = JsonConvert.SerializeObject(ocelotConfig); + var json = JsonConvert.SerializeObject(fileConfiguration); File.WriteAllText("ocelot.json", json); diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs index afff32f9..5b0e93db 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs @@ -12,14 +12,14 @@ using Ocelot.Configuration.Repository; namespace Ocelot.UnitTests.Configuration { - public class FileConfigurationRepositoryTests : IDisposable + public class FileConfigurationRepositoryTests { private readonly Mock _hostingEnvironment = new Mock(); private IFileConfigurationRepository _repo; private FileConfiguration _result; private FileConfiguration _fileConfiguration; - - // This is a bit dirty and it is dev.dev so that the configuration tests + + // This is a bit dirty and it is dev.dev so that the ConfigurationBuilderExtensionsTests // cant pick it up if they run in parralel..sigh these are not really unit // tests but whatever... private string _environmentName = "DEV.DEV"; @@ -225,10 +225,5 @@ namespace Ocelot.UnitTests.Configuration ReRoutes = reRoutes }; } - - public void Dispose() - { - File.Delete($"./ocelot.{_environmentName}.json"); - } } } diff --git a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs index df37ca8b..a8f8644a 100644 --- a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs @@ -149,6 +149,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => ThenTheFollowingDownstreamIsReturned(downstream)) .BDDfy(); } + [Fact] public void should_add_trace_id_header() { diff --git a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs index 90018b3e..7c29977d 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs @@ -1,16 +1,14 @@ -using Microsoft.Extensions.Configuration; -using Ocelot.DependencyInjection; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.DependencyInjection +namespace Ocelot.UnitTests.DependencyInjection { - using System; using System.Collections.Generic; using System.IO; using Newtonsoft.Json; using Ocelot.Configuration.File; + using Microsoft.Extensions.Configuration; + using Ocelot.DependencyInjection; + using Shouldly; + using TestStack.BDDfy; + using Xunit; public class ConfigurationBuilderExtensionsTests { From aa55fe34cba400b4e0416441661ccab099cc4575 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Tue, 17 Apr 2018 22:06:41 +0100 Subject: [PATCH 42/50] Feature/merge configuration files (#316) * #296 renamed configuration.json to ocelot.json in preparation * removed things we dont need for tests * another file we dont need * removed some async we dont need * refactoring to consolidate configuration code * removed another pointless abstraction * #296 started writing merge code * #296 coming up with ideas for this config merging * #296 still hacking this idea around * #296 will now do a crappy merge on the configuration * #296 change so tests pass on windows --- .gitignore | 4 +- docs/features/configuration.rst | 65 ++++-- docs/features/delegatinghandlers.rst | 8 +- docs/features/headerstransformation.rst | 8 +- docs/features/loadbalancer.rst | 2 +- docs/features/ratelimiting.rst | 2 +- docs/features/requestaggregation.rst | 4 +- docs/features/requestid.rst | 4 +- docs/features/routing.rst | 4 +- docs/features/tracing.rst | 2 +- docs/features/websockets.rst | 2 +- docs/introduction/gettingstarted.rst | 8 +- docs/introduction/notsupported.rst | 8 +- samples/OcelotGraphQL/OcelotGraphQL.csproj | 2 +- samples/OcelotGraphQL/Program.cs | 2 +- samples/OcelotGraphQL/README.md | 2 +- .../{configuration.json => ocelot.json} | 0 samples/OcelotServiceFabric/.gitignore | 2 +- .../OcelotApplicationApiGateway.csproj | 2 +- .../WebCommunicationListener.cs | 2 +- .../{configuration.json => ocelot.json} | 0 src/Ocelot/Cache/OutputCacheController.cs | 8 +- .../Authentication/HashMatcher.cs | 22 -- .../Authentication/IHashMatcher.cs | 7 - ...cs => FileInternalConfigurationCreator.cs} | 21 +- .../Creator/IInternalConfigurationCreator.cs | 11 + .../Creator/IOcelotConfigurationCreator.cs | 11 - .../IdentityServerConfigurationCreator.cs | 3 - .../FileConfigurationController.cs | 24 +- .../IIdentityServerConfiguration.cs | 30 ++- ...iguration.cs => IInternalConfiguration.cs} | 24 +- .../IdentityServerConfiguration.cs | 62 +++--- ...figuration.cs => InternalConfiguration.cs} | 40 ++-- .../Provider/FileConfigurationProvider.cs | 26 --- .../Provider/IFileConfigurationProvider.cs | 11 - .../Provider/IOcelotConfigurationProvider.cs | 11 - .../Provider/OcelotConfigurationProvider.cs | 32 --- .../ConsulFileConfigurationRepository.cs | 51 +++-- ....cs => DiskFileConfigurationRepository.cs} | 106 ++++----- .../IInternalConfigurationRepository.cs | 10 + .../IOcelotConfigurationRepository.cs | 12 - ...InMemoryInternalConfigurationRepository.cs | 29 +++ .../InMemoryOcelotConfigurationRepository.cs | 30 --- ... => FileAndInternalConfigurationSetter.cs} | 86 ++++---- .../ConfigurationBuilderExtensions.cs | 70 +++++- .../DependencyInjection/OcelotBuilder.cs | 47 +--- .../Finder/DownstreamRouteFinder.cs | 2 +- .../Finder/IDownstreamRouteFinder.cs | 2 +- .../DownstreamRouteFinderMiddleware.cs | 11 +- .../Middleware/ExceptionHandlerMiddleware.cs | 19 +- .../Middleware/OcelotMiddlewareExtensions.cs | 163 +++++++------- src/Ocelot/Raft/FilePeersProvider.cs | 15 +- src/Ocelot/Raft/HttpPeer.cs | 18 +- .../ConfigurationInConsulTests.cs | 6 +- .../CustomMiddlewareTests.cs | 2 +- .../Ocelot.AcceptanceTests.csproj | 2 +- test/Ocelot.AcceptanceTests/Steps.cs | 26 +-- .../TestConfiguration.cs | 2 +- .../Ocelot.AcceptanceTests/configuration.json | 59 ----- .../AdministrationTests.cs | 12 +- .../Ocelot.IntegrationTests.csproj | 2 +- test/Ocelot.IntegrationTests/RaftTests.cs | 6 +- .../ThreadSafeHeadersTests.cs | 6 +- .../{configuration.json => ocelot.json} | 0 .../Ocelot.ManualTest.csproj | 2 +- test/Ocelot.ManualTest/Program.cs | 2 +- .../{configuration.json => ocelot.json} | 0 .../FileConfigurationCreatorTests.cs | 72 +++--- .../FileConfigurationProviderTests.cs | 60 ----- .../FileConfigurationRepositoryTests.cs | 12 +- .../FileConfigurationSetterTests.cs | 22 +- .../Configuration/HashMatcherTests.cs | 76 ------- .../HeaderFindAndReplaceCreatorTests.cs | 1 + .../InMemoryConfigurationRepositoryTests.cs | 18 +- .../OcelotConfigurationProviderTests.cs | 80 ------- .../FileConfigurationControllerTests.cs | 28 ++- .../ConfigurationBuilderExtensionsTests.cs | 208 +++++++++++++++++- .../DependencyInjection/OcelotBuilderTests.cs | 4 +- .../DownstreamRouteFinderMiddlewareTests.cs | 31 ++- .../DownstreamRouteFinderTests.cs | 6 +- .../Errors/ExceptionHandlerMiddlewareTests.cs | 34 +-- test/Ocelot.UnitTests/Ocelot.UnitTests.csproj | 6 - test/Ocelot.UnitTests/configuration.json | 1 - test/Ocelot.UnitTests/idsrv3test.pfx | Bin 3395 -> 0 bytes 84 files changed, 883 insertions(+), 1050 deletions(-) rename samples/OcelotGraphQL/{configuration.json => ocelot.json} (100%) rename samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/{configuration.json => ocelot.json} (100%) delete mode 100644 src/Ocelot/Configuration/Authentication/HashMatcher.cs delete mode 100644 src/Ocelot/Configuration/Authentication/IHashMatcher.cs rename src/Ocelot/Configuration/Creator/{FileOcelotConfigurationCreator.cs => FileInternalConfigurationCreator.cs} (90%) create mode 100644 src/Ocelot/Configuration/Creator/IInternalConfigurationCreator.cs delete mode 100644 src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs rename src/Ocelot/Configuration/{Provider => }/IIdentityServerConfiguration.cs (67%) rename src/Ocelot/Configuration/{IOcelotConfiguration.cs => IInternalConfiguration.cs} (83%) rename src/Ocelot/Configuration/{Provider => }/IdentityServerConfiguration.cs (66%) rename src/Ocelot/Configuration/{OcelotConfiguration.cs => InternalConfiguration.cs} (67%) delete mode 100644 src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs delete mode 100644 src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs delete mode 100644 src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs delete mode 100644 src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs rename src/Ocelot/Configuration/Repository/{FileConfigurationRepository.cs => DiskFileConfigurationRepository.cs} (75%) create mode 100644 src/Ocelot/Configuration/Repository/IInternalConfigurationRepository.cs delete mode 100644 src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs create mode 100644 src/Ocelot/Configuration/Repository/InMemoryInternalConfigurationRepository.cs delete mode 100644 src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs rename src/Ocelot/Configuration/Setter/{FileConfigurationSetter.cs => FileAndInternalConfigurationSetter.cs} (63%) delete mode 100755 test/Ocelot.AcceptanceTests/configuration.json rename test/Ocelot.IntegrationTests/{configuration.json => ocelot.json} (100%) mode change 100755 => 100644 rename test/Ocelot.ManualTest/{configuration.json => ocelot.json} (100%) delete mode 100644 test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs delete mode 100644 test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs delete mode 100644 test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs delete mode 100755 test/Ocelot.UnitTests/configuration.json delete mode 100644 test/Ocelot.UnitTests/idsrv3test.pfx diff --git a/.gitignore b/.gitignore index 836e0bfe..476938f0 100644 --- a/.gitignore +++ b/.gitignore @@ -243,7 +243,7 @@ tools/ .DS_Store # Ocelot acceptance test config -test/Ocelot.AcceptanceTests/configuration.json +test/Ocelot.AcceptanceTests/ocelot.json # Read the docstates _build/ @@ -251,4 +251,4 @@ _static/ _templates/ # JetBrains Rider -.idea/ \ No newline at end of file +.idea/ diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 8a1e61a1..0306c563 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -1,7 +1,7 @@ Configuration ============ -An example configuration can be found `here `_. +An example configuration can be found `here `_. There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration. The ReRoutes are the objects that tell Ocelot how to treat an upstream request. The Global configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful @@ -69,22 +69,6 @@ Here is an example ReRoute configuration, You don't need to set all of these thi More information on how to use these options is below.. -Follow Redirects / Use CookieContainer -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior: - -1. AllowAutoRedirect is a value that indicates whether the request should follow redirection responses. Set it true if the request should automatically -follow redirection responses from the Downstream resource; otherwise false. The default value is false. -2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer -property to store server cookies and uses these cookies when sending requests. The default value is false. Please note -that if you are using the CookieContainer Ocelot caches the HttpClient for each downstream service. This means that all requests -to that DownstreamService will share the same cookies. `Issue 274 `_ was created because a user -noticed that the cookies were being shared. I tried to think of a nice way to handle this but I think it is impossible. If you don't cache the clients -that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight -requests. This would also mean that subsequent requests dont use the cookies from the previous response! All in all not a great situation. I would avoid setting -UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request! - Multiple environments ^^^^^^^^^^^^^^^^^^^^^ @@ -99,15 +83,40 @@ to you .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("configuration.json") + .AddJsonFile("ocelot.json") .AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json") .AddEnvironmentVariables(); }) -Ocelot should now use the environment specific configuration and fall back to configuration.json if there isnt one. +Ocelot will now use the environment specific configuration and fall back to ocelot.json if there isnt one. You also need to set the corresponding environment variable which is ASPNETCORE_ENVIRONMENT. More info on this can be found in the `asp.net core docs `_. +Merging configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This feature was requested in `Issue 296 `_ and allows users to have multiple configuration files to make managing large configurations easier. + +Instead of adding the configuration directly e.g. AddJsonFile("ocelot.json") you can call AddOcelot() like below. + +.. code-block:: csharp + + .ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddOcelot() + .AddEnvironmentVariables(); + }) + +In this scenario Ocelot will look for any files that match the pattern ocleot.*.json and then merge these together. If you want to set the GlobalConfiguration property you must have a file called ocelot.global.json. + +The way Ocelot merges the files is basically load them, loop over them, add any ReRoutes, add any AggregateReRoutes and if the file is called ocelot.global.json add the GlobalConfiguration aswell as any ReRoutes or AggregateReRoutes. Ocelot will then save the merged configuration to a file called ocelot.json and this will be used as the source of truth while ocelot is running. + +At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration. This is something to be aware of when you are investigating problems. I would advise always checking what is in ocelot.json if you have any problems. + Store configuration in consul ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -119,7 +128,7 @@ If you add the following when you register your services Ocelot will attempt to .AddOcelot() .AddStoreOcelotConfigurationInConsul(); -You also need to add the following to your configuration.json. This is how Ocelot +You also need to add the following to your ocelot.json. This is how Ocelot finds your Consul agent and interacts to load and store the configuration from Consul. .. code-block:: json @@ -135,3 +144,19 @@ I decided to create this feature after working on the raft consensus algorithm a I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now. This feature has a 3 second ttl cache before making a new request to your local consul agent. + +Follow Redirects / Use CookieContainer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior: + +1. AllowAutoRedirect is a value that indicates whether the request should follow redirection responses. Set it true if the request should automatically +follow redirection responses from the Downstream resource; otherwise false. The default value is false. +2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer +property to store server cookies and uses these cookies when sending requests. The default value is false. Please note +that if you are using the CookieContainer Ocelot caches the HttpClient for each downstream service. This means that all requests +to that DownstreamService will share the same cookies. `Issue 274 `_ was created because a user +noticed that the cookies were being shared. I tried to think of a nice way to handle this but I think it is impossible. If you don't cache the clients +that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight +requests. This would also mean that subsequent requests dont use the cookies from the previous response! All in all not a great situation. I would avoid setting +UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request! diff --git a/docs/features/delegatinghandlers.rst b/docs/features/delegatinghandlers.rst index 80dfa16b..445a46fb 100644 --- a/docs/features/delegatinghandlers.rst +++ b/docs/features/delegatinghandlers.rst @@ -40,7 +40,7 @@ Or transient as below... .AddTransientDelegatingHandler() Both of these Add methods have a default parameter called global which is set to false. If it is false then the intent of -the DelegatingHandler is to be applied to specific ReRoutes via configuration.json (more on that later). If it is set to true +the DelegatingHandler is to be applied to specific ReRoutes via ocelot.json (more on that later). If it is set to true then it becomes a global handler and will be applied to all ReRoutes. e.g. @@ -58,7 +58,7 @@ Or transient as below... .AddTransientDelegatingHandler(true) Finally if you want ReRoute specific DelegatingHandlers or to order your specific and / or global (more on this later) DelegatingHandlers -then you must add the following json to the specific ReRoute in configuration.json. The names in the array must match the class names of your +then you must add the following json to the specific ReRoute in ocelot.json. The names in the array must match the class names of your DelegatingHandlers for Ocelot to match them together. .. code-block:: json @@ -70,8 +70,8 @@ DelegatingHandlers for Ocelot to match them together. You can have as many DelegatingHandlers as you want and they are run in the following order: -1. Any globals that are left in the order they were added to services and are not in the DelegatingHandlers array from configuration.json. -2. Any non global DelegatingHandlers plus any globals that were in the DelegatingHandlers array from configuration.json ordered as they are in the DelegatingHandlers array. +1. Any globals that are left in the order they were added to services and are not in the DelegatingHandlers array from ocelot.json. +2. Any non global DelegatingHandlers plus any globals that were in the DelegatingHandlers array from ocelot.json ordered as they are in the DelegatingHandlers array. 3. Tracing DelegatingHandler if enabled (see tracing docs). 4. QoS DelegatingHandler if enabled (see QoS docs). 5. The HttpClient sends the HttpRequestMessage. diff --git a/docs/features/headerstransformation.rst b/docs/features/headerstransformation.rst index 544ae2b0..744227e2 100644 --- a/docs/features/headerstransformation.rst +++ b/docs/features/headerstransformation.rst @@ -8,7 +8,7 @@ Add to Request This feature was requestes in `GitHub #313 `_. -If you want to add a header to your upstream request please add the following to a ReRoute in your configuration.json: +If you want to add a header to your upstream request please add the following to a ReRoute in your ocelot.json: .. code-block:: json @@ -25,7 +25,7 @@ Add to Response This feature was requested in `GitHub #280 `_. -If you want to add a header to your downstream response please add the following to a ReRoute in configuration.json. +If you want to add a header to your downstream response please add the following to a ReRoute in ocelot.json.. .. code-block:: json @@ -57,7 +57,7 @@ The key is "Test" and the value is "http://www.bbc.co.uk/, http://ocelot.com/". Pre Downstream Request ^^^^^^^^^^^^^^^^^^^^^^ -Add the following to a ReRoute in configuration.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This header will be changed before the request downstream and will be sent to the downstream server. +Add the following to a ReRoute in ocelot.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This header will be changed before the request downstream and will be sent to the downstream server. .. code-block:: json @@ -68,7 +68,7 @@ Add the following to a ReRoute in configuration.json in order to replace http:// Post Downstream Request ^^^^^^^^^^^^^^^^^^^^^^^ -Add the following to a ReRoute in configuration.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This transformation will take place after Ocelot has received the response from the downstream service. +Add the following to a ReRoute in ocelot.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This transformation will take place after Ocelot has received the response from the downstream service. .. code-block:: json diff --git a/docs/features/loadbalancer.rst b/docs/features/loadbalancer.rst index f587aa22..c2cc0f26 100644 --- a/docs/features/loadbalancer.rst +++ b/docs/features/loadbalancer.rst @@ -16,7 +16,7 @@ You must choose in your configuration which load balancer to use. Configuration ^^^^^^^^^^^^^ -The following shows how to set up multiple downstream services for a ReRoute using configuration.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up. +The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up. .. code-block:: json diff --git a/docs/features/ratelimiting.rst b/docs/features/ratelimiting.rst index d929c905..7f9c0768 100644 --- a/docs/features/ratelimiting.rst +++ b/docs/features/ratelimiting.rst @@ -23,7 +23,7 @@ Period - This value specifies the period, such as 1s, 5m, 1h,1d and so on. PeriodTimespan - This value specifies that we can retry after a certain number of seconds. Limit - This value specifies the maximum number of requests that a client can make in a defined period. -You can also set the following in the GlobalConfiguration part of configuration.json +You can also set the following in the GlobalConfiguration part of ocelot.json .. code-block:: json diff --git a/docs/features/requestaggregation.rst b/docs/features/requestaggregation.rst index c3c7ad13..a5ca30f6 100644 --- a/docs/features/requestaggregation.rst +++ b/docs/features/requestaggregation.rst @@ -7,7 +7,7 @@ architecture with Ocelot. This feature was requested as part of `Issue 79 `_ and further improvements were made as part of `Issue 298 `_. -In order to set this up you must do something like the following in your configuration.json. Here we have specified two normal ReRoutes and each one has a Key property. +In order to set this up you must do something like the following in your ocelot.json. Here we have specified two normal ReRoutes and each one has a Key property. We then specify an Aggregate that composes the two ReRoutes using their keys in the ReRouteKeys list and says then we have the UpstreamPathTemplate which works like a normal ReRoute. Obviously you cannot have duplicate UpstreamPathTemplates between ReRoutes and Aggregates. You can use all of Ocelot's normal ReRoute options apart from RequestIdKey (explained in gotchas below). @@ -17,7 +17,7 @@ Advanced register your own Aggregators Ocelot started with just the basic request aggregation and since then we have added a more advanced method that let's the user take in the responses from the downstream services and then aggregate them into a response object. -The configuration.json setup is pretty much the same as the basic aggregation approach apart from you need to add an Aggregator property like below. +The ocelot.json setup is pretty much the same as the basic aggregation approach apart from you need to add an Aggregator property like below. .. code-block:: json diff --git a/docs/features/requestid.rst b/docs/features/requestid.rst index 6e4f239b..37752eda 100644 --- a/docs/features/requestid.rst +++ b/docs/features/requestid.rst @@ -12,7 +12,7 @@ In order to use the reques tid feature you have two options. *Global* -In your configuration.json set the following in the GlobalConfiguration section. This will be used for all requests into Ocelot. +In your ocelot.json set the following in the GlobalConfiguration section. This will be used for all requests into Ocelot. .. code-block:: json @@ -24,7 +24,7 @@ I reccomend using the GlobalConfiguration unless you really need it to be ReRout *ReRoute* -If you want to override this for a specific ReRoute add the following to configuration.json for the specific ReRoute. +If you want to override this for a specific ReRoute add the following to ocelot.json for the specific ReRoute. .. code-block:: json diff --git a/docs/features/routing.rst b/docs/features/routing.rst index b414d6c5..664021a6 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -145,9 +145,9 @@ Priority ^^^^^^^^ In `Issue 270 `_ I finally decided to expose the ReRoute priority in -configuration.json. This means you can decide in what order you want your ReRoutes to match the Upstream HttpRequest. +ocelot.json. This means you can decide in what order you want your ReRoutes to match the Upstream HttpRequest. -In order to get this working add the following to a ReRoute in configuration.json, 0 is just an example value here but will explain below. +In order to get this working add the following to a ReRoute in ocelot.json, 0 is just an example value here but will explain below. .. code-block:: json diff --git a/docs/features/tracing.rst b/docs/features/tracing.rst index 0a896d45..aa4b1fd3 100644 --- a/docs/features/tracing.rst +++ b/docs/features/tracing.rst @@ -20,7 +20,7 @@ In your ConfigureServices method option.Service = "Ocelot"; }); -Then in your configuration.json add the following to the ReRoute you want to trace.. +Then in your ocelot.json add the following to the ReRoute you want to trace.. .. code-block:: json diff --git a/docs/features/websockets.rst b/docs/features/websockets.rst index 828d1051..624f42a9 100644 --- a/docs/features/websockets.rst +++ b/docs/features/websockets.rst @@ -15,7 +15,7 @@ In your Configure method you need to tell your application to use WebSockets. app.UseOcelot().Wait(); }) -Then in your configuration.json add the following to proxy a ReRoute using websockets. +Then in your ocelot.json add the following to proxy a ReRoute using websockets. .. code-block:: json diff --git a/docs/introduction/gettingstarted.rst b/docs/introduction/gettingstarted.rst index 7f81cb51..0c11f40d 100644 --- a/docs/introduction/gettingstarted.rst +++ b/docs/introduction/gettingstarted.rst @@ -18,7 +18,7 @@ All versions can be found `here `_. **Configuration** -The following is a very basic configuration.json. It won't do anything but should get Ocelot starting. +The following is a very basic ocelot.json. It won't do anything but should get Ocelot starting. .. code-block:: json @@ -55,7 +55,7 @@ AddOcelot() (adds ocelot services), UseOcelot().Wait() (sets up all the Ocelot m .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("configuration.json") + .AddJsonFile("ocelot.json") .AddEnvironmentVariables(); }) .ConfigureServices(s => { @@ -87,7 +87,7 @@ All versions can be found `here `_. **Configuration** -The following is a very basic configuration.json. It won't do anything but should get Ocelot starting. +The following is a very basic ocelot.json. It won't do anything but should get Ocelot starting. .. code-block:: json @@ -135,7 +135,7 @@ An example startup using a json file for configuration can be seen below. .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddJsonFile("configuration.json") + .AddJsonFile("ocelot.json") .AddEnvironmentVariables(); Configuration = builder.Build(); diff --git a/docs/introduction/notsupported.rst b/docs/introduction/notsupported.rst index 37e5b5eb..e578bca2 100644 --- a/docs/introduction/notsupported.rst +++ b/docs/introduction/notsupported.rst @@ -7,7 +7,7 @@ Ocelot does not support... * Fowarding a host header - The host header that you send to Ocelot will not be forwarded to the downstream service. Obviously this would break everything :( -* Swagger - I have looked multiple times at building swagger.json out of the Ocelot configuration.json but it doesnt fit into the vision +* Swagger - I have looked multiple times at building swagger.json out of the Ocelot ocelot.json but it doesnt fit into the vision I have for Ocelot. If you would like to have Swagger in Ocelot then you must roll your own swagger.json and do the following in your Startup.cs or Program.cs. The code sample below registers a piece of middleware that loads your hand rolled swagger.json and returns it on /swagger/v1/swagger.json. It then registers the SwaggerUI middleware from Swashbuckle.AspNetCore @@ -28,8 +28,8 @@ it on /swagger/v1/swagger.json. It then registers the SwaggerUI middleware from app.UseOcelot().Wait(); -The main reasons why I don't think Swagger makes sense is we already hand roll our definition in configuration.json. -If we want people developing against Ocelot to be able to see what routes are available then either share the configuration.json +The main reasons why I don't think Swagger makes sense is we already hand roll our definition in ocelot.json. +If we want people developing against Ocelot to be able to see what routes are available then either share the ocelot.json with them (This should be as easy as granting access to a repo etc) or use the Ocelot administration API so that they can query Ocelot for the configuration. In addition to this many people will configure Ocelot to proxy all traffic like /products/{everything} to there product service @@ -40,4 +40,4 @@ package doesnt reload swagger.json if it changes during runtime. Ocelot's config information would not match. Unless I rolled my own Swagger implementation. If the user wants something to easily test against the Ocelot API then I suggest using Postman as a simple way to do this. It might -even be possible to write something that maps configuration.json to the postman json spec. However I don't intend to do this. \ No newline at end of file +even be possible to write something that maps ocelot.json to the postman json spec. However I don't intend to do this. \ No newline at end of file diff --git a/samples/OcelotGraphQL/OcelotGraphQL.csproj b/samples/OcelotGraphQL/OcelotGraphQL.csproj index 2f29e395..ec015d1b 100644 --- a/samples/OcelotGraphQL/OcelotGraphQL.csproj +++ b/samples/OcelotGraphQL/OcelotGraphQL.csproj @@ -3,7 +3,7 @@ netcoreapp2.0 - + PreserveNewest diff --git a/samples/OcelotGraphQL/Program.cs b/samples/OcelotGraphQL/Program.cs index 94e1295b..81938f9c 100644 --- a/samples/OcelotGraphQL/Program.cs +++ b/samples/OcelotGraphQL/Program.cs @@ -106,7 +106,7 @@ namespace OcelotGraphQL .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("configuration.json") + .AddJsonFile("ocelot.json") .AddEnvironmentVariables(); }) .ConfigureServices(s => { diff --git a/samples/OcelotGraphQL/README.md b/samples/OcelotGraphQL/README.md index 06ec668a..b2101d56 100644 --- a/samples/OcelotGraphQL/README.md +++ b/samples/OcelotGraphQL/README.md @@ -47,7 +47,7 @@ RESPONSE ## Notes -Please note this project never goes out to another service, it just gets the data for GraphQL in memory. You would need to add the details of your GraphQL server in configuration.json e.g. +Please note this project never goes out to another service, it just gets the data for GraphQL in memory. You would need to add the details of your GraphQL server in ocelot.json e.g. ```json { diff --git a/samples/OcelotGraphQL/configuration.json b/samples/OcelotGraphQL/ocelot.json similarity index 100% rename from samples/OcelotGraphQL/configuration.json rename to samples/OcelotGraphQL/ocelot.json diff --git a/samples/OcelotServiceFabric/.gitignore b/samples/OcelotServiceFabric/.gitignore index 733dbb07..84aa4b08 100644 --- a/samples/OcelotServiceFabric/.gitignore +++ b/samples/OcelotServiceFabric/.gitignore @@ -13,7 +13,7 @@ # Service fabric OcelotApplicationApiGatewayPkg/Code OcelotApplication/OcelotApplicationApiGatewayPkg/Code/appsettings.json -OcelotApplication/OcelotApplicationApiGatewayPkg/Code/configuration.json +OcelotApplication/OcelotApplicationApiGatewayPkg/Code/ocelot.json OcelotApplication/OcelotApplicationApiGatewayPkg/Code/runtimes/ OcelotApplicationServicePkg/Code OcelotApplication/OcelotApplicationApiGatewayPkg/Code/web.config diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj index 1ff69c09..08324cbe 100644 --- a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/OcelotApplicationApiGateway.csproj @@ -8,7 +8,7 @@ OcelotApplicationApiGateway - + PreserveNewest diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs index 6685e11f..7d913cfa 100644 --- a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs @@ -67,7 +67,7 @@ namespace OcelotApplicationApiGateway .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("configuration.json") + .AddJsonFile("ocelot.json") .AddEnvironmentVariables(); }) .ConfigureLogging((hostingContext, logging) => diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/configuration.json b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ocelot.json similarity index 100% rename from samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/configuration.json rename to samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/ocelot.json diff --git a/src/Ocelot/Cache/OutputCacheController.cs b/src/Ocelot/Cache/OutputCacheController.cs index 32b9fa7c..cc33e8aa 100644 --- a/src/Ocelot/Cache/OutputCacheController.cs +++ b/src/Ocelot/Cache/OutputCacheController.cs @@ -1,9 +1,5 @@ -using System.Net.Http; -using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Ocelot.Cache; -using Ocelot.Configuration.Provider; namespace Ocelot.Cache { @@ -11,7 +7,7 @@ namespace Ocelot.Cache [Route("outputcache")] public class OutputCacheController : Controller { - private IOcelotCache _cache; + private readonly IOcelotCache _cache; public OutputCacheController(IOcelotCache cache) { @@ -26,4 +22,4 @@ namespace Ocelot.Cache return new NoContentResult(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Configuration/Authentication/HashMatcher.cs b/src/Ocelot/Configuration/Authentication/HashMatcher.cs deleted file mode 100644 index 5f17362f..00000000 --- a/src/Ocelot/Configuration/Authentication/HashMatcher.cs +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 629bf008..00000000 --- a/src/Ocelot/Configuration/Authentication/IHashMatcher.cs +++ /dev/null @@ -1,7 +0,0 @@ -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/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs similarity index 90% rename from src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs rename to src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs index cee03813..c14e4aab 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs @@ -16,9 +16,8 @@ namespace Ocelot.Configuration.Creator /// /// Register as singleton /// - public class FileOcelotConfigurationCreator : IOcelotConfigurationCreator + public class FileInternalConfigurationCreator : IInternalConfigurationCreator { - private readonly IOptions _options; private readonly IConfigurationValidator _configurationValidator; private readonly IOcelotLogger _logger; private readonly IClaimsToThingCreator _claimsToThingCreator; @@ -35,8 +34,7 @@ namespace Ocelot.Configuration.Creator private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator; private readonly IDownstreamAddressesCreator _downstreamAddressesCreator; - public FileOcelotConfigurationCreator( - IOptions options, + public FileInternalConfigurationCreator( IConfigurationValidator configurationValidator, IOcelotLoggerFactory loggerFactory, IClaimsToThingCreator claimsToThingCreator, @@ -62,9 +60,8 @@ namespace Ocelot.Configuration.Creator _requestIdKeyCreator = requestIdKeyCreator; _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; _authOptionsCreator = authOptionsCreator; - _options = options; _configurationValidator = configurationValidator; - _logger = loggerFactory.CreateLogger(); + _logger = loggerFactory.CreateLogger(); _claimsToThingCreator = claimsToThingCreator; _serviceProviderConfigCreator = serviceProviderConfigCreator; _qosOptionsCreator = qosOptionsCreator; @@ -72,19 +69,19 @@ namespace Ocelot.Configuration.Creator _httpHandlerOptionsCreator = httpHandlerOptionsCreator; } - public async Task> Create(FileConfiguration fileConfiguration) + public async Task> Create(FileConfiguration fileConfiguration) { var config = await SetUpConfiguration(fileConfiguration); return config; } - private async Task> SetUpConfiguration(FileConfiguration fileConfiguration) + private async Task> SetUpConfiguration(FileConfiguration fileConfiguration) { var response = await _configurationValidator.IsValid(fileConfiguration); if (response.Data.IsError) { - return new ErrorResponse(response.Data.Errors); + return new ErrorResponse(response.Data.Errors); } var reRoutes = new List(); @@ -106,9 +103,9 @@ namespace Ocelot.Configuration.Creator var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration); - var config = new OcelotConfiguration(reRoutes, _adminPath.Path, serviceProviderConfiguration, fileConfiguration.GlobalConfiguration.RequestIdKey); + var config = new InternalConfiguration(reRoutes, _adminPath.Path, serviceProviderConfiguration, fileConfiguration.GlobalConfiguration.RequestIdKey); - return new OkResponse(config); + return new OkResponse(config); } public ReRoute SetUpAggregateReRoute(List reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) @@ -214,7 +211,7 @@ namespace Ocelot.Configuration.Creator .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) .WithUpstreamHost(fileReRoute.UpstreamHost) .WithDelegatingHandlers(fileReRoute.DelegatingHandlers) - .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) + .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) .WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream) .Build(); diff --git a/src/Ocelot/Configuration/Creator/IInternalConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IInternalConfigurationCreator.cs new file mode 100644 index 00000000..a0f3cb42 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IInternalConfigurationCreator.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Creator +{ + public interface IInternalConfigurationCreator + { + Task> Create(FileConfiguration fileConfiguration); + } +} diff --git a/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs deleted file mode 100644 index 4b431701..00000000 --- a/src/Ocelot/Configuration/Creator/IOcelotConfigurationCreator.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Creator -{ - public interface IOcelotConfigurationCreator - { - Task> Create(FileConfiguration fileConfiguration); - } -} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs index 5e7fcd37..8569001e 100644 --- a/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; -using Ocelot.Configuration.Provider; namespace Ocelot.Configuration.Creator { diff --git a/src/Ocelot/Configuration/FileConfigurationController.cs b/src/Ocelot/Configuration/FileConfigurationController.cs index 7bdf4926..707eb61d 100644 --- a/src/Ocelot/Configuration/FileConfigurationController.cs +++ b/src/Ocelot/Configuration/FileConfigurationController.cs @@ -2,34 +2,34 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; using Ocelot.Configuration.File; -using Ocelot.Configuration.Provider; using Ocelot.Configuration.Setter; using Ocelot.Raft; using Rafty.Concensus; namespace Ocelot.Configuration { + using Repository; + [Authorize] [Route("configuration")] public class FileConfigurationController : Controller { - private readonly IFileConfigurationProvider _configGetter; - private readonly IFileConfigurationSetter _configSetter; - private readonly IServiceProvider _serviceProvider; + private readonly IFileConfigurationRepository _repo; + private readonly IFileConfigurationSetter _setter; + private readonly IServiceProvider _provider; - public FileConfigurationController(IFileConfigurationProvider getFileConfig, IFileConfigurationSetter configSetter, IServiceProvider serviceProvider) + public FileConfigurationController(IFileConfigurationRepository repo, IFileConfigurationSetter setter, IServiceProvider provider) { - _configGetter = getFileConfig; - _configSetter = configSetter; - _serviceProvider = serviceProvider; + _repo = repo; + _setter = setter; + _provider = provider; } [HttpGet] public async Task Get() { - var response = await _configGetter.Get(); + var response = await _repo.Get(); if(response.IsError) { @@ -43,7 +43,7 @@ namespace Ocelot.Configuration public async Task Post([FromBody]FileConfiguration fileConfiguration) { //todo - this code is a bit shit sort it out.. - var test = _serviceProvider.GetService(typeof(INode)); + var test = _provider.GetService(typeof(INode)); if (test != null) { var node = (INode)test; @@ -56,7 +56,7 @@ namespace Ocelot.Configuration return new OkObjectResult(result.Command.Configuration); } - var response = await _configSetter.Set(fileConfiguration); + var response = await _setter.Set(fileConfiguration); if (response.IsError) { diff --git a/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs b/src/Ocelot/Configuration/IIdentityServerConfiguration.cs similarity index 67% rename from src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs rename to src/Ocelot/Configuration/IIdentityServerConfiguration.cs index 8a76eb9f..0eb70347 100644 --- a/src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs +++ b/src/Ocelot/Configuration/IIdentityServerConfiguration.cs @@ -1,16 +1,14 @@ -using System.Collections.Generic; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; - -namespace Ocelot.Configuration.Provider -{ - public interface IIdentityServerConfiguration - { - string ApiName { get; } - string ApiSecret { get; } - bool RequireHttps { get; } - List AllowedScopes { get; } - string CredentialsSigningCertificateLocation { get; } - string CredentialsSigningCertificatePassword { get; } - } -} \ No newline at end of file +namespace Ocelot.Configuration +{ + using System.Collections.Generic; + + public interface IIdentityServerConfiguration + { + string ApiName { get; } + string ApiSecret { get; } + bool RequireHttps { get; } + List AllowedScopes { get; } + string CredentialsSigningCertificateLocation { get; } + string CredentialsSigningCertificatePassword { get; } + } +} diff --git a/src/Ocelot/Configuration/IOcelotConfiguration.cs b/src/Ocelot/Configuration/IInternalConfiguration.cs similarity index 83% rename from src/Ocelot/Configuration/IOcelotConfiguration.cs rename to src/Ocelot/Configuration/IInternalConfiguration.cs index 2353cb85..c1781c48 100644 --- a/src/Ocelot/Configuration/IOcelotConfiguration.cs +++ b/src/Ocelot/Configuration/IInternalConfiguration.cs @@ -1,12 +1,12 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration -{ - public interface IOcelotConfiguration - { - List ReRoutes { get; } - string AdministrationPath {get;} - ServiceProviderConfiguration ServiceProviderConfiguration {get;} - string RequestId {get;} - } -} +using System.Collections.Generic; + +namespace Ocelot.Configuration +{ + public interface IInternalConfiguration + { + List ReRoutes { get; } + string AdministrationPath {get;} + ServiceProviderConfiguration ServiceProviderConfiguration {get;} + string RequestId {get;} + } +} diff --git a/src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs b/src/Ocelot/Configuration/IdentityServerConfiguration.cs similarity index 66% rename from src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs rename to src/Ocelot/Configuration/IdentityServerConfiguration.cs index 795e6994..b8b00ea2 100644 --- a/src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs +++ b/src/Ocelot/Configuration/IdentityServerConfiguration.cs @@ -1,32 +1,30 @@ -using System.Collections.Generic; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; - -namespace Ocelot.Configuration.Provider -{ - public class IdentityServerConfiguration : IIdentityServerConfiguration - { - public IdentityServerConfiguration( - string apiName, - bool requireHttps, - string apiSecret, - List allowedScopes, - string credentialsSigningCertificateLocation, - string credentialsSigningCertificatePassword) - { - ApiName = apiName; - RequireHttps = requireHttps; - ApiSecret = apiSecret; - AllowedScopes = allowedScopes; - CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation; - CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword; - } - - public string ApiName { get; private set; } - public bool RequireHttps { get; private set; } - public List AllowedScopes { get; private set; } - public string ApiSecret { get; private set; } - public string CredentialsSigningCertificateLocation { get; private set; } - public string CredentialsSigningCertificatePassword { get; private set; } - } -} \ No newline at end of file +namespace Ocelot.Configuration +{ + using System.Collections.Generic; + + public class IdentityServerConfiguration : IIdentityServerConfiguration + { + public IdentityServerConfiguration( + string apiName, + bool requireHttps, + string apiSecret, + List allowedScopes, + string credentialsSigningCertificateLocation, + string credentialsSigningCertificatePassword) + { + ApiName = apiName; + RequireHttps = requireHttps; + ApiSecret = apiSecret; + AllowedScopes = allowedScopes; + CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation; + CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword; + } + + public string ApiName { get; } + public bool RequireHttps { get; } + public List AllowedScopes { get; } + public string ApiSecret { get; } + public string CredentialsSigningCertificateLocation { get; } + public string CredentialsSigningCertificatePassword { get; } + } +} diff --git a/src/Ocelot/Configuration/OcelotConfiguration.cs b/src/Ocelot/Configuration/InternalConfiguration.cs similarity index 67% rename from src/Ocelot/Configuration/OcelotConfiguration.cs rename to src/Ocelot/Configuration/InternalConfiguration.cs index 1ab73b87..429bb9c0 100644 --- a/src/Ocelot/Configuration/OcelotConfiguration.cs +++ b/src/Ocelot/Configuration/InternalConfiguration.cs @@ -1,20 +1,20 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration -{ - public class OcelotConfiguration : IOcelotConfiguration - { - public OcelotConfiguration(List reRoutes, string administrationPath, ServiceProviderConfiguration serviceProviderConfiguration, string requestId) - { - ReRoutes = reRoutes; - AdministrationPath = administrationPath; - ServiceProviderConfiguration = serviceProviderConfiguration; - RequestId = requestId; - } - - public List ReRoutes { get; } - public string AdministrationPath {get;} - public ServiceProviderConfiguration ServiceProviderConfiguration {get;} - public string RequestId {get;} - } -} +using System.Collections.Generic; + +namespace Ocelot.Configuration +{ + public class InternalConfiguration : IInternalConfiguration + { + public InternalConfiguration(List reRoutes, string administrationPath, ServiceProviderConfiguration serviceProviderConfiguration, string requestId) + { + ReRoutes = reRoutes; + AdministrationPath = administrationPath; + ServiceProviderConfiguration = serviceProviderConfiguration; + RequestId = requestId; + } + + public List ReRoutes { get; } + public string AdministrationPath {get;} + public ServiceProviderConfiguration ServiceProviderConfiguration {get;} + public string RequestId {get;} + } +} diff --git a/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs deleted file mode 100644 index fdcd949b..00000000 --- a/src/Ocelot/Configuration/Provider/FileConfigurationProvider.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -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 async Task> Get() - { - var fileConfig = await _repo.Get(); - return new OkResponse(fileConfig.Data); - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs deleted file mode 100644 index c2ab51cb..00000000 --- a/src/Ocelot/Configuration/Provider/IFileConfigurationProvider.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Provider -{ - public interface IFileConfigurationProvider - { - Task> Get(); - } -} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs deleted file mode 100644 index 80f4583b..00000000 --- a/src/Ocelot/Configuration/Provider/IOcelotConfigurationProvider.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Provider -{ - public interface IOcelotConfigurationProvider - { - Task> Get(); - } -} diff --git a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs b/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs deleted file mode 100644 index ed72a60e..00000000 --- a/src/Ocelot/Configuration/Provider/OcelotConfigurationProvider.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Repository; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Provider -{ - /// - /// Register as singleton - /// - public class OcelotConfigurationProvider : IOcelotConfigurationProvider - { - private readonly IOcelotConfigurationRepository _config; - - public OcelotConfigurationProvider(IOcelotConfigurationRepository repo) - { - _config = repo; - } - - public async Task> Get() - { - var repoConfig = await _config.Get(); - - if (repoConfig.IsError) - { - return new ErrorResponse(repoConfig.Errors); - } - - return new OkResponse(repoConfig.Data); - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs index 7db04a09..21216168 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs @@ -1,30 +1,47 @@ -using System; -using System.Text; -using System.Threading.Tasks; -using Consul; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.Infrastructure.Consul; -using Ocelot.Responses; -using Ocelot.ServiceDiscovery.Configuration; - namespace Ocelot.Configuration.Repository { + using System; + using System.Text; + using System.Threading.Tasks; + using Consul; + using Newtonsoft.Json; + using Ocelot.Configuration.File; + using Ocelot.Infrastructure.Consul; + using Ocelot.Logging; + using Ocelot.Responses; + using Ocelot.ServiceDiscovery.Configuration; + public class ConsulFileConfigurationRepository : IFileConfigurationRepository { private readonly ConsulClient _consul; - private const string OcelotConfiguration = "OcelotConfiguration"; + private const string OcelotConfiguration = "InternalConfiguration"; private readonly Cache.IOcelotCache _cache; + private readonly IOcelotLogger _logger; public ConsulFileConfigurationRepository( - Cache.IOcelotCache cache, - ServiceProviderConfiguration serviceProviderConfig, - IConsulClientFactory factory) + Cache.IOcelotCache cache, + IInternalConfigurationRepository repo, + IConsulClientFactory factory, + IOcelotLoggerFactory loggerFactory) { - var consulHost = string.IsNullOrEmpty(serviceProviderConfig?.Host) ? "localhost" : serviceProviderConfig?.Host; - var consulPort = serviceProviderConfig?.Port ?? 8500; - var config = new ConsulRegistryConfiguration(consulHost, consulPort, OcelotConfiguration, serviceProviderConfig?.Token); + _logger = loggerFactory.CreateLogger(); _cache = cache; + + var internalConfig = repo.Get(); + + var consulHost = "localhost"; + var consulPort = 8500; + string token = null; + + if (!internalConfig.IsError) + { + consulHost = string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration?.Host) ? consulHost : internalConfig.Data.ServiceProviderConfiguration?.Host; + consulPort = internalConfig.Data.ServiceProviderConfiguration?.Port ?? consulPort; + token = internalConfig.Data.ServiceProviderConfiguration?.Token; + } + + var config = new ConsulRegistryConfiguration(consulHost, consulPort, OcelotConfiguration, token); + _consul = factory.Get(config); } diff --git a/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs similarity index 75% rename from src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs rename to src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs index ff5e3876..5fb871f4 100644 --- a/src/Ocelot/Configuration/Repository/FileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs @@ -1,52 +1,54 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Repository -{ - public class FileConfigurationRepository : IFileConfigurationRepository - { - private readonly string _configFilePath; - - private static readonly object _lock = new object(); - - public FileConfigurationRepository(IHostingEnvironment hostingEnvironment) - { - _configFilePath = $"{AppContext.BaseDirectory}/configuration{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json"; - } - - public Task> Get() - { - string jsonConfiguration; - - lock(_lock) - { - jsonConfiguration = System.IO.File.ReadAllText(_configFilePath); - } - - var fileConfiguration = JsonConvert.DeserializeObject(jsonConfiguration); - - return Task.FromResult>(new OkResponse(fileConfiguration)); - } - - public Task Set(FileConfiguration fileConfiguration) - { - string jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - lock(_lock) - { - if (System.IO.File.Exists(_configFilePath)) - { - System.IO.File.Delete(_configFilePath); - } - - System.IO.File.WriteAllText(_configFilePath, jsonConfiguration); - } - - return Task.FromResult(new OkResponse()); - } - } -} +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Repository +{ + public class DiskFileConfigurationRepository : IFileConfigurationRepository + { + private readonly string _configFilePath; + + private static readonly object _lock = new object(); + + private const string ConfigurationFileName = "ocelot"; + + public DiskFileConfigurationRepository(IHostingEnvironment hostingEnvironment) + { + _configFilePath = $"{AppContext.BaseDirectory}/{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json"; + } + + public Task> Get() + { + string jsonConfiguration; + + lock(_lock) + { + jsonConfiguration = System.IO.File.ReadAllText(_configFilePath); + } + + var fileConfiguration = JsonConvert.DeserializeObject(jsonConfiguration); + + return Task.FromResult>(new OkResponse(fileConfiguration)); + } + + public Task Set(FileConfiguration fileConfiguration) + { + string jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + lock(_lock) + { + if (System.IO.File.Exists(_configFilePath)) + { + System.IO.File.Delete(_configFilePath); + } + + System.IO.File.WriteAllText(_configFilePath, jsonConfiguration); + } + + return Task.FromResult(new OkResponse()); + } + } +} diff --git a/src/Ocelot/Configuration/Repository/IInternalConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/IInternalConfigurationRepository.cs new file mode 100644 index 00000000..5db4adb5 --- /dev/null +++ b/src/Ocelot/Configuration/Repository/IInternalConfigurationRepository.cs @@ -0,0 +1,10 @@ +using Ocelot.Responses; + +namespace Ocelot.Configuration.Repository +{ + public interface IInternalConfigurationRepository + { + Response Get(); + Response AddOrReplace(IInternalConfiguration internalConfiguration); + } +} diff --git a/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs deleted file mode 100644 index 16b386a1..00000000 --- a/src/Ocelot/Configuration/Repository/IOcelotConfigurationRepository.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Threading.Tasks; -using Ocelot.Configuration.File; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Repository -{ - public interface IOcelotConfigurationRepository - { - Task> Get(); - Task AddOrReplace(IOcelotConfiguration ocelotConfiguration); - } -} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Repository/InMemoryInternalConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/InMemoryInternalConfigurationRepository.cs new file mode 100644 index 00000000..2ac954e3 --- /dev/null +++ b/src/Ocelot/Configuration/Repository/InMemoryInternalConfigurationRepository.cs @@ -0,0 +1,29 @@ +using Ocelot.Responses; + +namespace Ocelot.Configuration.Repository +{ + /// + /// Register as singleton + /// + public class InMemoryInternalConfigurationRepository : IInternalConfigurationRepository + { + private static readonly object LockObject = new object(); + + private IInternalConfiguration _internalConfiguration; + + public Response Get() + { + return new OkResponse(_internalConfiguration); + } + + public Response AddOrReplace(IInternalConfiguration internalConfiguration) + { + lock (LockObject) + { + _internalConfiguration = internalConfiguration; + } + + return new OkResponse(); + } + } +} diff --git a/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs deleted file mode 100644 index 9ce50ba6..00000000 --- a/src/Ocelot/Configuration/Repository/InMemoryOcelotConfigurationRepository.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Threading.Tasks; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Repository -{ - /// - /// Register as singleton - /// - public class InMemoryOcelotConfigurationRepository : IOcelotConfigurationRepository - { - private static readonly object LockObject = new object(); - - private IOcelotConfiguration _ocelotConfiguration; - - public Task> Get() - { - return Task.FromResult>(new OkResponse(_ocelotConfiguration)); - } - - public Task AddOrReplace(IOcelotConfiguration ocelotConfiguration) - { - lock (LockObject) - { - _ocelotConfiguration = ocelotConfiguration; - } - - return Task.FromResult(new OkResponse()); - } - } -} diff --git a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs similarity index 63% rename from src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs rename to src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs index 38a7c1cb..6821553e 100644 --- a/src/Ocelot/Configuration/Setter/FileConfigurationSetter.cs +++ b/src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs @@ -1,42 +1,44 @@ -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; - private readonly IFileConfigurationRepository _repo; - - public FileConfigurationSetter(IOcelotConfigurationRepository configRepo, - IOcelotConfigurationCreator configCreator, IFileConfigurationRepository repo) - { - _configRepo = configRepo; - _configCreator = configCreator; - _repo = repo; - } - - public async Task Set(FileConfiguration fileConfig) - { - var response = await _repo.Set(fileConfig); - - if(response.IsError) - { - return new ErrorResponse(response.Errors); - } - - var config = await _configCreator.Create(fileConfig); - - if(!config.IsError) - { - await _configRepo.AddOrReplace(config.Data); - } - - return new ErrorResponse(config.Errors); - } - } -} +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 FileAndInternalConfigurationSetter : IFileConfigurationSetter + { + private readonly IInternalConfigurationRepository _configRepo; + private readonly IInternalConfigurationCreator _configCreator; + private readonly IFileConfigurationRepository _repo; + + public FileAndInternalConfigurationSetter( + IInternalConfigurationRepository configRepo, + IInternalConfigurationCreator configCreator, + IFileConfigurationRepository repo) + { + _configRepo = configRepo; + _configCreator = configCreator; + _repo = repo; + } + + public async Task Set(FileConfiguration fileConfig) + { + var response = await _repo.Set(fileConfig); + + if(response.IsError) + { + return new ErrorResponse(response.Errors); + } + + var config = await _configCreator.Create(fileConfig); + + if(!config.IsError) + { + _configRepo.AddOrReplace(config.Data); + } + + return new ErrorResponse(config.Errors); + } + } +} diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs index 56360ad5..00cb0578 100644 --- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs +++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs @@ -1,21 +1,73 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Configuration.Memory; - namespace Ocelot.DependencyInjection { + using System; + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Configuration.Memory; + using System.IO; + using System.Linq; + using System.Text.RegularExpressions; + using Configuration.File; + using Newtonsoft.Json; + public static class ConfigurationBuilderExtensions { - [Obsolete("Please set BaseUrl in configuration.json GlobalConfiguration.BaseUrl")] + [Obsolete("Please set BaseUrl in ocelot.json GlobalConfiguration.BaseUrl")] public static IConfigurationBuilder AddOcelotBaseUrl(this IConfigurationBuilder builder, string baseUrl) { - var memorySource = new MemoryConfigurationSource(); - memorySource.InitialData = new List> + var memorySource = new MemoryConfigurationSource { - new KeyValuePair("BaseUrl", baseUrl) + InitialData = new List> + { + new KeyValuePair("BaseUrl", baseUrl) + } }; + builder.Add(memorySource); + + return builder; + } + + public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder) + { + const string pattern = "(?i)ocelot(.*).json$"; + + var reg = new Regex(pattern); + + var files = Directory.GetFiles(".") + .Where(path => reg.IsMatch(path)).Where(x => x.Count(s => s == '.') == 3) + .ToList(); + + var fileConfiguration = new FileConfiguration(); + + foreach (var file in files) + { + // windows and unix sigh... + if(files.Count > 1 && (file == "./ocelot.json" || file == ".\\ocelot.json")) + { + continue; + } + + var lines = File.ReadAllText(file); + + var config = JsonConvert.DeserializeObject(lines); + + // windows and unix sigh... + if (file == "./ocelot.global.json" || file == ".\\ocelot.global.json") + { + fileConfiguration.GlobalConfiguration = config.GlobalConfiguration; + } + + fileConfiguration.Aggregates.AddRange(config.Aggregates); + fileConfiguration.ReRoutes.AddRange(config.ReRoutes); + } + + var json = JsonConvert.SerializeObject(fileConfiguration); + + File.WriteAllText("ocelot.json", json); + + builder.AddJsonFile("ocelot.json"); + return builder; } } diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 96e084c1..5373cc1b 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -1,7 +1,3 @@ -using Butterfly.Client.Tracing; -using Microsoft.Extensions.Options; -using Ocelot.Middleware.Multiplexer; - namespace Ocelot.DependencyInjection { using CacheManager.Core; @@ -12,17 +8,14 @@ namespace Ocelot.DependencyInjection 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; - using Ocelot.Configuration.Provider; using Ocelot.Configuration.Repository; using Ocelot.Configuration.Setter; 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; @@ -44,16 +37,14 @@ namespace Ocelot.DependencyInjection using System.Security.Cryptography.X509Certificates; using IdentityServer4.AccessTokenValidation; using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider; using Microsoft.Extensions.DependencyInjection.Extensions; - using System.Linq; using System.Net.Http; using Butterfly.Client.AspNetCore; using Ocelot.Infrastructure; using Ocelot.Infrastructure.Consul; + using Butterfly.Client.Tracing; + using Ocelot.Middleware.Multiplexer; public class OcelotBuilder : IOcelotBuilder { @@ -78,8 +69,8 @@ namespace Ocelot.DependencyInjection _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); @@ -91,9 +82,8 @@ namespace Ocelot.DependencyInjection _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); @@ -101,7 +91,6 @@ namespace Ocelot.DependencyInjection _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); - _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); @@ -252,17 +241,6 @@ namespace Ocelot.DependencyInjection public IOcelotBuilder AddStoreOcelotConfigurationInConsul() { - var serviceDiscoveryPort = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Port", 0); - var serviceDiscoveryHost = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Host", string.Empty); - var serviceDiscoveryToken = _configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Token", string.Empty); - - var config = new ServiceProviderConfigurationBuilder() - .WithPort(serviceDiscoveryPort) - .WithHost(serviceDiscoveryHost) - .WithToken(serviceDiscoveryToken) - .Build(); - - _services.AddSingleton(config); _services.AddSingleton(); _services.AddSingleton(); return this; @@ -278,12 +256,12 @@ namespace Ocelot.DependencyInjection _services.AddSingleton>(cacheManagerOutputCache); _services.AddSingleton>(ocelotOutputCacheManager); - var ocelotConfigCacheManagerOutputCache = CacheFactory.Build("OcelotConfigurationCache", settings); - var ocelotConfigCacheManager = new OcelotCacheManagerCache(ocelotConfigCacheManagerOutputCache); - _services.RemoveAll(typeof(ICacheManager)); - _services.RemoveAll(typeof(IOcelotCache)); - _services.AddSingleton>(ocelotConfigCacheManagerOutputCache); - _services.AddSingleton>(ocelotConfigCacheManager); + var ocelotConfigCacheManagerOutputCache = CacheFactory.Build("OcelotConfigurationCache", settings); + var ocelotConfigCacheManager = new OcelotCacheManagerCache(ocelotConfigCacheManagerOutputCache); + _services.RemoveAll(typeof(ICacheManager)); + _services.RemoveAll(typeof(IOcelotCache)); + _services.AddSingleton>(ocelotConfigCacheManagerOutputCache); + _services.AddSingleton>(ocelotConfigCacheManager); var fileConfigCacheManagerOutputCache = CacheFactory.Build("FileConfigurationCache", settings); var fileConfigCacheManager = new OcelotCacheManagerCache(fileConfigCacheManagerOutputCache); @@ -304,7 +282,6 @@ namespace Ocelot.DependencyInjection private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath) { _services.TryAddSingleton(identityServerConfiguration); - _services.TryAddSingleton(); var identityServerBuilder = _services .AddIdentityServer(o => { o.IssuerUri = "Ocelot"; diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index 35b6d92d..df70b0b3 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -18,7 +18,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder _placeholderNameAndValueFinder = urlPathPlaceholderNameAndValueFinder; } - public Response FindDownstreamRoute(string path, string httpMethod, IOcelotConfiguration configuration, string upstreamHost) + public Response FindDownstreamRoute(string path, string httpMethod, IInternalConfiguration configuration, string upstreamHost) { var downstreamRoutes = new List(); diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs index 81901231..1d8fae15 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/IDownstreamRouteFinder.cs @@ -6,6 +6,6 @@ namespace Ocelot.DownstreamRouteFinder.Finder { public interface IDownstreamRouteFinder { - Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod, IOcelotConfiguration configuration, string upstreamHost); + Response FindDownstreamRoute(string upstreamUrlPath, string upstreamHttpMethod, IInternalConfiguration configuration, string upstreamHost); } } diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index 70e4a4e2..970636d3 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -1,7 +1,6 @@ using System.Threading.Tasks; using System.Linq; -using Ocelot.Configuration; -using Ocelot.Configuration.Provider; +using Ocelot.Configuration.Repository; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.Infrastructure.Extensions; using Ocelot.Logging; @@ -14,17 +13,17 @@ namespace Ocelot.DownstreamRouteFinder.Middleware { private readonly OcelotRequestDelegate _next; private readonly IDownstreamRouteFinder _downstreamRouteFinder; - private readonly IOcelotConfigurationProvider _configProvider; + private readonly IInternalConfigurationRepository _repo; private readonly IMultiplexer _multiplexer; public DownstreamRouteFinderMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, IDownstreamRouteFinder downstreamRouteFinder, - IOcelotConfigurationProvider configProvider, + IInternalConfigurationRepository repo, IMultiplexer multiplexer) :base(loggerFactory.CreateLogger()) { - _configProvider = configProvider; + _repo = repo; _multiplexer = multiplexer; _next = next; _downstreamRouteFinder = downstreamRouteFinder; @@ -36,7 +35,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware var upstreamHost = context.HttpContext.Request.Headers["Host"]; - var configuration = await _configProvider.Get(); + var configuration = _repo.Get(); if (configuration.IsError) { diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs index 236e2e8d..41b0b83d 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs @@ -1,10 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; -using Ocelot.Configuration.Provider; -using Ocelot.DownstreamRouteFinder.Middleware; +using Ocelot.Configuration.Repository; using Ocelot.Infrastructure.Extensions; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; @@ -18,16 +15,16 @@ namespace Ocelot.Errors.Middleware public class ExceptionHandlerMiddleware : OcelotMiddleware { private readonly OcelotRequestDelegate _next; - private readonly IOcelotConfigurationProvider _provider; + private readonly IInternalConfigurationRepository _configRepo; private readonly IRequestScopedDataRepository _repo; public ExceptionHandlerMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IOcelotConfigurationProvider provider, + IOcelotLoggerFactory loggerFactory, + IInternalConfigurationRepository configRepo, IRequestScopedDataRepository repo) : base(loggerFactory.CreateLogger()) { - _provider = provider; + _configRepo = configRepo; _repo = repo; _next = next; } @@ -36,7 +33,7 @@ namespace Ocelot.Errors.Middleware { try { - await TrySetGlobalRequestId(context); + TrySetGlobalRequestId(context); Logger.LogDebug("ocelot pipeline started"); @@ -56,12 +53,12 @@ namespace Ocelot.Errors.Middleware Logger.LogDebug("ocelot pipeline finished"); } - private async Task TrySetGlobalRequestId(DownstreamContext context) + private void TrySetGlobalRequestId(DownstreamContext context) { //try and get the global request id and set it for logs... //should this basically be immutable per request...i guess it should! //first thing is get config - var configuration = await _provider.Get(); + var configuration = _configRepo.Get(); if(configuration.IsError) { diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 9d345dc2..25d818c5 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -4,14 +4,12 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using System.Diagnostics; using Microsoft.AspNetCore.Builder; using Ocelot.Configuration; using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; - using Ocelot.Configuration.Provider; using Ocelot.Configuration.Repository; using Ocelot.Configuration.Setter; using Ocelot.Responses; @@ -85,63 +83,108 @@ node.Start(nodeId.Id); } - private static async Task CreateConfiguration(IApplicationBuilder builder) + private static async Task CreateConfiguration(IApplicationBuilder builder) { - var deps = GetDependencies(builder); + // make configuration from file system? + // earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this + var fileConfig = (IOptions)builder.ApplicationServices.GetService(typeof(IOptions)); + + // now create the config + var internalConfigCreator = (IInternalConfigurationCreator)builder.ApplicationServices.GetService(typeof(IInternalConfigurationCreator)); + var internalConfig = await internalConfigCreator.Create(fileConfig.Value); - var ocelotConfiguration = await deps.provider.Get(); + // now save it in memory + var internalConfigRepo = (IInternalConfigurationRepository)builder.ApplicationServices.GetService(typeof(IInternalConfigurationRepository)); + internalConfigRepo.AddOrReplace(internalConfig.Data); - if (ConfigurationNotSetUp(ocelotConfiguration)) + var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); + + var fileConfigRepo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository)); + + if (UsingConsul(fileConfigRepo)) { - var response = await SetConfig(builder, deps.fileConfiguration, deps.setter, deps.provider, deps.repo); - - if (UnableToSetConfig(response)) + await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo); + } + else + { + await SetFileConfig(fileConfigSetter, fileConfig); + } + + return GetOcelotConfigAndReturn(internalConfigRepo); + } + + private static async Task SetFileConfigInConsul(IApplicationBuilder builder, + IFileConfigurationRepository fileConfigRepo, IOptions fileConfig, + IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo) + { + // get the config from consul. + var fileConfigFromConsul = await fileConfigRepo.Get(); + + if (IsError(fileConfigFromConsul)) + { + ThrowToStopOcelotStarting(fileConfigFromConsul); + } + else if (ConfigNotStoredInConsul(fileConfigFromConsul)) + { + //there was no config in consul set the file in config in consul + await fileConfigRepo.Set(fileConfig.Value); + } + else + { + // create the internal config from consul data + var internalConfig = await internalConfigCreator.Create(fileConfigFromConsul.Data); + + if (IsError(internalConfig)) { - ThrowToStopOcelotStarting(response); + ThrowToStopOcelotStarting(internalConfig); + } + else + { + // add the internal config to the internal repo + var response = internalConfigRepo.AddOrReplace(internalConfig.Data); + + if (IsError(response)) + { + ThrowToStopOcelotStarting(response); + } + } + + if (IsError(internalConfig)) + { + ThrowToStopOcelotStarting(internalConfig); } } - return await GetOcelotConfigAndReturn(deps.provider); + //todo - this starts the poller if it has been registered...please this is so bad. + var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller)); } - private static async Task SetConfig(IApplicationBuilder builder, IOptions fileConfiguration, IFileConfigurationSetter setter, IOcelotConfigurationProvider provider, IFileConfigurationRepository repo) + private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptions fileConfig) { - if (UsingConsul(repo)) + Response response; + response = await fileConfigSetter.Set(fileConfig.Value); + + if (IsError(response)) { - return await SetUpConfigFromConsul(builder, repo, setter, fileConfiguration); + ThrowToStopOcelotStarting(response); } - - return await setter.Set(fileConfiguration.Value); } - private static bool UnableToSetConfig(Response response) + private static bool ConfigNotStoredInConsul(Responses.Response fileConfigFromConsul) + { + return fileConfigFromConsul.Data == null; + } + + private static bool IsError(Response response) { return response == null || response.IsError; } - private static bool ConfigurationNotSetUp(Ocelot.Responses.Response ocelotConfiguration) + private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider) { - return ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError; - } + var ocelotConfiguration = provider.Get(); - private static (IOptions fileConfiguration, IFileConfigurationSetter setter, IOcelotConfigurationProvider provider, IFileConfigurationRepository repo) GetDependencies(IApplicationBuilder builder) - { - var fileConfiguration = (IOptions)builder.ApplicationServices.GetService(typeof(IOptions)); - - var setter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); - - var provider = (IOcelotConfigurationProvider)builder.ApplicationServices.GetService(typeof(IOcelotConfigurationProvider)); - - var repo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository)); - - return (fileConfiguration, setter, provider, repo); - } - - private static async Task GetOcelotConfigAndReturn(IOcelotConfigurationProvider provider) - { - var ocelotConfiguration = await provider.Get(); - - if(ocelotConfiguration == null || ocelotConfiguration.Data == null || ocelotConfiguration.IsError) + if(ocelotConfiguration?.Data == null || ocelotConfiguration.IsError) { ThrowToStopOcelotStarting(ocelotConfiguration); } @@ -159,49 +202,7 @@ return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository); } - private static async Task SetUpConfigFromConsul(IApplicationBuilder builder, IFileConfigurationRepository consulFileConfigRepo, IFileConfigurationSetter setter, IOptions fileConfig) - { - Response config = null; - - var ocelotConfigurationRepository = - (IOcelotConfigurationRepository) builder.ApplicationServices.GetService( - typeof(IOcelotConfigurationRepository)); - - var ocelotConfigurationCreator = - (IOcelotConfigurationCreator) builder.ApplicationServices.GetService( - typeof(IOcelotConfigurationCreator)); - - var fileConfigFromConsul = await consulFileConfigRepo.Get(); - - if (fileConfigFromConsul.Data == null) - { - config = await setter.Set(fileConfig.Value); - var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller)); - } - else - { - var ocelotConfig = await ocelotConfigurationCreator.Create(fileConfigFromConsul.Data); - - if(ocelotConfig.IsError) - { - return new ErrorResponse(ocelotConfig.Errors); - } - - config = await ocelotConfigurationRepository.AddOrReplace(ocelotConfig.Data); - - if (config.IsError) - { - return new ErrorResponse(config.Errors); - } - - //todo - this starts the poller if it has been registered...please this is so bad. - var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller)); - } - - return new OkResponse(); - } - - private static void CreateAdministrationArea(IApplicationBuilder builder, IOcelotConfiguration configuration) + private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration) { if(!string.IsNullOrEmpty(configuration.AdministrationPath)) { diff --git a/src/Ocelot/Raft/FilePeersProvider.cs b/src/Ocelot/Raft/FilePeersProvider.cs index d31dc2ca..52b877df 100644 --- a/src/Ocelot/Raft/FilePeersProvider.cs +++ b/src/Ocelot/Raft/FilePeersProvider.cs @@ -1,10 +1,8 @@ -using System; using System.Collections.Generic; using System.Net.Http; -using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Options; using Ocelot.Configuration; -using Ocelot.Configuration.Provider; +using Ocelot.Configuration.Repository; using Ocelot.Middleware; using Rafty.Concensus; using Rafty.Infrastructure; @@ -15,21 +13,20 @@ namespace Ocelot.Raft public class FilePeersProvider : IPeersProvider { private readonly IOptions _options; - private List _peers; + private readonly List _peers; private IBaseUrlFinder _finder; - private IOcelotConfigurationProvider _provider; + private IInternalConfigurationRepository _repo; private IIdentityServerConfiguration _identityServerConfig; - public FilePeersProvider(IOptions options, IBaseUrlFinder finder, IOcelotConfigurationProvider provider, IIdentityServerConfiguration identityServerConfig) + public FilePeersProvider(IOptions options, IBaseUrlFinder finder, IInternalConfigurationRepository repo, IIdentityServerConfiguration identityServerConfig) { _identityServerConfig = identityServerConfig; - _provider = provider; + _repo = repo; _finder = finder; _options = options; _peers = new List(); - //todo - sort out async nonsense.. - var config = _provider.Get().GetAwaiter().GetResult(); + var config = _repo.Get(); foreach (var item in _options.Value.Peers) { var httpClient = new HttpClient(); diff --git a/src/Ocelot/Raft/HttpPeer.cs b/src/Ocelot/Raft/HttpPeer.cs index cd5ceef5..fcec77ea 100644 --- a/src/Ocelot/Raft/HttpPeer.cs +++ b/src/Ocelot/Raft/HttpPeer.cs @@ -1,12 +1,8 @@ using System; using System.Collections.Generic; using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; using Newtonsoft.Json; -using Ocelot.Authentication; using Ocelot.Configuration; -using Ocelot.Configuration.Provider; using Ocelot.Middleware; using Rafty.Concensus; using Rafty.FiniteStateMachine; @@ -16,15 +12,15 @@ namespace Ocelot.Raft [ExcludeFromCoverage] public class HttpPeer : IPeer { - private string _hostAndPort; - private HttpClient _httpClient; - private JsonSerializerSettings _jsonSerializerSettings; - private string _baseSchemeUrlAndPort; + private readonly string _hostAndPort; + private readonly HttpClient _httpClient; + private readonly JsonSerializerSettings _jsonSerializerSettings; + private readonly string _baseSchemeUrlAndPort; private BearerToken _token; - private IOcelotConfiguration _config; - private IIdentityServerConfiguration _identityServerConfiguration; + private readonly IInternalConfiguration _config; + private readonly IIdentityServerConfiguration _identityServerConfiguration; - public HttpPeer(string hostAndPort, HttpClient httpClient, IBaseUrlFinder finder, IOcelotConfiguration config, IIdentityServerConfiguration identityServerConfiguration) + public HttpPeer(string hostAndPort, HttpClient httpClient, IBaseUrlFinder finder, IInternalConfiguration config, IIdentityServerConfiguration identityServerConfiguration) { _identityServerConfiguration = identityServerConfiguration; _config = config; diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index 797f340c..5a5cce82 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -303,7 +303,7 @@ namespace Ocelot.AcceptanceTests { app.Run(async context => { - if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/OcelotConfiguration") + if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") { var json = JsonConvert.SerializeObject(_config); @@ -315,7 +315,7 @@ namespace Ocelot.AcceptanceTests await context.Response.WriteJsonAsync(new FakeConsulGetResponse[] { kvp }); } - else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/OcelotConfiguration") + else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") { try { @@ -352,7 +352,7 @@ namespace Ocelot.AcceptanceTests public int CreateIndex => 100; public int ModifyIndex => 200; public int LockIndex => 200; - public string Key => "OcelotConfiguration"; + public string Key => "InternalConfiguration"; public int Flags => 0; public string Value { get; private set; } public string Session => "adf4238a-882b-9ddc-4a9d-5b6758e4159e"; diff --git a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs index 341986fe..422348d0 100644 --- a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -26,7 +26,7 @@ namespace Ocelot.AcceptanceTests { _counter = 0; _steps = new Steps(); - _configurationPath = "configuration.json"; + _configurationPath = "ocelot.json"; } [Fact] diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 4f67b970..18ff45f1 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -15,7 +15,7 @@ - + PreserveNewest diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index ba0da5d1..f5c186ca 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -66,7 +66,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureLogging((hostingContext, logging) => @@ -124,7 +124,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -152,7 +152,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -190,7 +190,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -221,7 +221,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -254,7 +254,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -287,7 +287,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -319,7 +319,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -358,7 +358,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -400,7 +400,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -437,7 +437,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -465,7 +465,7 @@ namespace Ocelot.AcceptanceTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -525,7 +525,7 @@ namespace Ocelot.AcceptanceTests var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile("configuration.json") + .AddJsonFile("ocelot.json") .AddEnvironmentVariables(); var configuration = builder.Build(); diff --git a/test/Ocelot.AcceptanceTests/TestConfiguration.cs b/test/Ocelot.AcceptanceTests/TestConfiguration.cs index 5f29d1bd..6f5818ca 100644 --- a/test/Ocelot.AcceptanceTests/TestConfiguration.cs +++ b/test/Ocelot.AcceptanceTests/TestConfiguration.cs @@ -5,6 +5,6 @@ namespace Ocelot.AcceptanceTests { public static class TestConfiguration { - public static string ConfigurationPath => Path.Combine(AppContext.BaseDirectory, "configuration.json"); + public static string ConfigurationPath => Path.Combine(AppContext.BaseDirectory, "ocelot.json"); } } diff --git a/test/Ocelot.AcceptanceTests/configuration.json b/test/Ocelot.AcceptanceTests/configuration.json deleted file mode 100755 index 027025b0..00000000 --- a/test/Ocelot.AcceptanceTests/configuration.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "ReRoutes": [ - { - "DownstreamPathTemplate": "41879/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": "Get", - "AuthenticationOptions": { - "Provider": null, - "ProviderRootUrl": null, - "ApiName": null, - "RequireHttps": false, - "AllowedScopes": [], - "ApiSecret": 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 - }, - "AdministrationPath": null, - "RateLimitOptions": { - "ClientIdHeader": "ClientId", - "QuotaExceededMessage": null, - "RateLimitCounterPrefix": "ocelot", - "DisableRateLimitHeaders": false, - "HttpStatusCode": 429 - } - } -} \ No newline at end of file diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs index 763d642c..84b0c0bb 100644 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ b/test/Ocelot.IntegrationTests/AdministrationTests.cs @@ -461,7 +461,7 @@ namespace Ocelot.IntegrationTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(x => @@ -579,7 +579,7 @@ namespace Ocelot.IntegrationTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(x => { @@ -612,7 +612,7 @@ namespace Ocelot.IntegrationTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(x => @@ -652,7 +652,7 @@ namespace Ocelot.IntegrationTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(x => { @@ -675,7 +675,7 @@ namespace Ocelot.IntegrationTests private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) { - var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json"; + var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); @@ -688,7 +688,7 @@ namespace Ocelot.IntegrationTests var text = File.ReadAllText(configurationPath); - configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; if (File.Exists(configurationPath)) { diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj index 644d217b..97c8782f 100644 --- a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj @@ -14,7 +14,7 @@ ..\..\codeanalysis.ruleset - + PreserveNewest diff --git a/test/Ocelot.IntegrationTests/RaftTests.cs b/test/Ocelot.IntegrationTests/RaftTests.cs index 3dc01bb9..baad5091 100644 --- a/test/Ocelot.IntegrationTests/RaftTests.cs +++ b/test/Ocelot.IntegrationTests/RaftTests.cs @@ -301,7 +301,7 @@ namespace Ocelot.IntegrationTests private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) { - var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json"; + var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); @@ -314,7 +314,7 @@ namespace Ocelot.IntegrationTests var text = File.ReadAllText(configurationPath); - configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; if (File.Exists(configurationPath)) { @@ -340,7 +340,7 @@ namespace Ocelot.IntegrationTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddJsonFile("peers.json", optional: true, reloadOnChange: true); #pragma warning disable CS0618 config.AddOcelotBaseUrl(url); diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index e61fa63b..ed125aa4 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -108,7 +108,7 @@ namespace Ocelot.IntegrationTests var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("configuration.json"); + config.AddJsonFile("ocelot.json"); config.AddEnvironmentVariables(); }) .ConfigureServices(x => @@ -138,7 +138,7 @@ namespace Ocelot.IntegrationTests private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) { - var configurationPath = $"{Directory.GetCurrentDirectory()}/configuration.json"; + var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); @@ -151,7 +151,7 @@ namespace Ocelot.IntegrationTests var text = File.ReadAllText(configurationPath); - configurationPath = $"{AppContext.BaseDirectory}/configuration.json"; + configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; if (File.Exists(configurationPath)) { diff --git a/test/Ocelot.IntegrationTests/configuration.json b/test/Ocelot.IntegrationTests/ocelot.json old mode 100755 new mode 100644 similarity index 100% rename from test/Ocelot.IntegrationTests/configuration.json rename to test/Ocelot.IntegrationTests/ocelot.json diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj index d2e3e7bc..a43e6b23 100644 --- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj +++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj @@ -16,7 +16,7 @@ - + PreserveNewest diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index 3bab21b7..966b1097 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -21,7 +21,7 @@ namespace Ocelot.ManualTest .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("configuration.json") + .AddJsonFile("ocelot.json") .AddEnvironmentVariables(); }) .ConfigureServices(s => { diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/ocelot.json similarity index 100% rename from test/Ocelot.ManualTest/configuration.json rename to test/Ocelot.ManualTest/ocelot.json diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 479c3e28..40fe4f02 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -1,22 +1,18 @@ -using System.Collections.Generic; -using Castle.Components.DictionaryAdapter; -using Microsoft.Extensions.Options; -using Moq; -using Ocelot.Cache; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Validator; -using Ocelot.Logging; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration +namespace Ocelot.UnitTests.Configuration { - using System; + using System.Collections.Generic; + using Moq; + using Ocelot.Cache; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.Configuration.Creator; + using Ocelot.Configuration.File; + using Ocelot.Configuration.Validator; + using Ocelot.Logging; + using Ocelot.Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; using Ocelot.DependencyInjection; using Ocelot.Errors; using Ocelot.UnitTests.TestData; @@ -24,23 +20,22 @@ namespace Ocelot.UnitTests.Configuration public class FileConfigurationCreatorTests { - private readonly Mock> _fileConfig; private readonly Mock _validator; - private Response _config; + private Response _config; private FileConfiguration _fileConfiguration; private readonly Mock _logger; - private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator; - private Mock _claimsToThingCreator; - private Mock _authOptionsCreator; - private Mock _upstreamTemplatePatternCreator; - private Mock _requestIdKeyCreator; - private Mock _serviceProviderConfigCreator; - private Mock _qosOptionsCreator; - private Mock _fileReRouteOptionsCreator; - private Mock _rateLimitOptions; - private Mock _regionCreator; - private Mock _httpHandlerOptionsCreator; - private Mock _adminPath; + private readonly FileInternalConfigurationCreator _internalConfigurationCreator; + private readonly Mock _claimsToThingCreator; + private readonly Mock _authOptionsCreator; + private readonly Mock _upstreamTemplatePatternCreator; + private readonly Mock _requestIdKeyCreator; + private readonly Mock _serviceProviderConfigCreator; + private readonly Mock _qosOptionsCreator; + private readonly Mock _fileReRouteOptionsCreator; + private readonly Mock _rateLimitOptions; + private readonly Mock _regionCreator; + private readonly Mock _httpHandlerOptionsCreator; + private readonly Mock _adminPath; private readonly Mock _headerFindAndReplaceCreator; private readonly Mock _downstreamAddressesCreator; @@ -48,7 +43,6 @@ namespace Ocelot.UnitTests.Configuration { _logger = new Mock(); _validator = new Mock(); - _fileConfig = new Mock>(); _claimsToThingCreator = new Mock(); _authOptionsCreator = new Mock(); _upstreamTemplatePatternCreator = new Mock(); @@ -63,8 +57,7 @@ namespace Ocelot.UnitTests.Configuration _headerFindAndReplaceCreator = new Mock(); _downstreamAddressesCreator = new Mock(); - _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( - _fileConfig.Object, + _internalConfigurationCreator = new FileInternalConfigurationCreator( _validator.Object, _logger.Object, _claimsToThingCreator.Object, @@ -262,7 +255,7 @@ namespace Ocelot.UnitTests.Configuration .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheFollowingRegionIsReturned("region")) .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheRegionCreatorIsCalledCorrectly("region")) + .Then(x => x.ThenTheRegionCreatorIsCalledCorrectly()) .And(x => x.ThenTheHeaderFindAndReplaceCreatorIsCalledCorrectly()) .BDDfy(); } @@ -800,14 +793,11 @@ namespace Ocelot.UnitTests.Configuration private void GivenTheConfigIs(FileConfiguration fileConfiguration) { _fileConfiguration = fileConfiguration; - _fileConfig - .Setup(x => x.Value) - .Returns(_fileConfiguration); } private void WhenICreateTheConfig() { - _config = _ocelotConfigurationCreator.Create(_fileConfiguration).Result; + _config = _internalConfigurationCreator.Create(_fileConfiguration).Result; } private void ThenTheReRoutesAre(List expectedReRoutes) @@ -928,7 +918,7 @@ namespace Ocelot.UnitTests.Configuration .Returns(region); } - private void ThenTheRegionCreatorIsCalledCorrectly(string expected) + private void ThenTheRegionCreatorIsCalledCorrectly() { _regionCreator .Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once); diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs deleted file mode 100644 index 506da50c..00000000 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationProviderTests.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using Moq; -using Ocelot.Configuration; -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 IFileConfigurationProvider _provider; - private Mock _repo; - private FileConfiguration _result; - private FileConfiguration _fileConfiguration; - - public FileConfigurationProviderTests() - { - _repo = new Mock(); - _provider = new FileConfigurationProvider(_repo.Object); - } - - [Fact] - public void should_return_file_configuration() - { - var config = new FileConfiguration(); - - this.Given(x => x.GivenTheConfigurationIs(config)) - .When(x => x.WhenIGetTheReRoutes()) - .Then(x => x.ThenTheRepoIsCalledCorrectly()) - .BDDfy(); - } - - private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) - { - _fileConfiguration = fileConfiguration; - _repo - .Setup(x => x.Get()) - .ReturnsAsync(new OkResponse(fileConfiguration)); - } - - private void WhenIGetTheReRoutes() - { - _result = _provider.Get().Result.Data; - } - - private void ThenTheRepoIsCalledCorrectly() - { - _repo - .Verify(x => x.Get(), Times.Once); - } - } -} diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs index 15dd3729..5b0e93db 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs @@ -18,12 +18,16 @@ namespace Ocelot.UnitTests.Configuration private IFileConfigurationRepository _repo; private FileConfiguration _result; private FileConfiguration _fileConfiguration; - private string _environmentName = "DEV"; + + // This is a bit dirty and it is dev.dev so that the ConfigurationBuilderExtensionsTests + // cant pick it up if they run in parralel..sigh these are not really unit + // tests but whatever... + private string _environmentName = "DEV.DEV"; public FileConfigurationRepositoryTests() { _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); - _repo = new FileConfigurationRepository(_hostingEnvironment.Object); + _repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); } [Fact] @@ -75,7 +79,7 @@ namespace Ocelot.UnitTests.Configuration { _environmentName = null; _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); - _repo = new FileConfigurationRepository(_hostingEnvironment.Object); + _repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); } private void GivenIHaveAConfiguration(FileConfiguration fileConfiguration) @@ -113,7 +117,7 @@ namespace Ocelot.UnitTests.Configuration private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) { - var configurationPath = $"{AppContext.BaseDirectory}/configuration{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; + var configurationPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs index e16148f2..ff33872c 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationSetterTests.cs @@ -18,19 +18,19 @@ namespace Ocelot.UnitTests.Configuration public class FileConfigurationSetterTests { private FileConfiguration _fileConfiguration; - private FileConfigurationSetter _configSetter; - private Mock _configRepo; - private Mock _configCreator; - private Response _configuration; + private FileAndInternalConfigurationSetter _configSetter; + private Mock _configRepo; + 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, _repo.Object); + _configRepo = new Mock(); + _configCreator = new Mock(); + _configSetter = new FileAndInternalConfigurationSetter(_configRepo.Object, _configCreator.Object, _repo.Object); } [Fact] @@ -38,11 +38,11 @@ namespace Ocelot.UnitTests.Configuration { var fileConfig = new FileConfiguration(); var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - var config = new OcelotConfiguration(new List(), string.Empty, serviceProviderConfig, "asdf"); + var config = new InternalConfiguration(new List(), string.Empty, serviceProviderConfig, "asdf"); this.Given(x => GivenTheFollowingConfiguration(fileConfig)) .And(x => GivenTheRepoReturns(new OkResponse())) - .And(x => GivenTheCreatorReturns(new OkResponse(config))) + .And(x => GivenTheCreatorReturns(new OkResponse(config))) .When(x => WhenISetTheConfiguration()) .Then(x => ThenTheConfigurationRepositoryIsCalledCorrectly()) .BDDfy(); @@ -67,7 +67,7 @@ namespace Ocelot.UnitTests.Configuration this.Given(x => GivenTheFollowingConfiguration(fileConfig)) .And(x => GivenTheRepoReturns(new OkResponse())) - .And(x => GivenTheCreatorReturns(new ErrorResponse(It.IsAny()))) + .And(x => GivenTheCreatorReturns(new ErrorResponse(It.IsAny()))) .When(x => WhenISetTheConfiguration()) .And(x => ThenAnErrorResponseIsReturned()) .BDDfy(); @@ -85,7 +85,7 @@ namespace Ocelot.UnitTests.Configuration _result.ShouldBeOfType(); } - private void GivenTheCreatorReturns(Response configuration) + private void GivenTheCreatorReturns(Response configuration) { _configuration = configuration; _configCreator diff --git a/test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs b/test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs deleted file mode 100644 index 55c5edc9..00000000 --- a/test/Ocelot.UnitTests/Configuration/HashMatcherTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -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/HeaderFindAndReplaceCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs index df37ca8b..a8f8644a 100644 --- a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs @@ -149,6 +149,7 @@ namespace Ocelot.UnitTests.Configuration .Then(x => ThenTheFollowingDownstreamIsReturned(downstream)) .BDDfy(); } + [Fact] public void should_add_trace_id_header() { diff --git a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs index cdc3f94d..50bf1f85 100644 --- a/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/InMemoryConfigurationRepositoryTests.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Configuration.Repository; @@ -14,14 +12,14 @@ namespace Ocelot.UnitTests.Configuration { public class InMemoryConfigurationRepositoryTests { - private readonly InMemoryOcelotConfigurationRepository _repo; - private IOcelotConfiguration _config; + private readonly InMemoryInternalConfigurationRepository _repo; + private IInternalConfiguration _config; private Response _result; - private Response _getResult; + private Response _getResult; public InMemoryConfigurationRepositoryTests() { - _repo = new InMemoryOcelotConfigurationRepository(); + _repo = new InMemoryInternalConfigurationRepository(); } [Fact] @@ -49,7 +47,7 @@ namespace Ocelot.UnitTests.Configuration private void WhenIGetTheConfiguration() { - _getResult = _repo.Get().Result; + _getResult = _repo.Get(); } private void GivenThereIsASavedConfiguration() @@ -58,14 +56,14 @@ namespace Ocelot.UnitTests.Configuration WhenIAddOrReplaceTheConfig(); } - private void GivenTheConfigurationIs(IOcelotConfiguration config) + private void GivenTheConfigurationIs(IInternalConfiguration config) { _config = config; } private void WhenIAddOrReplaceTheConfig() { - _result = _repo.AddOrReplace(_config).Result; + _result = _repo.AddOrReplace(_config); } private void ThenNoErrorsAreReturned() @@ -73,7 +71,7 @@ namespace Ocelot.UnitTests.Configuration _result.IsError.ShouldBeFalse(); } - class FakeConfig : IOcelotConfiguration + class FakeConfig : IInternalConfiguration { private readonly string _downstreamTemplatePath; diff --git a/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs b/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs deleted file mode 100644 index 982fe091..00000000 --- a/test/Ocelot.UnitTests/Configuration/OcelotConfigurationProviderTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.Collections.Generic; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -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() - { - var serviceProviderConfig = new ServiceProviderConfigurationBuilder().Build(); - - this.Given(x => x.GivenTheRepoReturns(new OkResponse(new OcelotConfiguration(new List(), string.Empty, serviceProviderConfig, "")))) - .When(x => x.WhenIGetTheConfig()) - .Then(x => x.TheFollowingIsReturned(new OkResponse(new OcelotConfiguration(new List(), string.Empty, serviceProviderConfig, "")))) - .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()) - .ReturnsAsync(config); - } - - private void WhenIGetTheConfig() - { - _result = _ocelotConfigurationProvider.Get().Result; - } - - 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 c0ae8b87..520d8274 100644 --- a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs @@ -8,32 +8,30 @@ using Ocelot.Responses; using TestStack.BDDfy; using Xunit; using Shouldly; -using Ocelot.Configuration.Provider; -using Microsoft.Extensions.DependencyInjection; using Ocelot.Raft; using Rafty.Concensus; -using Newtonsoft.Json; -using Rafty.FiniteStateMachine; using Ocelot.Configuration; namespace Ocelot.UnitTests.Controllers { + using Ocelot.Configuration.Repository; + public class FileConfigurationControllerTests { - private FileConfigurationController _controller; - private Mock _configGetter; - private Mock _configSetter; + private readonly FileConfigurationController _controller; + private readonly Mock _repo; + private readonly Mock _setter; private IActionResult _result; private FileConfiguration _fileConfiguration; - private Mock _provider; + private readonly Mock _provider; private Mock _node; public FileConfigurationControllerTests() { _provider = new Mock(); - _configGetter = new Mock(); - _configSetter = new Mock(); - _controller = new FileConfigurationController(_configGetter.Object, _configSetter.Object, _provider.Object); + _repo = new Mock(); + _setter = new Mock(); + _controller = new FileConfigurationController(_repo.Object, _setter.Object, _provider.Object); } [Fact] @@ -140,14 +138,14 @@ namespace Ocelot.UnitTests.Controllers private void GivenTheConfigSetterReturns(Response response) { - _configSetter + _setter .Setup(x => x.Set(It.IsAny())) .ReturnsAsync(response); } private void ThenTheConfigrationSetterIsCalledCorrectly() { - _configSetter + _setter .Verify(x => x.Set(_fileConfiguration), Times.Once); } @@ -168,7 +166,7 @@ namespace Ocelot.UnitTests.Controllers private void GivenTheGetConfigurationReturns(Ocelot.Responses.Response fileConfiguration) { - _configGetter + _repo .Setup(x => x.Get()) .ReturnsAsync(fileConfiguration); } @@ -180,7 +178,7 @@ namespace Ocelot.UnitTests.Controllers private void TheTheGetFileConfigurationIsCalledCorrectly() { - _configGetter + _repo .Verify(x => x.Get(), Times.Once); } diff --git a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs index 5d32b1ed..7c29977d 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs @@ -1,25 +1,213 @@ -using Microsoft.Extensions.Configuration; -using Ocelot.DependencyInjection; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.DependencyInjection +namespace Ocelot.UnitTests.DependencyInjection { + using System.Collections.Generic; + using System.IO; + using Newtonsoft.Json; + using Ocelot.Configuration.File; + using Microsoft.Extensions.Configuration; + using Ocelot.DependencyInjection; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + public class ConfigurationBuilderExtensionsTests { private IConfigurationRoot _configuration; private string _result; + private IConfigurationRoot _configRoot; + private FileConfiguration _globalConfig; + private FileConfiguration _reRouteA; + private FileConfiguration _reRouteB; + private FileConfiguration _aggregate; [Fact] public void should_add_base_url_to_config() { - this.Given(x => GivenTheBaseUrl("test")) - .When(x => WhenIGet("BaseUrl")) - .Then(x => ThenTheResultIs("test")) + this.Given(_ => GivenTheBaseUrl("test")) + .When(_ => WhenIGet("BaseUrl")) + .Then(_ => ThenTheResultIs("test")) .BDDfy(); } + [Fact] + public void should_merge_files() + { + this.Given(_ => GivenMultipleConfigurationFiles()) + .When(_ => WhenIAddOcelotConfiguration()) + .Then(_ => ThenTheConfigsAreMerged()) + .BDDfy(); + } + + private void GivenMultipleConfigurationFiles() + { + _globalConfig = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + BaseUrl = "BaseUrl", + RateLimitOptions = new FileRateLimitOptions + { + HttpStatusCode = 500, + ClientIdHeader = "ClientIdHeader", + DisableRateLimitHeaders = true, + QuotaExceededMessage = "QuotaExceededMessage", + RateLimitCounterPrefix = "RateLimitCounterPrefix" + }, + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Host = "Host", + Port = 80, + Type = "Type" + }, + RequestIdKey = "RequestIdKey" + } + }; + + _reRouteA = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamScheme = "DownstreamScheme", + DownstreamPathTemplate = "DownstreamPathTemplate", + Key = "Key", + UpstreamHost = "UpstreamHost", + UpstreamHttpMethod = new List + { + "UpstreamHttpMethod" + }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "Host", + Port = 80 + } + } + } + } + }; + + _reRouteB = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamScheme = "DownstreamSchemeB", + DownstreamPathTemplate = "DownstreamPathTemplateB", + Key = "KeyB", + UpstreamHost = "UpstreamHostB", + UpstreamHttpMethod = new List + { + "UpstreamHttpMethodB" + }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "HostB", + Port = 80 + } + } + }, + new FileReRoute + { + DownstreamScheme = "DownstreamSchemeBB", + DownstreamPathTemplate = "DownstreamPathTemplateBB", + Key = "KeyBB", + UpstreamHost = "UpstreamHostBB", + UpstreamHttpMethod = new List + { + "UpstreamHttpMethodBB" + }, + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "HostBB", + Port = 80 + } + } + } + } + }; + + _aggregate = new FileConfiguration + { + Aggregates = new List + { + new FileAggregateReRoute + { + ReRouteKeys = new List + { + "KeyB", + "KeyBB" + }, + UpstreamPathTemplate = "UpstreamPathTemplate", + }, + new FileAggregateReRoute + { + ReRouteKeys = new List + { + "KeyB", + "KeyBB" + }, + UpstreamPathTemplate = "UpstreamPathTemplate", + } + } + }; + + File.WriteAllText("ocelot.global.json", JsonConvert.SerializeObject(_globalConfig)); + File.WriteAllText("ocelot.reRoutesA.json", JsonConvert.SerializeObject(_reRouteA)); + File.WriteAllText("ocelot.reRoutesB.json", JsonConvert.SerializeObject(_reRouteB)); + File.WriteAllText("ocelot.aggregates.json", JsonConvert.SerializeObject(_aggregate)); + } + + private void WhenIAddOcelotConfiguration() + { + IConfigurationBuilder builder = new ConfigurationBuilder(); + builder.AddOcelot(); + _configRoot = builder.Build(); + } + + private void ThenTheConfigsAreMerged() + { + var fc = (FileConfiguration)_configRoot.Get(typeof(FileConfiguration)); + + fc.GlobalConfiguration.BaseUrl.ShouldBe(_globalConfig.GlobalConfiguration.BaseUrl); + fc.GlobalConfiguration.RateLimitOptions.ClientIdHeader.ShouldBe(_globalConfig.GlobalConfiguration.RateLimitOptions.ClientIdHeader); + fc.GlobalConfiguration.RateLimitOptions.DisableRateLimitHeaders.ShouldBe(_globalConfig.GlobalConfiguration.RateLimitOptions.DisableRateLimitHeaders); + fc.GlobalConfiguration.RateLimitOptions.HttpStatusCode.ShouldBe(_globalConfig.GlobalConfiguration.RateLimitOptions.HttpStatusCode); + fc.GlobalConfiguration.RateLimitOptions.QuotaExceededMessage.ShouldBe(_globalConfig.GlobalConfiguration.RateLimitOptions.QuotaExceededMessage); + fc.GlobalConfiguration.RateLimitOptions.RateLimitCounterPrefix.ShouldBe(_globalConfig.GlobalConfiguration.RateLimitOptions.RateLimitCounterPrefix); + fc.GlobalConfiguration.RequestIdKey.ShouldBe(_globalConfig.GlobalConfiguration.RequestIdKey); + fc.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(_globalConfig.GlobalConfiguration.ServiceDiscoveryProvider.Host); + fc.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(_globalConfig.GlobalConfiguration.ServiceDiscoveryProvider.Port); + fc.GlobalConfiguration.ServiceDiscoveryProvider.Type.ShouldBe(_globalConfig.GlobalConfiguration.ServiceDiscoveryProvider.Type); + + fc.ReRoutes.Count.ShouldBe(_reRouteA.ReRoutes.Count + _reRouteB.ReRoutes.Count); + + fc.ReRoutes.ShouldContain(x => x.DownstreamPathTemplate == _reRouteA.ReRoutes[0].DownstreamPathTemplate); + fc.ReRoutes.ShouldContain(x => x.DownstreamPathTemplate == _reRouteB.ReRoutes[0].DownstreamPathTemplate); + fc.ReRoutes.ShouldContain(x => x.DownstreamPathTemplate == _reRouteB.ReRoutes[1].DownstreamPathTemplate); + + fc.ReRoutes.ShouldContain(x => x.DownstreamScheme == _reRouteA.ReRoutes[0].DownstreamScheme); + fc.ReRoutes.ShouldContain(x => x.DownstreamScheme == _reRouteB.ReRoutes[0].DownstreamScheme); + fc.ReRoutes.ShouldContain(x => x.DownstreamScheme == _reRouteB.ReRoutes[1].DownstreamScheme); + + fc.ReRoutes.ShouldContain(x => x.Key == _reRouteA.ReRoutes[0].Key); + fc.ReRoutes.ShouldContain(x => x.Key == _reRouteB.ReRoutes[0].Key); + fc.ReRoutes.ShouldContain(x => x.Key == _reRouteB.ReRoutes[1].Key); + + fc.ReRoutes.ShouldContain(x => x.UpstreamHost == _reRouteA.ReRoutes[0].UpstreamHost); + fc.ReRoutes.ShouldContain(x => x.UpstreamHost == _reRouteB.ReRoutes[0].UpstreamHost); + fc.ReRoutes.ShouldContain(x => x.UpstreamHost == _reRouteB.ReRoutes[1].UpstreamHost); + + fc.Aggregates.Count.ShouldBe(_aggregate.Aggregates.Count); + } + private void GivenTheBaseUrl(string baseUrl) { #pragma warning disable CS0618 diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 0b13954a..b295a564 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -325,8 +325,8 @@ namespace Ocelot.UnitTests.DependencyInjection var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); var outputCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); var instance = (ICacheManager)outputCacheManager.ImplementationInstance; - var ocelotConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); - var ocelotConfigCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); + var ocelotConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); + var ocelotConfigCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); var fileConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); var fileConfigCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index d3363b00..95bcaf47 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -1,7 +1,4 @@ -using Ocelot.Middleware; -using Ocelot.Middleware.Multiplexer; - -namespace Ocelot.UnitTests.DownstreamRouteFinder +namespace Ocelot.UnitTests.DownstreamRouteFinder { using System.Collections.Generic; using System.Threading.Tasks; @@ -9,7 +6,6 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; - using Ocelot.Configuration.Provider; using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.Middleware; @@ -19,23 +15,26 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder using Shouldly; using TestStack.BDDfy; using Xunit; + using Ocelot.Configuration.Repository; + using Ocelot.Middleware; + using Ocelot.Middleware.Multiplexer; public class DownstreamRouteFinderMiddlewareTests { private readonly Mock _finder; - private readonly Mock _provider; + private readonly Mock _repo; private Response _downstreamRoute; - private IOcelotConfiguration _config; + private IInternalConfiguration _config; private Mock _loggerFactory; private Mock _logger; - private DownstreamRouteFinderMiddleware _middleware; - private DownstreamContext _downstreamContext; + private readonly DownstreamRouteFinderMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; private OcelotRequestDelegate _next; private readonly Mock _multiplexer; public DownstreamRouteFinderMiddlewareTests() { - _provider = new Mock(); + _repo = new Mock(); _finder = new Mock(); _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _loggerFactory = new Mock(); @@ -43,13 +42,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; _multiplexer = new Mock(); - _middleware = new DownstreamRouteFinderMiddleware(_next, _loggerFactory.Object, _finder.Object, _provider.Object, _multiplexer.Object); + _middleware = new DownstreamRouteFinderMiddleware(_next, _loggerFactory.Object, _finder.Object, _repo.Object, _multiplexer.Object); } [Fact] public void should_call_scoped_data_repository_correctly() { - var config = new OcelotConfiguration(null, null, new ServiceProviderConfigurationBuilder().Build(), ""); + var config = new InternalConfiguration(null, null, new ServiceProviderConfigurationBuilder().Build(), ""); var downstreamReRoute = new DownstreamReRouteBuilder() .WithDownstreamPathTemplate("any old string") @@ -74,19 +73,19 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder _middleware.Invoke(_downstreamContext).GetAwaiter().GetType(); } - private void GivenTheFollowingConfig(IOcelotConfiguration config) + private void GivenTheFollowingConfig(IInternalConfiguration config) { _config = config; - _provider + _repo .Setup(x => x.Get()) - .ReturnsAsync(new OkResponse(_config)); + .Returns(new OkResponse(_config)); } private void GivenTheDownStreamRouteFinderReturns(DownstreamRoute downstreamRoute) { _downstreamRoute = new OkResponse(downstreamRoute); _finder - .Setup(x => x.FindDownstreamRoute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.FindDownstreamRoute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(_downstreamRoute); } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index cb5c2a67..7406b1c5 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -2,8 +2,6 @@ using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.Provider; using Ocelot.DownstreamRouteFinder; using Ocelot.DownstreamRouteFinder.Finder; using Ocelot.DownstreamRouteFinder.UrlMatcher; @@ -23,7 +21,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private string _upstreamUrlPath; private Response _result; private List _reRoutesConfig; - private OcelotConfiguration _config; + private InternalConfiguration _config; private Response _match; private string _upstreamHttpMethod; private string _upstreamHost; @@ -711,7 +709,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void GivenTheConfigurationIs(List reRoutesConfig, string adminPath, ServiceProviderConfiguration serviceProviderConfig) { _reRoutesConfig = reRoutesConfig; - _config = new OcelotConfiguration(_reRoutesConfig, adminPath, serviceProviderConfig, ""); + _config = new InternalConfiguration(_reRoutesConfig, adminPath, serviceProviderConfig, ""); } private void GivenThereIsAnUpstreamUrlPath(string upstreamUrlPath) diff --git a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs index 6b52a1c5..8802a5bc 100644 --- a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs @@ -9,17 +9,17 @@ namespace Ocelot.UnitTests.Errors using TestStack.BDDfy; using Xunit; using Microsoft.AspNetCore.Http; - using Ocelot.Configuration.Provider; using Moq; using Ocelot.Configuration; using Ocelot.Errors; using Ocelot.Infrastructure.RequestData; using Ocelot.Middleware; + using Ocelot.Configuration.Repository; public class ExceptionHandlerMiddlewareTests { bool _shouldThrowAnException; - private readonly Mock _provider; + private readonly Mock _configRepo; private readonly Mock _repo; private Mock _loggerFactory; private Mock _logger; @@ -29,7 +29,7 @@ namespace Ocelot.UnitTests.Errors public ExceptionHandlerMiddlewareTests() { - _provider = new Mock(); + _configRepo = new Mock(); _repo = new Mock(); _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _loggerFactory = new Mock(); @@ -45,13 +45,13 @@ namespace Ocelot.UnitTests.Errors context.HttpContext.Response.StatusCode = (int)HttpStatusCode.OK; }; - _middleware = new ExceptionHandlerMiddleware(_next, _loggerFactory.Object, _provider.Object, _repo.Object); + _middleware = new ExceptionHandlerMiddleware(_next, _loggerFactory.Object, _configRepo.Object, _repo.Object); } [Fact] public void NoDownstreamException() { - var config = new OcelotConfiguration(null, null, null, null); + var config = new InternalConfiguration(null, null, null, null); this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) .And(_ => GivenTheConfigurationIs(config)) @@ -64,7 +64,7 @@ namespace Ocelot.UnitTests.Errors [Fact] public void DownstreamException() { - var config = new OcelotConfiguration(null, null, null, null); + var config = new InternalConfiguration(null, null, null, null); this.Given(_ => GivenAnExceptionWillBeThrownDownstream()) .And(_ => GivenTheConfigurationIs(config)) @@ -76,7 +76,7 @@ namespace Ocelot.UnitTests.Errors [Fact] public void ShouldSetRequestId() { - var config = new OcelotConfiguration(null, null, null, "requestidkey"); + var config = new InternalConfiguration(null, null, null, "requestidkey"); this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) .And(_ => GivenTheConfigurationIs(config)) @@ -89,7 +89,7 @@ namespace Ocelot.UnitTests.Errors [Fact] public void ShouldSetAspDotNetRequestId() { - var config = new OcelotConfiguration(null, null, null, null); + var config = new InternalConfiguration(null, null, null, null); this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) .And(_ => GivenTheConfigurationIs(config)) @@ -133,8 +133,8 @@ namespace Ocelot.UnitTests.Errors private void GivenTheConfigThrows() { var ex = new Exception("outer", new Exception("inner")); - _provider - .Setup(x => x.Get()).ThrowsAsync(ex); + _configRepo + .Setup(x => x.Get()).Throws(ex); } private void ThenAnExceptionIsThrown() @@ -144,9 +144,9 @@ namespace Ocelot.UnitTests.Errors private void GivenTheConfigReturnsError() { - var response = new Responses.ErrorResponse(new FakeError()); - _provider - .Setup(x => x.Get()).ReturnsAsync(response); + var response = new Responses.ErrorResponse(new FakeError()); + _configRepo + .Setup(x => x.Get()).Returns(response); } private void TheRequestIdIsSet(string key, string value) @@ -154,11 +154,11 @@ namespace Ocelot.UnitTests.Errors _repo.Verify(x => x.Add(key, value), Times.Once); } - private void GivenTheConfigurationIs(IOcelotConfiguration config) + private void GivenTheConfigurationIs(IInternalConfiguration config) { - var response = new Responses.OkResponse(config); - _provider - .Setup(x => x.Get()).ReturnsAsync(response); + var response = new Responses.OkResponse(config); + _configRepo + .Setup(x => x.Get()).Returns(response); } private void GivenAnExceptionWillNotBeThrownDownstream() diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index 8df6245a..395dde67 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -28,12 +28,6 @@ - - - PreserveNewest - - - diff --git a/test/Ocelot.UnitTests/configuration.json b/test/Ocelot.UnitTests/configuration.json deleted file mode 100755 index 618957b8..00000000 --- a/test/Ocelot.UnitTests/configuration.json +++ /dev/null @@ -1 +0,0 @@ -{"ReRoutes":[{"DownstreamPathTemplate":"/test/test/{test}","UpstreamPathTemplate":null,"UpstreamHttpMethod":null,"AuthenticationOptions":{"Provider":null,"ProviderRootUrl":null,"ApiName":null,"RequireHttps":false,"AllowedScopes":[],"ApiSecret":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 diff --git a/test/Ocelot.UnitTests/idsrv3test.pfx b/test/Ocelot.UnitTests/idsrv3test.pfx deleted file mode 100644 index 0247dea03f0cc23694291f21310f3ae88880e2bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3395 zcmY*ac{tQ<7yiu{V_$|rA%;e>y=E+9O_nU#g|hFBC`-tmWsqeoSyGW9LYA!AlQm0d zlqs?cuRUcMvVK$7_r34+{c)aipZh-Nxy~QY_1q{N(`7J-3WZ}lgwlyV(0Q=O1fl`u z;TYE;IL2iPy@0|&nf_0rK7rt<4^TL2G9|X44F8>Cqz8fXaF7!e4sw9vh0_0zrd-Yp zq5aB`c0pwf*#!pE3`1~`u};|qLmAL66%SqGD&c1ok7w*g=3CPGVk4GBqUnz5R$^lb z8Dv(rRpfX7yvJ$AZ8B=IukK|?oWq7THPW9AE8<%>%oONtPAOw&x8_?KHa0J|WVwA0 zIe9iq|#j@0h-r2z9#p>N7n4=mGfXBZdZv zm>}$|9($ZRdyt-g#VGBa?>B!qNzif-i+FE)kucwfM0uQ_?eH5E22H7{O&W(b9&xxe z%p<>vWCX)-exQO)Be=&=gf&-c#+j`(NUetfn}WVXG{= z^!3S{N|*XdJW@10Ikf3}LcuN>qA~Ixlg<}c;VO{NzpbcV)gX{XXMvCF$|Bihu8%Mj`v7 z@JI#bMy0mL?ntjDyu>tItFCrcM?2T4qxi{DAYXF4re+jt!0KM!4AX1-`m6J2B-j7$ ztQmXW9+nsyVA76pGD!SNDBJX7<=P3^TAtMP*S&|$8V_zcInNp6F})=P6L9WM3skx( zrU*k+zF?-S=hmjpL4Q3zv>!AS5ZdH` zP7@1%4o~2pGsTCkqHI#fTE9t6L}0I0RV#X80*5W8dQ!d^3i!EAcx!{g?Ymhx9_uH| z%5-;5L5^5@FPajHS9ShoBMyy!p(c{qxOAL#hI6ENh505_rZ0?SGHg>G?cH-JcX$bP zvvcygKZ|q33xcOvl0F>Lq;-3oT1}&U{+hFQhdrnZ&f3Cd?*G~+e;NZj-CLQ#d7u*d z-zLck*=~$_*oTD=7glD2s_n4ZBbndKCJM<*Y#U_RIHLGB-|y!WU`T^)1|P6xbeP|G zVeM+?bDY~u1~eh71YCS>5m|2W++)$^^VxHSdmxwhWqlh$#}_R*QJIE}!YhyC22(}y z-pGi)Mp$4isupi_SdyK1kwa|ypqYxDZM%%-W8XLUrq=uHuIVLfoLXn0Ft*+*&7DasMmP3gdi3$so3cjv zU3_I_!HIUJ-KLn$?yVs^q%Nt?{K4vH$8|KG-fP7I-JGh){ZkukKp&IeTFS zofK|@;`zesc<{wV&~=^Lpxwgq@1SZU!pFuL4xnXwJhXzpFXWPHqe5C^&F$XOKSyA*?hARwF^42%X)?En0pbR1|X1Ofs80A>9z2}c|9=>s8v zEFceP0#bk)B`W|LfCL~z!7_mQA0!RPQ8WpPf}*g$)hhsoqDlYhLQ^z_KfESzA7%UR z0wA<8pCMoXxBgEJg#e8I z^!ZaN7vLt~Loo#6Kiktl^Kj613iSpI0w}5OUj_7kE&%=Q0@7Z?>>U#@$=@yzfrG{o ztFTv(L~LX}xO!x0^EITtLxl@_o6uy5gghAR{hz9rAUI9X6qKa_Nw%q za~SdO27));Ss1O7WmAmU?z>@+sX7%|EH>F*@OZUVn!`%vFPjg13@;Tl|_JIFJuO?ibe+@(=CitY0KN zmhw8P&DGlJBqvEH_i~51(xCCqvU$O5a^w(gap!{;x$=mI;>(I{4_^3{xSVlt0*&Z-y38aD8;?f`*U1VzA?{YPa$fn^V7$cGLd)&c%khfmt-qvZ_d8X! z7hHsG8{dHEPrBwl**uN9qgJ5pDa-DS;*TkBvMr}WsGRp(tl&q zOLj#>q5fr!g3h>N*4Lo!^2f&yedb9`Kc@UII#(J*#=~mQpg7_^@Qad_`7&Rw^Q13P zmkj26C2^Lfg&(Un^M{l&&Z~Al#>~&po-IRgbH;zV|EZU6sq2W4r<`>`jAnHJX0F#X zoYLuTJJ&S__HOHM}CU)!}{mUnHM4&H-PJ zDgU|rTaFE6VJ^#8$-7}h}^b=$AFm^Ju%|Irt#Xm@y!x8ht)nP}yX zak6LD=XrWjz}YIk=NKi;Oyzuyhr4N#>$;BIHeVmO7CwR&BH~$h($R>lxm#|jH)hMo z7Cl?fME$4w@i!`TUwnfzepq`tb2MXQ>vjOez4DO&G+ zwbxqf;c;Lz7e^2GJN4&pn)*n036&#X{M)L}3jNt9WQoG#Ltw0 zBSd@4uASn_19~vFMd|jhEOlmOnzg#t-W`Y8`{ihls#Ej*@-YyvQR5@XB{Zgn*UU@bPjBb)ma-dM*TyAY#Qr-I?}ssTqWiQUU~9nVL8urj8g zB=?6~(E%Bt>5<*!OPB%-9y0pkl!uu8}JyuP^C{VwK-!6&8CcOsFR z#AD|e+mNE9i#41w#l(h}rbw&h^*Xp8>93ZTvg}r-DJps1W6hRpeV*HGw|(EWnX7>t zi;7~9X)yDN{8DJzLpxCoH*tL3SHK!$Z}tQc<%NTk$t)S*4<=4>wFvMd!y)pV_liw) z7Z+8=AXg^QgwL(&DRsQU5*({(LDt{G-4Rx#dhx6AP+_msH%Jue6QCy=B0w?y#4k$7;> z=5ttmpV&vFVv}ZY>6NE%#+W))M)nU;WMS%-mtLT!)&4oAMhnY2Hb@dJUGXLb^4wIex}=co7n{7tD1N!| zw63xzN%ImPTf3iZ?X@yq6*F$jX5my$Q%SSyOrlD)y}jkyw`e{y&l34ahp)821A!iS z4-;-p@j6Gn!f>FJQ2ZzwD76?f6_^_WN5dA?3G%E0bF79+L#MT|(Yv~t5ct?-mV0Fj V%$88{h~I%@Xjg7x^oQR@_8&Ry9S;Bi From 2b8c905a26f22271f08c221c7a6e8beff614dc68 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Wed, 18 Apr 2018 08:27:23 +0100 Subject: [PATCH 43/50] #296 remembered how to regex --- .../DependencyInjection/ConfigurationBuilderExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs index 00cb0578..9e471475 100644 --- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs +++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs @@ -30,12 +30,12 @@ namespace Ocelot.DependencyInjection public static IConfigurationBuilder AddOcelot(this IConfigurationBuilder builder) { - const string pattern = "(?i)ocelot(.*).json$"; + const string pattern = "(?i)ocelot.([a-zA-Z0-9]*).json"; var reg = new Regex(pattern); var files = Directory.GetFiles(".") - .Where(path => reg.IsMatch(path)).Where(x => x.Count(s => s == '.') == 3) + .Where(path => reg.IsMatch(path)) .ToList(); var fileConfiguration = new FileConfiguration(); From f9dc8659c0b14d7900e3625e41c3ff9d7cce5dc5 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Wed, 18 Apr 2018 08:48:21 +0100 Subject: [PATCH 44/50] forgot to update docs --- docs/features/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 0306c563..4c96eade 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -111,7 +111,7 @@ Instead of adding the configuration directly e.g. AddJsonFile("ocelot.json") you .AddEnvironmentVariables(); }) -In this scenario Ocelot will look for any files that match the pattern ocleot.*.json and then merge these together. If you want to set the GlobalConfiguration property you must have a file called ocelot.global.json. +In this scenario Ocelot will look for any files that match the pattern (?i)ocelot.([a-zA-Z0-9]*).json and then merge these together. If you want to set the GlobalConfiguration property you must have a file called ocelot.global.json. The way Ocelot merges the files is basically load them, loop over them, add any ReRoutes, add any AggregateReRoutes and if the file is called ocelot.global.json add the GlobalConfiguration aswell as any ReRoutes or AggregateReRoutes. Ocelot will then save the merged configuration to a file called ocelot.json and this will be used as the source of truth while ocelot is running. From 5e1605882be33a592a41fe08515a7cafbb67016d Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Wed, 18 Apr 2018 15:24:16 +0100 Subject: [PATCH 45/50] Feature/timeout for http client (#319) * #318 http client obeys Qos timeout or defaults to 90 seconds, which is think is default for http client anyway but zero docs.... * #318 updated docs to specify default timeout and make it clear how to set it on a ReRoute basis * #318 missed this * #318 missed this --- docs/features/qualityofservice.rst | 15 +++- src/Ocelot/Configuration/File/FileReRoute.cs | 1 + src/Ocelot/Configuration/QoSOptions.cs | 8 +- src/Ocelot/Requester/HttpClientBuilder.cs | 14 +++- .../Requester/HttpClientHttpRequester.cs | 4 + .../Responder/ErrorsToHttpStatusCodeMapper.cs | 70 ++++++++-------- .../ButterflyTracingTests.cs | 10 ++- test/Ocelot.AcceptanceTests/QoSTests.cs | 81 ++++++++++++++++++- test/Ocelot.ManualTest/ocelot.json | 15 ++++ .../Requester/HttpClientBuilderTests.cs | 3 + .../Requester/HttpClientHttpRequesterTest.cs | 66 +++++++++++++-- 11 files changed, 236 insertions(+), 51 deletions(-) diff --git a/docs/features/qualityofservice.rst b/docs/features/qualityofservice.rst index 9eebf923..17bf373d 100644 --- a/docs/features/qualityofservice.rst +++ b/docs/features/qualityofservice.rst @@ -17,6 +17,17 @@ Add the following section to a ReRoute configuration. 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. +TimeoutValue means if 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. \ No newline at end of file +You can set the TimeoutValue in isoldation of the ExceptionsAllowedBeforeBreaking and DurationOfBreak options. + +.. code-block:: json + + "QoSOptions": { + "TimeoutValue":5000 + } + +There is no point setting the other two in isolation as they affect each other :) + +If you do not add a QoS section QoS will not be used however Ocelot will default to a 90 second timeout +on all downstream requests. If someone needs this to be configurable open an issue. \ No newline at end of file diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 0b2a06f9..5948c268 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -48,5 +48,6 @@ namespace Ocelot.Configuration.File public string Key { get;set; } public List DelegatingHandlers {get;set;} public int Priority { get;set; } + public int Timeout { get; set; } } } diff --git a/src/Ocelot/Configuration/QoSOptions.cs b/src/Ocelot/Configuration/QoSOptions.cs index 651bd506..8c5d6d27 100644 --- a/src/Ocelot/Configuration/QoSOptions.cs +++ b/src/Ocelot/Configuration/QoSOptions.cs @@ -16,12 +16,12 @@ namespace Ocelot.Configuration TimeoutStrategy = timeoutStrategy; } - public int ExceptionsAllowedBeforeBreaking { get; private set; } + public int ExceptionsAllowedBeforeBreaking { get; } - public int DurationOfBreak { get; private set; } + public int DurationOfBreak { get; } - public int TimeoutValue { get; private set; } + public int TimeoutValue { get; } - public TimeoutStrategy TimeoutStrategy { get; private set; } + public TimeoutStrategy TimeoutStrategy { get; } } } diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index 348bb207..fe80b11b 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -17,6 +17,7 @@ namespace Ocelot.Requester private HttpClient _httpClient; private IHttpClient _client; private HttpClientHandler _httpclientHandler; + private readonly TimeSpan _defaultTimeout; public HttpClientBuilder( IDelegatingHandlerHandlerFactory factory, @@ -26,6 +27,10 @@ namespace Ocelot.Requester _factory = factory; _cacheHandlers = cacheHandlers; _logger = logger; + + // This is hardcoded at the moment but can easily be added to configuration + // if required by a user request. + _defaultTimeout = TimeSpan.FromSeconds(90); } public IHttpClient Create(DownstreamContext request) @@ -46,7 +51,14 @@ namespace Ocelot.Requester CookieContainer = new CookieContainer() }; - _httpClient = new HttpClient(CreateHttpMessageHandler(_httpclientHandler, request.DownstreamReRoute)); + var timeout = request.DownstreamReRoute.QosOptionsOptions.TimeoutValue == 0 + ? _defaultTimeout + : TimeSpan.FromMilliseconds(request.DownstreamReRoute.QosOptionsOptions.TimeoutValue); + + _httpClient = new HttpClient(CreateHttpMessageHandler(_httpclientHandler, request.DownstreamReRoute)) + { + Timeout = timeout + }; _client = new HttpClientWrapper(_httpClient); diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index ddbcf119..c7914c94 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -39,6 +39,10 @@ namespace Ocelot.Requester { return new ErrorResponse(new RequestTimedOutError(exception)); } + catch (TaskCanceledException exception) + { + return new ErrorResponse(new RequestTimedOutError(exception)); + } catch (BrokenCircuitException exception) { return new ErrorResponse(new RequestTimedOutError(exception)); diff --git a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs index be7c59b9..61d27de0 100644 --- a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs +++ b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs @@ -1,38 +1,38 @@ -using System.Collections.Generic; -using System.Linq; -using Ocelot.Errors; - -namespace Ocelot.Responder -{ - public class ErrorsToHttpStatusCodeMapper : IErrorsToHttpStatusCodeMapper - { - public int Map(List errors) - { - if (errors.Any(e => e.Code == OcelotErrorCode.UnauthenticatedError)) - { - return 401; - } - - if (errors.Any(e => e.Code == OcelotErrorCode.UnauthorizedError - || e.Code == OcelotErrorCode.ClaimValueNotAuthorisedError - || e.Code == OcelotErrorCode.ScopeNotAuthorisedError - || e.Code == OcelotErrorCode.UserDoesNotHaveClaimError - || e.Code == OcelotErrorCode.CannotFindClaimError)) - { - return 403; - } - - if (errors.Any(e => e.Code == OcelotErrorCode.RequestTimedOutError)) - { - return 503; +using System.Collections.Generic; +using System.Linq; +using Ocelot.Errors; + +namespace Ocelot.Responder +{ + public class ErrorsToHttpStatusCodeMapper : IErrorsToHttpStatusCodeMapper + { + public int Map(List errors) + { + if (errors.Any(e => e.Code == OcelotErrorCode.UnauthenticatedError)) + { + return 401; + } + + if (errors.Any(e => e.Code == OcelotErrorCode.UnauthorizedError + || e.Code == OcelotErrorCode.ClaimValueNotAuthorisedError + || e.Code == OcelotErrorCode.ScopeNotAuthorisedError + || e.Code == OcelotErrorCode.UserDoesNotHaveClaimError + || e.Code == OcelotErrorCode.CannotFindClaimError)) + { + return 403; + } + + if (errors.Any(e => e.Code == OcelotErrorCode.RequestTimedOutError)) + { + return 503; + } + + if (errors.Any(e => e.Code == OcelotErrorCode.UnableToFindDownstreamRouteError)) + { + return 404; } - if (errors.Any(e => e.Code == OcelotErrorCode.UnableToFindDownstreamRouteError)) - { - return 404; - } - return 404; - } - } -} \ No newline at end of file + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs index cde7b3bc..51f459ef 100644 --- a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs +++ b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs @@ -14,6 +14,8 @@ using static Rafty.Infrastructure.Wait; namespace Ocelot.AcceptanceTests { + using Xunit.Abstractions; + public class ButterflyTracingTests : IDisposable { private IWebHost _serviceOneBuilder; @@ -23,9 +25,11 @@ namespace Ocelot.AcceptanceTests private string _downstreamPathOne; private string _downstreamPathTwo; private int _butterflyCalled; + private readonly ITestOutputHelper _output; - public ButterflyTracingTests() + public ButterflyTracingTests(ITestOutputHelper output) { + _output = output; _steps = new Steps(); } @@ -104,7 +108,9 @@ namespace Ocelot.AcceptanceTests .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom")) .BDDfy(); - var commandOnAllStateMachines = WaitFor(10000).Until(() => _butterflyCalled == 4); + var commandOnAllStateMachines = WaitFor(10000).Until(() => _butterflyCalled >= 4); + + _output.WriteLine($"_butterflyCalled is {_butterflyCalled}"); commandOnAllStateMachines.ShouldBeTrue(); } diff --git a/test/Ocelot.AcceptanceTests/QoSTests.cs b/test/Ocelot.AcceptanceTests/QoSTests.cs index 5e82ba3d..0a9a110a 100644 --- a/test/Ocelot.AcceptanceTests/QoSTests.cs +++ b/test/Ocelot.AcceptanceTests/QoSTests.cs @@ -25,6 +25,82 @@ namespace Ocelot.AcceptanceTests _steps = new Steps(); } + [Fact] + public void should_not_timeout() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51569, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + QoSOptions = new FileQoSOptions + { + TimeoutValue = 1000, + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51569", 200, string.Empty, 10)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenThePostHasContent("postContent")) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + [Fact] + public void should_timeout() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51579, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + QoSOptions = new FileQoSOptions + { + TimeoutValue = 10, + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51579", 201, string.Empty, 1000)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenThePostHasContent("postContent")) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.ServiceUnavailable)) + .BDDfy(); + } + [Fact] public void should_open_circuit_breaker_then_close() { @@ -122,7 +198,7 @@ namespace Ocelot.AcceptanceTests }; this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51872", "Hello from Laura")) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", 200, "Hello from Tom")) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51880/", 200, "Hello from Tom", 0)) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .And(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -193,7 +269,7 @@ namespace Ocelot.AcceptanceTests _brokenService.Start(); } - private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) + private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody, int timeout) { _workingService = new WebHostBuilder() .UseUrls(url) @@ -205,6 +281,7 @@ namespace Ocelot.AcceptanceTests { app.Run(async context => { + Thread.Sleep(timeout); context.Response.StatusCode = statusCode; await context.Response.WriteAsync(responseBody); }); diff --git a/test/Ocelot.ManualTest/ocelot.json b/test/Ocelot.ManualTest/ocelot.json index 12f7f188..c21f8abb 100644 --- a/test/Ocelot.ManualTest/ocelot.json +++ b/test/Ocelot.ManualTest/ocelot.json @@ -1,5 +1,20 @@ { "ReRoutes": [ + { + "DownstreamPathTemplate": "/profile", + "DownstreamScheme": "http", + "UpstreamPathTemplate": "/profile", + "UpstreamHttpMethod": [ "Get" ], + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 3000 + } + ], + "QoSOptions": { + "TimeoutValue": 360000 + } + }, { "DownstreamPathTemplate": "/api/values", "DownstreamScheme": "http", diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index 45855f65..2f2bf1c7 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -50,6 +50,7 @@ namespace Ocelot.UnitTests.Requester .WithIsQos(false) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)) .WithReRouteKey("") + .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); this.Given(x => GivenTheFactoryReturns()) @@ -66,6 +67,7 @@ namespace Ocelot.UnitTests.Requester .WithIsQos(false) .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)) .WithReRouteKey("") + .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); var fakeOne = new FakeDelegatingHandler(); @@ -93,6 +95,7 @@ namespace Ocelot.UnitTests.Requester .WithIsQos(false) .WithHttpHandlerOptions(new HttpHandlerOptions(false, true, false)) .WithReRouteKey("") + .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); this.Given(_ => GivenADownstreamService()) diff --git a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs index c80ea391..d2210cf0 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs @@ -21,7 +21,7 @@ namespace Ocelot.UnitTests.Requester public class HttpClientHttpRequesterTest { private readonly Mock _cacheHandlers; - private Mock _factory; + private readonly Mock _factory; private Response _response; private readonly HttpClientHttpRequester _httpClientRequester; private DownstreamContext _request; @@ -47,8 +47,12 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_call_request_correctly() { - var reRoute = new DownstreamReRouteBuilder().WithIsQos(false) - .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)).WithReRouteKey("").Build(); + var reRoute = new DownstreamReRouteBuilder() + .WithIsQos(false) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)) + .WithReRouteKey("") + .WithQosOptions(new QoSOptionsBuilder().Build()) + .Build(); var context = new DownstreamContext(new DefaultHttpContext()) { @@ -66,8 +70,12 @@ namespace Ocelot.UnitTests.Requester [Fact] public void should_call_request_unable_to_complete_request() { - var reRoute = new DownstreamReRouteBuilder().WithIsQos(false) - .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)).WithReRouteKey("").Build(); + var reRoute = new DownstreamReRouteBuilder() + .WithIsQos(false) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)) + .WithReRouteKey("") + .WithQosOptions(new QoSOptionsBuilder().Build()) + .Build(); var context = new DownstreamContext(new DefaultHttpContext()) { @@ -81,6 +89,30 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } + [Fact] + public void http_client_request_times_out() + { + var reRoute = new DownstreamReRouteBuilder() + .WithIsQos(false) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)) + .WithReRouteKey("") + .WithQosOptions(new QoSOptionsBuilder().WithTimeoutValue(1).Build()) + .Build(); + + var context = new DownstreamContext(new DefaultHttpContext()) + { + DownstreamReRoute = reRoute, + DownstreamRequest = new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri("http://localhost:60080") }), + }; + + this.Given(_ => GivenTheRequestIs(context)) + .And(_ => GivenTheHouseReturnsTimeoutHandler()) + .When(_ => WhenIGetResponse()) + .Then(_ => ThenTheResponseIsCalledError()) + .And(_ => ThenTheErrorIsTimeout()) + .BDDfy(); + } + private void GivenTheRequestIs(DownstreamContext request) { _request = request; @@ -101,6 +133,11 @@ namespace Ocelot.UnitTests.Requester _response.IsError.ShouldBeTrue(); } + private void ThenTheErrorIsTimeout() + { + _response.Errors[0].ShouldBeOfType(); + } + private void GivenTheHouseReturnsOkHandler() { var handlers = new List> @@ -111,6 +148,16 @@ namespace Ocelot.UnitTests.Requester _factory.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse>>(handlers)); } + private void GivenTheHouseReturnsTimeoutHandler() + { + var handlers = new List> + { + () => new TimeoutDelegatingHandler() + }; + + _factory.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse>>(handlers)); + } + class OkDelegatingHandler : DelegatingHandler { protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) @@ -118,5 +165,14 @@ namespace Ocelot.UnitTests.Requester return Task.FromResult(new HttpResponseMessage()); } } + + class TimeoutDelegatingHandler : DelegatingHandler + { + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + await Task.Delay(100000, cancellationToken); + return new HttpResponseMessage(); + } + } } } From a5f3e0fa7531580d9612ce464586c06c132387f3 Mon Sep 17 00:00:00 2001 From: Joseph Bales Date: Wed, 18 Apr 2018 14:36:35 -0500 Subject: [PATCH 46/50] Fixing documentation typo (#321) --- docs/introduction/gettingstarted.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/introduction/gettingstarted.rst b/docs/introduction/gettingstarted.rst index 0c11f40d..089acc4f 100644 --- a/docs/introduction/gettingstarted.rst +++ b/docs/introduction/gettingstarted.rst @@ -9,7 +9,7 @@ built to netcoreapp2.0 `this Date: Fri, 20 Apr 2018 21:28:49 +0100 Subject: [PATCH 47/50] Feature/steeltoe (#324) * #262 - Integrated Steeltoe Service Discovery with Ocelot for review. * messing around * seems to be working with eureka * acceptance test passing with external lib references * #262 support for netflix eureka service discovery thanks to pivotal * #262 fixed warnings --- README.md | 3 +- docs/features/servicediscovery.rst | 35 +- .../DependencyInjection/IOcelotBuilder.cs | 2 +- .../DependencyInjection/OcelotBuilder.cs | 21 ++ .../Middleware/OcelotMiddlewareExtensions.cs | 11 + src/Ocelot/Ocelot.csproj | 1 + .../EurekaServiceDiscoveryProvider.cs | 34 ++ .../Providers/FakeEurekaDiscoveryClient.cs | 27 ++ .../ServiceDiscoveryProviderFactory.cs | 19 +- .../Ocelot.AcceptanceTests.csproj | 3 + .../ServiceDiscoveryTests.cs | 324 ++++++++++++++++-- test/Ocelot.AcceptanceTests/appsettings.json | 14 + .../appsettings.product.json | 24 ++ test/Ocelot.ManualTest/Program.cs | 40 +-- test/Ocelot.ManualTest/appsettings.json | 8 +- .../OcelotPipelineExtensionsTests.cs | 28 +- .../EurekaServiceDiscoveryProviderTests.cs | 117 +++++++ .../ServiceProviderFactoryTests.cs | 26 +- 18 files changed, 665 insertions(+), 72 deletions(-) create mode 100644 src/Ocelot/ServiceDiscovery/Providers/EurekaServiceDiscoveryProvider.cs create mode 100644 src/Ocelot/ServiceDiscovery/Providers/FakeEurekaDiscoveryClient.cs create mode 100644 test/Ocelot.AcceptanceTests/appsettings.product.json create mode 100644 test/Ocelot.UnitTests/ServiceDiscovery/EurekaServiceDiscoveryProviderTests.cs diff --git a/README.md b/README.md index f8ad7b22..edd28eaf 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio * Routing * Request Aggregation -* Service Discovery with Consul +* Service Discovery with Consul & Eureka * Service Fabric * WebSockets * Authentication @@ -52,6 +52,7 @@ A quick list of Ocelot's capabilities for more information see the [documentatio * Headers / Query String / Claims Transformation * Custom Middleware / Delegating Handlers * Configuration / Administration REST API +* Platform / Cloud agnostic ## How to install diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index 3add60f1..88b13057 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -52,4 +52,37 @@ If you are using ACL with Consul Ocelot supports adding the X-Consul-Token heade "Token": "footoken" } -Ocelot will add this token to the consul client that it uses to make requests and that is then used for every request. \ No newline at end of file +Ocelot will add this token to the consul client that it uses to make requests and that is then used for every request. + +Eureka +^^^^^^ + +This feature was requested as part of `Issue 262 `_ . to add support for Netflix's +Eureka service discovery provider. The main reason for this is it is a key part of `Steeltoe `_ which is something +to do with `Pivotal `_! Anyway enough of the background. + +In order to get this working add the following to ocelot.json.. + +.. code-block:: json + + "ServiceDiscoveryProvider": { + "Type": "Eureka" + } + +And following the guide `Here `_ you may also need to add some stuff to appsettings.json. For example the json below +tells the steeltoe / pivotal services where to look for the service discovery server and if the service should register with it. + +.. code-block:: json + + "eureka": { + "client": { + "serviceUrl": "http://localhost:8761/eureka/", + "shouldRegisterWithEureka": true + } + } + +Ocelot will now register all the necessary services when it starts up and if you have the json above will register itself with +Eureka. One of the services polls Eureka every 30 seconds (default) and gets the latest service state and persists this in memory. +When Ocelot asks for a given service it is retrieved from memory so performance is not a big problem. Please note that this code +is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them for all the hard work. + diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index a513e19e..7cf514cb 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -21,7 +21,7 @@ namespace Ocelot.DependencyInjection IOcelotBuilder AddSingletonDelegatingHandler(bool global = false) where T : DelegatingHandler; - + IOcelotBuilder AddTransientDelegatingHandler(bool global = false) where T : DelegatingHandler; diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 5373cc1b..672e8540 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -45,6 +45,8 @@ namespace Ocelot.DependencyInjection using Ocelot.Infrastructure.Consul; using Butterfly.Client.Tracing; using Ocelot.Middleware.Multiplexer; + using Pivotal.Discovery.Client; + using ServiceDiscovery.Providers; public class OcelotBuilder : IOcelotBuilder { @@ -112,6 +114,17 @@ namespace Ocelot.DependencyInjection _services.TryAddSingleton(); _services.TryAddSingleton(); + if (UsingEurekaServiceDiscoveryProvider(configurationRoot)) + { + _services.AddDiscoveryClient(configurationRoot); + } + else + { + _services.TryAddSingleton(); + } + + _services.TryAddSingleton(); + // see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc // could maybe use a scoped data repository _services.TryAddSingleton(); @@ -346,5 +359,13 @@ namespace Ocelot.DependencyInjection } }; } + + private static bool UsingEurekaServiceDiscoveryProvider(IConfiguration configurationRoot) + { + var type = configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Type", + string.Empty); + + return type.ToLower() == "eureka"; + } } } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 25d818c5..4fc3db7a 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -17,6 +17,7 @@ using Rafty.Concensus; using Rafty.Infrastructure; using Ocelot.Middleware.Pipeline; + using Pivotal.Discovery.Client; public static class OcelotMiddlewareExtensions { @@ -38,6 +39,11 @@ SetUpRafty(builder); } + if (UsingEurekaServiceDiscoveryProvider(configuration)) + { + builder.UseDiscoveryClient(); + } + ConfigureDiagnosticListener(builder); var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices); @@ -63,6 +69,11 @@ return builder; } + private static bool UsingEurekaServiceDiscoveryProvider(IInternalConfiguration configuration) + { + return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "eureka"; + } + private static bool UsingRafty(IApplicationBuilder builder) { var possible = builder.ApplicationServices.GetService(typeof(INode)) as INode; diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index 68f1dc9f..833887a2 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -46,6 +46,7 @@ + diff --git a/src/Ocelot/ServiceDiscovery/Providers/EurekaServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/Providers/EurekaServiceDiscoveryProvider.cs new file mode 100644 index 00000000..ab78db1b --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/Providers/EurekaServiceDiscoveryProvider.cs @@ -0,0 +1,34 @@ +namespace Ocelot.ServiceDiscovery.Providers +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Pivotal.Discovery.Client; + using Values; + + public class EurekaServiceDiscoveryProvider : IServiceDiscoveryProvider + { + private readonly IDiscoveryClient _client; + private readonly string _serviceName; + + public EurekaServiceDiscoveryProvider(string serviceName, IDiscoveryClient client) + { + _client = client; + _serviceName = serviceName; + } + + public Task> Get() + { + var services = new List(); + + var instances = _client.GetInstances(_serviceName); + + if (instances != null && instances.Any()) + { + services.AddRange(instances.Select(i => new Service(i.ServiceId, new ServiceHostAndPort(i.Host, i.Port), "", "", new List()))); + } + + return Task.FromResult(services); + } + } +} diff --git a/src/Ocelot/ServiceDiscovery/Providers/FakeEurekaDiscoveryClient.cs b/src/Ocelot/ServiceDiscovery/Providers/FakeEurekaDiscoveryClient.cs new file mode 100644 index 00000000..78612148 --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/Providers/FakeEurekaDiscoveryClient.cs @@ -0,0 +1,27 @@ +namespace Ocelot.ServiceDiscovery.Providers +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using Pivotal.Discovery.Client; + + public class FakeEurekaDiscoveryClient : IDiscoveryClient + { + public IServiceInstance GetLocalServiceInstance() + { + throw new System.NotImplementedException(); + } + + public IList GetInstances(string serviceId) + { + throw new System.NotImplementedException(); + } + + public Task ShutdownAsync() + { + throw new System.NotImplementedException(); + } + + public string Description { get; } + public IList Services { get; } + } +} diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index d13c333a..cd678c4f 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -8,15 +8,19 @@ using Ocelot.Values; namespace Ocelot.ServiceDiscovery { + using Pivotal.Discovery.Client; + public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory { private readonly IOcelotLoggerFactory _factory; - private readonly IConsulClientFactory _clientFactory; + private readonly IConsulClientFactory _consulFactory; + private readonly IDiscoveryClient _eurekaClient; - public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IConsulClientFactory clientFactory) + public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IConsulClientFactory consulFactory, IDiscoveryClient eurekaClient) { _factory = factory; - _clientFactory = clientFactory; + _consulFactory = consulFactory; + _eurekaClient = eurekaClient; } public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute) @@ -40,14 +44,19 @@ namespace Ocelot.ServiceDiscovery private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration serviceConfig, string serviceName) { - if (serviceConfig.Type == "ServiceFabric") + if (serviceConfig.Type?.ToLower() == "servicefabric") { var config = new ServiceFabricConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName); return new ServiceFabricServiceDiscoveryProvider(config); } + if (serviceConfig.Type?.ToLower() == "eureka") + { + return new EurekaServiceDiscoveryProvider(serviceName, _eurekaClient); + } + var consulRegistryConfiguration = new ConsulRegistryConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName, serviceConfig.Token); - return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory, _clientFactory); + return new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory, _consulFactory); } } } diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 18ff45f1..1824df0d 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -15,6 +15,9 @@ + + PreserveNewest + PreserveNewest diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs index 972b165f..151bb99c 100644 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs @@ -1,26 +1,29 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using Consul; -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 { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using Consul; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.File; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + using Newtonsoft.Json; + using Pivotal.Discovery.Client; + public class ServiceDiscoveryTests : IDisposable { private IWebHost _builderOne; private IWebHost _builderTwo; private IWebHost _fakeConsulBuilder; private readonly Steps _steps; - private readonly List _serviceEntries; + private readonly List _consulServices; + private readonly List _eurekaInstances; private int _counterOne; private int _counterTwo; private static readonly object SyncLock = new object(); @@ -31,11 +34,59 @@ namespace Ocelot.AcceptanceTests public ServiceDiscoveryTests() { _steps = new Steps(); - _serviceEntries = new List(); + _consulServices = new List(); + _eurekaInstances = new List(); } [Fact] - public void should_use_service_discovery_and_load_balance_request() + public void should_use_eureka_service_discovery_and_make_request() + { + var eurekaPort = 8761; + var serviceName = "product"; + var downstreamServicePort = 50371; + var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}"; + var fakeEurekaServiceDiscoveryUrl = $"http://localhost:{eurekaPort}"; + + var instanceOne = new FakeEurekaService(serviceName, "localhost", downstreamServicePort, false, + new Uri($"http://localhost:{downstreamServicePort}"), new Dictionary()); + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + ServiceName = serviceName, + LoadBalancer = "LeastConnection", + UseServiceDiscovery = true, + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Type = "Eureka" + } + } + }; + + this.Given(x => x.GivenEurekaProductServiceOneIsRunning(downstreamServiceOneUrl, 200)) + .And(x => x.GivenThereIsAFakeEurekaServiceDiscoveryProvider(fakeEurekaServiceDiscoveryUrl, serviceName)) + .And(x => x.GivenTheServicesAreRegisteredWithEureka(instanceOne)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(_ => _steps.ThenTheResponseBodyShouldBe(nameof(ServiceDiscoveryTests))) + .BDDfy(); + } + + [Fact] + public void should_use_consul_service_discovery_and_load_balance_request() { var consulPort = 8502; var serviceName = "product"; @@ -102,7 +153,6 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } - //test from issue #213 [Fact] public void should_handle_request_to_consul_for_downstream_service_and_make_request() { @@ -158,7 +208,6 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } - //test from issue #295 [Fact] public void should_use_token_to_make_request_to_consul() { @@ -218,7 +267,7 @@ namespace Ocelot.AcceptanceTests } [Fact] - public void should_send_request_to_service_after_it_becomes_available() + public void should_send_request_to_service_after_it_becomes_available_in_consul() { var consulPort = 8501; var serviceName = "product"; @@ -296,7 +345,7 @@ namespace Ocelot.AcceptanceTests private void WhenIAddAServiceBackIn(ServiceEntry serviceEntryTwo) { - _serviceEntries.Add(serviceEntryTwo); + _consulServices.Add(serviceEntryTwo); } private void ThenOnlyOneServiceHasBeenCalled() @@ -307,7 +356,7 @@ namespace Ocelot.AcceptanceTests private void WhenIRemoveAService(ServiceEntry serviceEntryTwo) { - _serviceEntries.Remove(serviceEntryTwo); + _consulServices.Remove(serviceEntryTwo); } private void GivenIResetCounters() @@ -332,10 +381,100 @@ namespace Ocelot.AcceptanceTests { foreach(var serviceEntry in serviceEntries) { - _serviceEntries.Add(serviceEntry); + _consulServices.Add(serviceEntry); } } + private void GivenTheServicesAreRegisteredWithEureka(params IServiceInstance[] serviceInstances) + { + foreach (var instance in serviceInstances) + { + _eurekaInstances.Add(instance); + } + } + + private void GivenThereIsAFakeEurekaServiceDiscoveryProvider(string url, string serviceName) + { + _fakeConsulBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + if (context.Request.Path.Value == "/eureka/apps/") + { + var apps = new List(); + + foreach (var serviceInstance in _eurekaInstances) + { + var a = new Application + { + name = serviceName, + instance = new List + { + new Instance + { + instanceId = $"{serviceInstance.Host}:{serviceInstance}", + hostName = serviceInstance.Host, + app = serviceName, + ipAddr = "127.0.0.1", + status = "UP", + overriddenstatus = "UNKNOWN", + port = new Port {value = serviceInstance.Port, enabled = "true"}, + securePort = new SecurePort {value = serviceInstance.Port, enabled = "true"}, + countryId = 1, + dataCenterInfo = new DataCenterInfo {value = "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", name = "MyOwn"}, + leaseInfo = new LeaseInfo + { + renewalIntervalInSecs = 30, + durationInSecs = 90, + registrationTimestamp = 1457714988223, + lastRenewalTimestamp= 1457716158319, + evictionTimestamp = 0, + serviceUpTimestamp = 1457714988223 + }, + metadata = new Metadata + { + value = "java.util.Collections$EmptyMap" + }, + homePageUrl = $"{serviceInstance.Host}:{serviceInstance.Port}", + statusPageUrl = $"{serviceInstance.Host}:{serviceInstance.Port}", + healthCheckUrl = $"{serviceInstance.Host}:{serviceInstance.Port}", + vipAddress = serviceName, + isCoordinatingDiscoveryServer = "false", + lastUpdatedTimestamp = "1457714988223", + lastDirtyTimestamp = "1457714988172", + actionType = "ADDED" + } + } + }; + + apps.Add(a); + } + + var applications = new EurekaApplications + { + applications = new Applications + { + application = apps, + apps__hashcode = "UP_1_", + versions__delta = "1" + } + }; + + await context.Response.WriteJsonAsync(applications); + } + }); + }) + .Build(); + + _fakeConsulBuilder.Start(); + } + private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName) { _fakeConsulBuilder = new WebHostBuilder() @@ -355,7 +494,7 @@ namespace Ocelot.AcceptanceTests _receivedToken = values.First(); } - await context.Response.WriteJsonAsync(_serviceEntries); + await context.Response.WriteJsonAsync(_consulServices); } }); }) @@ -433,6 +572,34 @@ namespace Ocelot.AcceptanceTests _builderTwo.Start(); } + private void GivenEurekaProductServiceOneIsRunning(string url, int statusCode) + { + _builderOne = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + try + { + context.Response.StatusCode = 200; + await context.Response.WriteAsync(nameof(ServiceDiscoveryTests)); + } + catch (Exception exception) + { + await context.Response.WriteAsync(exception.StackTrace); + } + }); + }) + .Build(); + + _builderOne.Start(); + } + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) { _builder = new WebHostBuilder() @@ -471,4 +638,113 @@ namespace Ocelot.AcceptanceTests _steps.Dispose(); } } + + public class FakeEurekaService : IServiceInstance + { + public FakeEurekaService(string serviceId, string host, int port, bool isSecure, Uri uri, IDictionary metadata) + { + ServiceId = serviceId; + Host = host; + Port = port; + IsSecure = isSecure; + Uri = uri; + Metadata = metadata; + } + + public string ServiceId { get; } + public string Host { get; } + public int Port { get; } + public bool IsSecure { get; } + public Uri Uri { get; } + public IDictionary Metadata { get; } + } + + public class Port + { + [JsonProperty("$")] + public int value { get; set; } + + [JsonProperty("@enabled")] + public string enabled { get; set; } + } + + public class SecurePort + { + [JsonProperty("$")] + public int value { get; set; } + + [JsonProperty("@enabled")] + public string enabled { get; set; } + } + + public class DataCenterInfo + { + [JsonProperty("@class")] + public string value { get; set; } + + public string name { get; set; } + } + + public class LeaseInfo + { + public int renewalIntervalInSecs { get; set; } + + public int durationInSecs { get; set; } + + public long registrationTimestamp { get; set; } + + public long lastRenewalTimestamp { get; set; } + + public int evictionTimestamp { get; set; } + + public long serviceUpTimestamp { get; set; } + } + + public class Metadata + { + [JsonProperty("@class")] + public string value { get; set; } + } + + public class Instance + { + public string instanceId { get; set; } + public string hostName { get; set; } + public string app { get; set; } + public string ipAddr { get; set; } + public string status { get; set; } + public string overriddenstatus { get; set; } + public Port port { get; set; } + public SecurePort securePort { get; set; } + public int countryId { get; set; } + public DataCenterInfo dataCenterInfo { get; set; } + public LeaseInfo leaseInfo { get; set; } + public Metadata metadata { get; set; } + public string homePageUrl { get; set; } + public string statusPageUrl { get; set; } + public string healthCheckUrl { get; set; } + public string vipAddress { get; set; } + public string isCoordinatingDiscoveryServer { get; set; } + public string lastUpdatedTimestamp { get; set; } + public string lastDirtyTimestamp { get; set; } + public string actionType { get; set; } + } + + public class Application + { + public string name { get; set; } + public List instance { get; set; } + } + + public class Applications + { + public string versions__delta { get; set; } + public string apps__hashcode { get; set; } + public List application { get; set; } + } + + public class EurekaApplications + { + public Applications applications { get; set; } + } } diff --git a/test/Ocelot.AcceptanceTests/appsettings.json b/test/Ocelot.AcceptanceTests/appsettings.json index 6d9b7c55..36499f4d 100644 --- a/test/Ocelot.AcceptanceTests/appsettings.json +++ b/test/Ocelot.AcceptanceTests/appsettings.json @@ -6,5 +6,19 @@ "System": "Error", "Microsoft": "Error" } + }, + "spring": { + "application": { + "name": "ocelot" + } + }, + "eureka": { + "client": { + "serviceUrl": "http://localhost:8761/eureka/", + "shouldRegisterWithEureka": true, + "shouldFetchRegistry": true, + "port": 5000, + "hostName": "localhost" + } } } diff --git a/test/Ocelot.AcceptanceTests/appsettings.product.json b/test/Ocelot.AcceptanceTests/appsettings.product.json new file mode 100644 index 00000000..8d8f06c7 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/appsettings.product.json @@ -0,0 +1,24 @@ +{ + "Logging": { + "IncludeScopes": true, + "LogLevel": { + "Default": "Error", + "System": "Error", + "Microsoft": "Error" + } + }, + "spring": { + "application": { + "name": "product" + } + }, + "eureka": { + "client": { + "serviceUrl": "http://localhost:8761/eureka/", + "shouldRegisterWithEureka": true + }, + "instance": { + "port": 50371 + } + } +} diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index 966b1097..dcc7676d 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -1,13 +1,13 @@ -using System.IO; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Configuration; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; - -namespace Ocelot.ManualTest +namespace Ocelot.ManualTest { + using System.IO; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.DependencyInjection; + using Ocelot.Middleware; + public class Program { public static void Main(string[] args) @@ -32,17 +32,17 @@ namespace Ocelot.ManualTest x.Audience = "test"; }); - s.AddOcelot() - .AddCacheManager(x => - { - x.WithDictionaryHandle(); - }) - /* .AddOpenTracing(option => - { - option.CollectorUrl = "http://localhost:9618"; - option.Service = "Ocelot.ManualTest"; - })*/ - .AddAdministration("/administration", "secret"); + s.AddOcelot() + .AddCacheManager(x => + { + x.WithDictionaryHandle(); + }) + /*.AddOpenTracing(option => + { + option.CollectorUrl = "http://localhost:9618"; + option.Service = "Ocelot.ManualTest"; + })*/ + .AddAdministration("/administration", "secret"); }) .ConfigureLogging((hostingContext, logging) => { diff --git a/test/Ocelot.ManualTest/appsettings.json b/test/Ocelot.ManualTest/appsettings.json index 584fc4fc..16fe363b 100644 --- a/test/Ocelot.ManualTest/appsettings.json +++ b/test/Ocelot.ManualTest/appsettings.json @@ -2,9 +2,15 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Trace", + "Default": "Error", "System": "Error", "Microsoft": "Error" } + }, + "eureka": { + "client": { + "serviceUrl": "http://localhost:8761/eureka/", + "shouldRegisterWithEureka": true + } } } diff --git a/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs index d61124ba..17f9e3e8 100644 --- a/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs +++ b/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs @@ -1,20 +1,15 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using Ocelot.Middleware.Multiplexer; -using Ocelot.Middleware.Pipeline; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - namespace Ocelot.UnitTests.Middleware { + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.DependencyInjection; + using Ocelot.Middleware; + using Ocelot.Middleware.Pipeline; + using Pivotal.Discovery.Client; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + public class OcelotPipelineExtensionsTests { private OcelotPipelineBuilder _builder; @@ -45,9 +40,10 @@ namespace Ocelot.UnitTests.Middleware var root = test.Build(); var services = new ServiceCollection(); services.AddSingleton(root); + services.AddDiscoveryClient(new DiscoveryOptions {ClientType = DiscoveryClientType.EUREKA}); services.AddOcelot(); var provider = services.BuildServiceProvider(); _builder = new OcelotPipelineBuilder(provider); } } -} \ No newline at end of file +} diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/EurekaServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/EurekaServiceDiscoveryProviderTests.cs new file mode 100644 index 00000000..0ec071d1 --- /dev/null +++ b/test/Ocelot.UnitTests/ServiceDiscovery/EurekaServiceDiscoveryProviderTests.cs @@ -0,0 +1,117 @@ +namespace Ocelot.UnitTests.ServiceDiscovery +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Moq; + using Ocelot.ServiceDiscovery.Providers; + using Pivotal.Discovery.Client; + using Shouldly; + using TestStack.BDDfy; + using Values; + using Xunit; + + public class EurekaServiceDiscoveryProviderTests + { + private readonly EurekaServiceDiscoveryProvider _provider; + private readonly Mock _client; + private readonly string _serviceId; + private List _instances; + private List _result; + + public EurekaServiceDiscoveryProviderTests() + { + _serviceId = "Laura"; + _client = new Mock(); + _provider = new EurekaServiceDiscoveryProvider(_serviceId, _client.Object); + } + + [Fact] + public void should_return_empty_services() + { + this.When(_ => WhenIGet()) + .Then(_ => ThenTheCountIs(0)) + .BDDfy(); + } + + [Fact] + public void should_return_service_from_client() + { + var instances = new List + { + new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary()) + }; + + this.Given(_ => GivenThe(instances)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheCountIs(1)) + .And(_ => ThenTheClientIsCalledCorrectly()) + .And(_ => ThenTheServiceIsMapped()) + .BDDfy(); + } + + [Fact] + public void should_return_services_from_client() + { + var instances = new List + { + new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary()), + new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary()) + }; + + this.Given(_ => GivenThe(instances)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheCountIs(2)) + .And(_ => ThenTheClientIsCalledCorrectly()) + .BDDfy(); + } + + private void ThenTheServiceIsMapped() + { + _result[0].HostAndPort.DownstreamHost.ShouldBe("somehost"); + _result[0].HostAndPort.DownstreamPort.ShouldBe(801); + _result[0].Name.ShouldBe(_serviceId); + } + + private void ThenTheCountIs(int expected) + { + _result.Count.ShouldBe(expected); + } + + private void ThenTheClientIsCalledCorrectly() + { + _client.Verify(x => x.GetInstances(_serviceId), Times.Once); + } + + private async Task WhenIGet() + { + _result = await _provider.Get(); + } + + private void GivenThe(List instances) + { + _instances = instances; + _client.Setup(x => x.GetInstances(It.IsAny())).Returns(instances); + } + } + + public class EurekaService : IServiceInstance + { + public EurekaService(string serviceId, string host, int port, bool isSecure, Uri uri, IDictionary metadata) + { + ServiceId = serviceId; + Host = host; + Port = port; + IsSecure = isSecure; + Uri = uri; + Metadata = metadata; + } + + public string ServiceId { get; } + public string Host { get; } + public int Port { get; } + public bool IsSecure { get; } + public Uri Uri { get; } + public IDictionary Metadata { get; } + } +} diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index 3e998431..4621913a 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -13,6 +13,8 @@ using Xunit; namespace Ocelot.UnitTests.ServiceDiscovery { + using Pivotal.Discovery.Client; + public class ServiceProviderFactoryTests { private ServiceProviderConfiguration _serviceConfig; @@ -20,13 +22,13 @@ namespace Ocelot.UnitTests.ServiceDiscovery private readonly ServiceDiscoveryProviderFactory _factory; private DownstreamReRoute _reRoute; private Mock _loggerFactory; - private IConsulClientFactory _clientFactory; + private Mock _discoveryClient; public ServiceProviderFactoryTests() { _loggerFactory = new Mock(); - _clientFactory = new ConsulClientFactory(); - _factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, _clientFactory); + _discoveryClient = new Mock(); + _factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, new ConsulClientFactory(), _discoveryClient.Object); } [Fact] @@ -99,6 +101,24 @@ namespace Ocelot.UnitTests.ServiceDiscovery .BDDfy(); } + [Fact] + public void should_return_eureka_provider() + { + var reRoute = new DownstreamReRouteBuilder() + .WithServiceName("product") + .WithUseServiceDiscovery(true) + .Build(); + + var serviceConfig = new ServiceProviderConfigurationBuilder() + .WithType("Eureka") + .Build(); + + this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) + .When(x => x.WhenIGetTheServiceProvider()) + .Then(x => x.ThenTheServiceProviderIs()) + .BDDfy(); + } + private void ThenTheFollowingServicesAreReturned(List downstreamAddresses) { var result = (ConfigurationServiceProvider)_result; From 636d116491a3ae0a85bed9b91ed9041be6ae8549 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Sun, 22 Apr 2018 12:05:49 +0100 Subject: [PATCH 48/50] #309 allow users to ignore ssl warnings, not sure this is advisable (#325) * #309 allow users to ignore ssl warnings, not sure this is advisable * #309 docs for ssl ignore --- docs/features/configuration.rst | 14 +- .../Builder/DownstreamReRouteBuilder.cs | 10 +- .../FileInternalConfigurationCreator.cs | 1 + src/Ocelot/Configuration/DownstreamReRoute.cs | 5 +- src/Ocelot/Configuration/File/FileReRoute.cs | 1 + src/Ocelot/Requester/HttpClientBuilder.cs | 27 ++-- .../Ocelot.AcceptanceTests.csproj | 3 + test/Ocelot.AcceptanceTests/SslTests.cs | 146 ++++++++++++++++++ .../Requester/HttpClientBuilderTests.cs | 24 +++ 9 files changed, 218 insertions(+), 13 deletions(-) create mode 100644 test/Ocelot.AcceptanceTests/SslTests.cs diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 4c96eade..343d7cbe 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -64,7 +64,8 @@ Here is an example ReRoute configuration, You don't need to set all of these thi "UseCookieContainer": true, "UseTracing": true }, - "UseServiceDiscovery": false + "UseServiceDiscovery": false, + "DangerousAcceptAnyServerCertificateValidator": false } More information on how to use these options is below.. @@ -160,3 +161,14 @@ noticed that the cookies were being shared. I tried to think of a nice way to ha that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight requests. This would also mean that subsequent requests dont use the cookies from the previous response! All in all not a great situation. I would avoid setting UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request! + +SSL Errors +---------- + +Id you want to ignore SSL warnings / errors set the following in your ReRoute config. + +.. code-block:: json + + "DangerousAcceptAnyServerCertificateValidator": false + +I don't reccomend doing this, I suggest creating your own certificate and then getting it trusted by your local / remote machine if you can. \ No newline at end of file diff --git a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs index 49931421..15fddb97 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs @@ -40,6 +40,7 @@ namespace Ocelot.Configuration.Builder private List _delegatingHandlers; private List _addHeadersToDownstream; private List _addHeadersToUpstream; + private bool _dangerousAcceptAnyServerCertificateValidator; public DownstreamReRouteBuilder() { @@ -241,6 +242,12 @@ namespace Ocelot.Configuration.Builder return this; } + public DownstreamReRouteBuilder WithDangerousAcceptAnyServerCertificateValidator(bool dangerousAcceptAnyServerCertificateValidator) + { + _dangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator; + return this; + } + public DownstreamReRoute Build() { return new DownstreamReRoute( @@ -272,7 +279,8 @@ namespace Ocelot.Configuration.Builder _reRouteKey, _delegatingHandlers, _addHeadersToDownstream, - _addHeadersToUpstream); + _addHeadersToUpstream, + _dangerousAcceptAnyServerCertificateValidator); } } } diff --git a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs index c14e4aab..46eec9d6 100644 --- a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs @@ -213,6 +213,7 @@ namespace Ocelot.Configuration.Creator .WithDelegatingHandlers(fileReRoute.DelegatingHandlers) .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) .WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream) + .WithDangerousAcceptAnyServerCertificateValidator(fileReRoute.DangerousAcceptAnyServerCertificateValidator) .Build(); return reRoute; diff --git a/src/Ocelot/Configuration/DownstreamReRoute.cs b/src/Ocelot/Configuration/DownstreamReRoute.cs index 90e96ec5..4fe89f22 100644 --- a/src/Ocelot/Configuration/DownstreamReRoute.cs +++ b/src/Ocelot/Configuration/DownstreamReRoute.cs @@ -35,8 +35,10 @@ namespace Ocelot.Configuration string reRouteKey, List delegatingHandlers, List addHeadersToDownstream, - List addHeadersToUpstream) + List addHeadersToUpstream, + bool dangerousAcceptAnyServerCertificateValidator) { + DangerousAcceptAnyServerCertificateValidator = dangerousAcceptAnyServerCertificateValidator; AddHeadersToDownstream = addHeadersToDownstream; DelegatingHandlers = delegatingHandlers; Key = key; @@ -97,5 +99,6 @@ namespace Ocelot.Configuration public List DelegatingHandlers {get;private set;} public List AddHeadersToDownstream {get;private set;} public List AddHeadersToUpstream { get; private set; } + public bool DangerousAcceptAnyServerCertificateValidator { get; private set; } } } diff --git a/src/Ocelot/Configuration/File/FileReRoute.cs b/src/Ocelot/Configuration/File/FileReRoute.cs index 5948c268..acc6572a 100644 --- a/src/Ocelot/Configuration/File/FileReRoute.cs +++ b/src/Ocelot/Configuration/File/FileReRoute.cs @@ -49,5 +49,6 @@ namespace Ocelot.Configuration.File public List DelegatingHandlers {get;set;} public int Priority { get;set; } public int Timeout { get; set; } + public bool DangerousAcceptAnyServerCertificateValidator { get; set; } } } diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index fe80b11b..fdc90ebf 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -16,7 +16,6 @@ namespace Ocelot.Requester private string _cacheKey; private HttpClient _httpClient; private IHttpClient _client; - private HttpClientHandler _httpclientHandler; private readonly TimeSpan _defaultTimeout; public HttpClientBuilder( @@ -33,9 +32,9 @@ namespace Ocelot.Requester _defaultTimeout = TimeSpan.FromSeconds(90); } - public IHttpClient Create(DownstreamContext request) + public IHttpClient Create(DownstreamContext context) { - _cacheKey = GetCacheKey(request); + _cacheKey = GetCacheKey(context); var httpClient = _cacheHandlers.Get(_cacheKey); @@ -44,18 +43,26 @@ namespace Ocelot.Requester return httpClient; } - _httpclientHandler = new HttpClientHandler + var httpclientHandler = new HttpClientHandler { - AllowAutoRedirect = request.DownstreamReRoute.HttpHandlerOptions.AllowAutoRedirect, - UseCookies = request.DownstreamReRoute.HttpHandlerOptions.UseCookieContainer, + AllowAutoRedirect = context.DownstreamReRoute.HttpHandlerOptions.AllowAutoRedirect, + UseCookies = context.DownstreamReRoute.HttpHandlerOptions.UseCookieContainer, CookieContainer = new CookieContainer() }; - var timeout = request.DownstreamReRoute.QosOptionsOptions.TimeoutValue == 0 - ? _defaultTimeout - : TimeSpan.FromMilliseconds(request.DownstreamReRoute.QosOptionsOptions.TimeoutValue); + if(context.DownstreamReRoute.DangerousAcceptAnyServerCertificateValidator) + { + httpclientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; - _httpClient = new HttpClient(CreateHttpMessageHandler(_httpclientHandler, request.DownstreamReRoute)) + _logger + .LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {context.DownstreamReRoute.DownstreamPathTemplate}"); + } + + var timeout = context.DownstreamReRoute.QosOptionsOptions.TimeoutValue == 0 + ? _defaultTimeout + : TimeSpan.FromMilliseconds(context.DownstreamReRoute.QosOptionsOptions.TimeoutValue); + + _httpClient = new HttpClient(CreateHttpMessageHandler(httpclientHandler, context.DownstreamReRoute)) { Timeout = timeout }; diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 1824df0d..9edbfd74 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -21,6 +21,9 @@ PreserveNewest + + PreserveNewest + diff --git a/test/Ocelot.AcceptanceTests/SslTests.cs b/test/Ocelot.AcceptanceTests/SslTests.cs new file mode 100644 index 00000000..309bbf28 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/SslTests.cs @@ -0,0 +1,146 @@ +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 SslTests : IDisposable + { + private IWebHost _builder; + private readonly Steps _steps; + private string _downstreamPath; + + public SslTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_dangerous_accept_any_server_certificate_validator() + { + int port = 51129; + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "https", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + DangerousAcceptAnyServerCertificateValidator = true + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"https://localhost:{port}", "/", 200, "Hello from Laura", port)) + .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_dangerous_accept_any_server_certificate_validator() + { + int port = 52129; + + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "https", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + DangerousAcceptAnyServerCertificateValidator = false + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn($"https://localhost:{port}", "/", 200, "Hello from Laura", port)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.NotFound)) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody, int port) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel(options => + { + options.Listen(IPAddress.Loopback, port, listenOptions => + { + listenOptions.UseHttps("idsrv3test.pfx", "idsrv3test"); + }); + }) + .UseContentRoot(Directory.GetCurrentDirectory()) + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if(_downstreamPath != basePath) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); + }) + .Build(); + + _builder.Start(); + } + + internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) + { + _downstreamPath.ShouldBe(expectedDownstreamPath); + } + + public void Dispose() + { + _builder?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index 2f2bf1c7..d4bc994c 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -60,6 +60,25 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } + [Fact] + public void should_log_if_ignoring_ssl_errors() + { + var reRoute = new DownstreamReRouteBuilder() + .WithIsQos(false) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)) + .WithReRouteKey("") + .WithQosOptions(new QoSOptionsBuilder().Build()) + .WithDangerousAcceptAnyServerCertificateValidator(true) + .Build(); + + this.Given(x => GivenTheFactoryReturns()) + .And(x => GivenARequest(reRoute)) + .When(x => WhenIBuild()) + .Then(x => ThenTheHttpClientShouldNotBeNull()) + .Then(x => ThenTheDangerousAcceptAnyServerCertificateValidatorWarningIsLogged()) + .BDDfy(); + } + [Fact] public void should_call_delegating_handlers_in_order() { @@ -111,6 +130,11 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } + private void ThenTheDangerousAcceptAnyServerCertificateValidatorWarningIsLogged() + { + _logger.Verify(x => x.LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {_context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {_context.DownstreamReRoute.DownstreamPathTemplate}"), Times.Once); + } + private void GivenTheClientIsCached() { _cacheHandlers.Setup(x => x.Get(It.IsAny())).Returns(_httpClient); From 77211e9f16e178a2bc8d2cba25ca4dde52894fa5 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Tue, 24 Apr 2018 08:30:17 +0100 Subject: [PATCH 49/50] Feature/store configuraton json idented (#328) * messing around with benchmark.net.seems Ocelot adds about 2ms to a request..lets make this less? :) * #326 store json indented so it looks nice :P --- docs/features/configuration.rst | 2 +- docs/features/servicediscovery.rst | 8 +- .../ConsulFileConfigurationRepository.cs | 4 +- .../DiskFileConfigurationRepository.cs | 2 +- .../Consul/ConsulClientFactory.cs | 2 +- .../Consul/IConsulClientFactory.cs | 2 +- .../ConsulServiceDiscoveryProvider.cs | 2 +- test/Ocelot.AcceptanceTests/Steps.cs | 4 +- .../AllTheThingsBenchmarks.cs | 157 ++++++++++++++++++ .../ExceptionHandlerMiddlewareBenchmarks.cs | 69 ++++++++ test/Ocelot.Benchmarks/Program.cs | 4 +- ...lPathToUrlPathTemplateMatcherBenchmarks.cs | 26 ++- .../ConsulFileConfigurationRepositoryTests.cs | 137 +++++++++++++++ ...> DiskFileConfigurationRepositoryTests.cs} | 79 +++++---- 14 files changed, 445 insertions(+), 53 deletions(-) create mode 100644 test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs create mode 100644 test/Ocelot.Benchmarks/ExceptionHandlerMiddlewareBenchmarks.cs create mode 100644 test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationRepositoryTests.cs rename test/Ocelot.UnitTests/Configuration/{FileConfigurationRepositoryTests.cs => DiskFileConfigurationRepositoryTests.cs} (73%) diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 343d7cbe..4e11e7ed 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -163,7 +163,7 @@ requests. This would also mean that subsequent requests dont use the cookies fro UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request! SSL Errors ----------- +^^^^^^^^^^ Id you want to ignore SSL warnings / errors set the following in your ReRoute config. diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index 88b13057..6fe25c12 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -69,18 +69,20 @@ In order to get this working add the following to ocelot.json.. "Type": "Eureka" } -And following the guide `Here `_ you may also need to add some stuff to appsettings.json. For example the json below -tells the steeltoe / pivotal services where to look for the service discovery server and if the service should register with it. +And following the guide `Here `_ you may also need to add some stuff to appsettings.json. For example the json below tells the steeltoe / pivotal services where to look for the service discovery server and if the service should register with it. .. code-block:: json "eureka": { "client": { "serviceUrl": "http://localhost:8761/eureka/", - "shouldRegisterWithEureka": true + "shouldRegisterWithEureka": false, + "shouldFetchRegistry": true } } +I am told that if shouldRegisterWithEureka is false then shouldFetchRegistry will defaut to true so you don't need it explicitly but left it in there. + Ocelot will now register all the necessary services when it starts up and if you have the json above will register itself with Eureka. One of the services polls Eureka every 30 seconds (default) and gets the latest service state and persists this in memory. When Ocelot asks for a given service it is retrieved from memory so performance is not a big problem. Please note that this code diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs index 21216168..165e035b 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs @@ -13,7 +13,7 @@ namespace Ocelot.Configuration.Repository public class ConsulFileConfigurationRepository : IFileConfigurationRepository { - private readonly ConsulClient _consul; + private readonly IConsulClient _consul; private const string OcelotConfiguration = "InternalConfiguration"; private readonly Cache.IOcelotCache _cache; private readonly IOcelotLogger _logger; @@ -72,7 +72,7 @@ namespace Ocelot.Configuration.Repository public async Task Set(FileConfiguration ocelotConfiguration) { - var json = JsonConvert.SerializeObject(ocelotConfiguration); + var json = JsonConvert.SerializeObject(ocelotConfiguration, Formatting.Indented); var bytes = Encoding.UTF8.GetBytes(json); diff --git a/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs index 5fb871f4..a870419c 100644 --- a/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs @@ -36,7 +36,7 @@ namespace Ocelot.Configuration.Repository public Task Set(FileConfiguration fileConfiguration) { - string jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + string jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); lock(_lock) { diff --git a/src/Ocelot/Infrastructure/Consul/ConsulClientFactory.cs b/src/Ocelot/Infrastructure/Consul/ConsulClientFactory.cs index 0e7f2782..26799934 100644 --- a/src/Ocelot/Infrastructure/Consul/ConsulClientFactory.cs +++ b/src/Ocelot/Infrastructure/Consul/ConsulClientFactory.cs @@ -6,7 +6,7 @@ namespace Ocelot.Infrastructure.Consul { public class ConsulClientFactory : IConsulClientFactory { - public ConsulClient Get(ConsulRegistryConfiguration config) + public IConsulClient Get(ConsulRegistryConfiguration config) { return new ConsulClient(c => { diff --git a/src/Ocelot/Infrastructure/Consul/IConsulClientFactory.cs b/src/Ocelot/Infrastructure/Consul/IConsulClientFactory.cs index 27e413fc..43428686 100644 --- a/src/Ocelot/Infrastructure/Consul/IConsulClientFactory.cs +++ b/src/Ocelot/Infrastructure/Consul/IConsulClientFactory.cs @@ -5,6 +5,6 @@ namespace Ocelot.Infrastructure.Consul { public interface IConsulClientFactory { - ConsulClient Get(ConsulRegistryConfiguration config); + IConsulClient Get(ConsulRegistryConfiguration config); } } diff --git a/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs index 12c34cb2..4c1afcc7 100644 --- a/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs +++ b/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs @@ -15,7 +15,7 @@ namespace Ocelot.ServiceDiscovery.Providers { private readonly ConsulRegistryConfiguration _config; private readonly IOcelotLogger _logger; - private readonly ConsulClient _consul; + private readonly IConsulClient _consul; private const string VersionPrefix = "version-"; public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration config, IOcelotLoggerFactory factory, IConsulClientFactory clientFactory) diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index f5c186ca..aac5be0d 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -88,7 +88,7 @@ namespace Ocelot.AcceptanceTests { var configurationPath = TestConfiguration.ConfigurationPath; - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); if (File.Exists(configurationPath)) { @@ -100,7 +100,7 @@ namespace Ocelot.AcceptanceTests public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration, string configurationPath) { - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); if (File.Exists(configurationPath)) { diff --git a/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs b/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs new file mode 100644 index 00000000..3e9e1a4a --- /dev/null +++ b/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.IO; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Middleware; +using Ocelot.DependencyInjection; +using System.Net.Http; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes.Jobs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Validators; + +namespace Ocelot.Benchmarks +{ + [Config(typeof(AllTheThingsBenchmarks))] + public class AllTheThingsBenchmarks : ManualConfig + { + private IWebHost _service; + private IWebHost _ocelot; + private HttpClient _httpClient; + + public AllTheThingsBenchmarks() + { + Add(StatisticColumn.AllStatistics); + Add(MemoryDiagnoser.Default); + Add(BaselineValidator.FailOnError); + } + + [GlobalSetup] + public void SetUp() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 201, string.Empty); + GivenThereIsAConfiguration(configuration); + GivenOcelotIsRunning("http://localhost:5000"); + + _httpClient = new HttpClient(); + } + + [Benchmark(Baseline = true)] + public async Task Baseline() + { + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:5000/"); + var response = await _httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); + } + + // * Summary * + // BenchmarkDotNet=v0.10.13, OS=macOS 10.12.6 (16G1212) [Darwin 16.7.0] + // Intel Core i5-4278U CPU 2.60GHz (Haswell), 1 CPU, 4 logical cores and 2 physical cores + // .NET Core SDK=2.1.4 + // [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT + // DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT + + // Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | Scaled | Gen 0 | Gen 1 | Allocated | + // --------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|------:|-------:|--------:|-------:|----------:| + // Baseline | 2.102 ms | 0.0292 ms | 0.0273 ms | 0.0070 ms | 2.063 ms | 2.080 ms | 2.093 ms | 2.122 ms | 2.152 ms | 475.8 | 1.00 | 31.2500 | 3.9063 | 1.63 KB | + + private void GivenOcelotIsRunning(string url) + { + _ocelot = new WebHostBuilder() + .UseKestrel() + .UseUrls(url) + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddJsonFile("ocelot.json") + .AddEnvironmentVariables(); + }) + .ConfigureServices(s => { + s.AddOcelot(); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + }) + .UseIISIntegration() + .Configure(app => + { + app.UseOcelot().Wait(); + }) + .Build(); + + _ocelot.Start(); + } + + public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = Path.Combine(AppContext.BaseDirectory, "ocelot.json");; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) + { + _service = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _service.Start(); + } + } +} diff --git a/test/Ocelot.Benchmarks/ExceptionHandlerMiddlewareBenchmarks.cs b/test/Ocelot.Benchmarks/ExceptionHandlerMiddlewareBenchmarks.cs new file mode 100644 index 00000000..9b1f3312 --- /dev/null +++ b/test/Ocelot.Benchmarks/ExceptionHandlerMiddlewareBenchmarks.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.IO; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Middleware; +using Ocelot.DependencyInjection; +using System.Net.Http; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes.Jobs; +using Ocelot.Configuration.Repository; +using Ocelot.Infrastructure.RequestData; +using Ocelot.Logging; +using Ocelot.Errors.Middleware; +using Microsoft.Extensions.DependencyInjection; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Validators; + +namespace Ocelot.Benchmarks +{ + [SimpleJob(launchCount: 1, warmupCount: 2, targetCount: 5)] + [Config(typeof(ExceptionHandlerMiddlewareBenchmarks))] + public class ExceptionHandlerMiddlewareBenchmarks : ManualConfig + { + private ExceptionHandlerMiddleware _middleware; + private DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; + + public ExceptionHandlerMiddlewareBenchmarks() + { + Add(StatisticColumn.AllStatistics); + Add(MemoryDiagnoser.Default); + Add(BaselineValidator.FailOnError); + } + + [GlobalSetup] + public void SetUp() + { + var serviceCollection = new ServiceCollection(); + var config = new ConfigurationRoot(new List()); + var builder = new OcelotBuilder(serviceCollection, config); + var services = serviceCollection.BuildServiceProvider(); + var loggerFactory = services.GetService(); + var configRepo = services.GetService(); + var repo = services.GetService(); + _next = async context => { + await Task.CompletedTask; + throw new Exception("BOOM"); + }; + _middleware = new ExceptionHandlerMiddleware(_next, loggerFactory, configRepo, repo); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + } + + [Benchmark(Baseline = true)] + public async Task Baseline() + { + await _middleware.Invoke(_downstreamContext); + } + } +} diff --git a/test/Ocelot.Benchmarks/Program.cs b/test/Ocelot.Benchmarks/Program.cs index 56b87404..8601eeaa 100644 --- a/test/Ocelot.Benchmarks/Program.cs +++ b/test/Ocelot.Benchmarks/Program.cs @@ -7,7 +7,9 @@ namespace Ocelot.Benchmarks public static void Main(string[] args) { var switcher = new BenchmarkSwitcher(new[] { - typeof(UrlPathToUrlPathTemplateMatcherBenchmarks), + typeof(UrlPathToUrlPathTemplateMatcherBenchmarks), + typeof(AllTheThingsBenchmarks), + typeof(ExceptionHandlerMiddlewareBenchmarks) }); switcher.Run(args); diff --git a/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs b/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs index d7f4434b..3f02f48b 100644 --- a/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs +++ b/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs @@ -1,6 +1,9 @@ +using System; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Columns; using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Validators; using Ocelot.DownstreamRouteFinder.UrlMatcher; namespace Ocelot.Benchmarks @@ -15,6 +18,8 @@ namespace Ocelot.Benchmarks public UrlPathToUrlPathTemplateMatcherBenchmarks() { Add(StatisticColumn.AllStatistics); + Add(MemoryDiagnoser.Default); + Add(BaselineValidator.FailOnError); } [GlobalSetup] @@ -25,16 +30,23 @@ namespace Ocelot.Benchmarks _downstreamUrlPathTemplate = "api/product/products/{productId}/variants/"; } - [Benchmark] - public void Benchmark1() + [Benchmark(Baseline = true)] + public void Baseline() { _urlPathMatcher.Match(_downstreamUrlPath, _downstreamUrlPathTemplate); } - [Benchmark] - public void Benchmark2() - { - _urlPathMatcher.Match(_downstreamUrlPath, _downstreamUrlPathTemplate); - } + // * Summary * + + // BenchmarkDotNet=v0.10.13, OS=macOS 10.12.6 (16G1212) [Darwin 16.7.0] + // Intel Core i5-4278U CPU 2.60GHz (Haswell), 1 CPU, 4 logical cores and 2 physical cores + // .NET Core SDK=2.1.4 + // [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT + // DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT + + + // Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | + // ----------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|----------:| + // Benchmark1 | 3.133 us | 0.0492 us | 0.0460 us | 0.0119 us | 3.082 us | 3.100 us | 3.122 us | 3.168 us | 3.233 us | 319,161.9 | } } diff --git a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationRepositoryTests.cs new file mode 100644 index 00000000..b6b1cc6d --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationRepositoryTests.cs @@ -0,0 +1,137 @@ +namespace Ocelot.UnitTests.Configuration +{ + using Xunit; + using TestStack.BDDfy; + using Shouldly; + using Ocelot.Configuration.Repository; + using Moq; + using Ocelot.Infrastructure.Consul; + using Ocelot.Logging; + using Ocelot.Configuration.File; + using Ocelot.Cache; + using System; + using System.Collections.Generic; + using Ocelot.Responses; + using System.Threading.Tasks; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.ServiceDiscovery.Configuration; + using Consul; + using Newtonsoft.Json; + using System.Text; + using System.Threading; + using System.Linq; + + public class ConsulFileConfigurationRepositoryTests + { + private ConsulFileConfigurationRepository _repo; + private Mock> _cache; + private Mock _internalRepo; + private Mock _factory; + private Mock _loggerFactory; + private Mock _client; + private Mock _kvEndpoint; + private FileConfiguration _fileConfiguration; + private Response _result; + + public ConsulFileConfigurationRepositoryTests() + { + _cache = new Mock>(); + _internalRepo = new Mock(); + _loggerFactory = new Mock(); + + _factory = new Mock(); + _client = new Mock(); + _kvEndpoint = new Mock(); + + _client + .Setup(x => x.KV) + .Returns(_kvEndpoint.Object); + _factory + .Setup(x => x.Get(It.IsAny())) + .Returns(_client.Object); + + _internalRepo + .Setup(x => x.Get()) + .Returns(new OkResponse(new InternalConfiguration(new List(), "", new ServiceProviderConfigurationBuilder().Build(), ""))); + + _repo = new ConsulFileConfigurationRepository(_cache.Object, _internalRepo.Object, _factory.Object, _loggerFactory.Object); + } + + [Fact] + public void should_set_config() + { + var config = FakeFileConfiguration(); + + this.Given(_ => GivenIHaveAConfiguration(config)) + .And(_ => GivenWritingToConsulSucceeds()) + .When(_ => WhenISetTheConfiguration()) + .Then(_ => ThenTheConfigurationIsStoredAs(config)) + .BDDfy(); + } + + private void GivenWritingToConsulSucceeds() + { + var response = new WriteResult(); + response.Response = true; + + _kvEndpoint + .Setup(x => x.Put(It.IsAny(), It.IsAny())).ReturnsAsync(response); + } + + private void ThenTheConfigurationIsStoredAs(FileConfiguration config) + { + var json = JsonConvert.SerializeObject(config, Formatting.Indented); + + var bytes = Encoding.UTF8.GetBytes(json); + + _kvEndpoint + .Verify(x => x.Put(It.Is(k => k.Value.SequenceEqual(bytes)), It.IsAny()), Times.Once); + } + + private async Task WhenISetTheConfiguration() + { + _result = await _repo.Set(_fileConfiguration); + } + + private void GivenIHaveAConfiguration(FileConfiguration config) + { + _fileConfiguration = config; + } + + private FileConfiguration FakeFileConfiguration() + { + var reRoutes = new List + { + new FileReRoute + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "123.12.12.12", + Port = 80, + } + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/asdfs/test/{test}" + } + }; + + var globalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Port = 198, + Host = "blah" + } + }; + + return new FileConfiguration + { + GlobalConfiguration = globalConfiguration, + ReRoutes = reRoutes + }; + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs similarity index 73% rename from test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs rename to test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs index 5b0e93db..c2e77b38 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs @@ -1,21 +1,22 @@ -using System; -using System.Collections.Generic; -using Moq; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using Newtonsoft.Json; -using System.IO; -using Microsoft.AspNetCore.Hosting; -using Ocelot.Configuration.Repository; - namespace Ocelot.UnitTests.Configuration { - public class FileConfigurationRepositoryTests + using System; + using System.Collections.Generic; + using Moq; + using Ocelot.Configuration.File; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + using Newtonsoft.Json; + using System.IO; + using Microsoft.AspNetCore.Hosting; + using Ocelot.Configuration.Repository; + + public class DiskFileConfigurationRepositoryTests { private readonly Mock _hostingEnvironment = new Mock(); private IFileConfigurationRepository _repo; + private string _configurationPath; private FileConfiguration _result; private FileConfiguration _fileConfiguration; @@ -24,7 +25,7 @@ namespace Ocelot.UnitTests.Configuration // tests but whatever... private string _environmentName = "DEV.DEV"; - public FileConfigurationRepositoryTests() + public DiskFileConfigurationRepositoryTests() { _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); _repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); @@ -35,9 +36,9 @@ namespace Ocelot.UnitTests.Configuration { var config = FakeFileConfigurationForGet(); - this.Given(x => x.GivenTheConfigurationIs(config)) - .When(x => x.WhenIGetTheReRoutes()) - .Then(x => x.ThenTheFollowingIsReturned(config)) + this.Given(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenIGetTheReRoutes()) + .Then(_ => ThenTheFollowingIsReturned(config)) .BDDfy(); } @@ -46,10 +47,10 @@ namespace Ocelot.UnitTests.Configuration { var config = FakeFileConfigurationForGet(); - this.Given(x => x.GivenTheEnvironmentNameIsUnavailable()) - .And(x => x.GivenTheConfigurationIs(config)) - .When(x => x.WhenIGetTheReRoutes()) - .Then(x => x.ThenTheFollowingIsReturned(config)) + this.Given(_ => GivenTheEnvironmentNameIsUnavailable()) + .And(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenIGetTheReRoutes()) + .Then(_ => ThenTheFollowingIsReturned(config)) .BDDfy(); } @@ -58,9 +59,10 @@ namespace Ocelot.UnitTests.Configuration { var config = FakeFileConfigurationForSet(); - this.Given(x => GivenIHaveAConfiguration(config)) - .When(x => WhenISetTheConfiguration()) - .Then(x => ThenTheConfigurationIsStoredAs(config)) + this.Given(_ => GivenIHaveAConfiguration(config)) + .When(_ => WhenISetTheConfiguration()) + .Then(_ => ThenTheConfigurationIsStoredAs(config)) + .And(_ => ThenTheConfigurationJsonIsIndented(config)) .BDDfy(); } @@ -68,10 +70,12 @@ namespace Ocelot.UnitTests.Configuration public void should_set_file_configuration_if_environment_name_is_unavailable() { var config = FakeFileConfigurationForSet(); - this.Given(x => GivenIHaveAConfiguration(config)) - .And(x => GivenTheEnvironmentNameIsUnavailable()) - .When(x => WhenISetTheConfiguration()) - .Then(x => ThenTheConfigurationIsStoredAs(config)) + + this.Given(_ => GivenIHaveAConfiguration(config)) + .And(_ => GivenTheEnvironmentNameIsUnavailable()) + .When(_ => WhenISetTheConfiguration()) + .Then(_ => ThenTheConfigurationIsStoredAs(config)) + .And(_ => ThenTheConfigurationJsonIsIndented(config)) .BDDfy(); } @@ -117,16 +121,25 @@ namespace Ocelot.UnitTests.Configuration private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) { - var configurationPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; + _configurationPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); - if (File.Exists(configurationPath)) + if (File.Exists(_configurationPath)) { - File.Delete(configurationPath); + File.Delete(_configurationPath); } - File.WriteAllText(configurationPath, jsonConfiguration); + File.WriteAllText(_configurationPath, jsonConfiguration); + } + + private void ThenTheConfigurationJsonIsIndented(FileConfiguration expecteds) + { + var path = !string.IsNullOrEmpty(_configurationPath) ? _configurationPath : _configurationPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; + + var resultText = File.ReadAllText(_configurationPath); + var expectedText = JsonConvert.SerializeObject(expecteds, Formatting.Indented); + resultText.ShouldBe(expectedText); } private void WhenIGetTheReRoutes() From fad190fcae129e8db1a9611a81c21d2e9094b426 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Tue, 24 Apr 2018 08:30:45 +0100 Subject: [PATCH 50/50] Feature/extra request id test (#329) * messing around with benchmark.net.seems Ocelot adds about 2ms to a request..lets make this less? :) * #326 store json indented so it looks nice :P * #327 show another test how request id works --- test/Ocelot.AcceptanceTests/RequestIdTests.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/test/Ocelot.AcceptanceTests/RequestIdTests.cs b/test/Ocelot.AcceptanceTests/RequestIdTests.cs index 6ac271c9..7989dc90 100644 --- a/test/Ocelot.AcceptanceTests/RequestIdTests.cs +++ b/test/Ocelot.AcceptanceTests/RequestIdTests.cs @@ -132,6 +132,43 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_use_global_request_id_create_and_forward() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51873, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + }, + GlobalConfiguration = new FileGlobalConfiguration + { + RequestIdKey = _steps.RequestIdKey + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51873")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheRequestIdIsReturned()) + .BDDfy(); + } + private void GivenThereIsAServiceRunningOn(string url) { _builder = new WebHostBuilder()