From faaabbe7a75f1006ea055243c4fd63c868aae137 Mon Sep 17 00:00:00 2001 From: Abolfazl Date: Mon, 4 Feb 2019 15:51:50 +0330 Subject: [PATCH 1/8] fix issue #661 for Advanced aggregations (#704) * Add Advanced Aggregation Feature * fix overwrite error * distinct data for better performance * remove constructor parameter * fix tests issue * fix tests * fix tests issue * Add UnitTest and AcceptanceTest * fix responseKeys typo * Update SimpleJsonResponseAggregator.cs * change port --- .../Configuration/Builder/ReRouteBuilder.cs | 14 ++- .../Creator/AggregatesCreator.cs | 17 ++- .../File/AggregateReRouteConfig.cs | 13 ++ .../File/FileAggregateReRoute.cs | 3 +- src/Ocelot/Configuration/ReRoute.cs | 8 +- .../InMemoryResponseAggregatorFactory.cs | 6 +- .../Middleware/Multiplexer/Multiplexer.cs | 114 ++++++++++++++--- .../SimpleJsonResponseAggregator.cs | 62 ++++++++-- test/Ocelot.AcceptanceTests/AggregateTests.cs | 115 ++++++++++++++++-- .../ResponseAggregatorFactoryTests.cs | 3 +- .../SimpleJsonResponseAggregatorTests.cs | 56 ++++++++- 11 files changed, 352 insertions(+), 59 deletions(-) create mode 100644 src/Ocelot/Configuration/File/AggregateReRouteConfig.cs diff --git a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs index 824fe09d..dae8c11e 100644 --- a/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/ReRouteBuilder.cs @@ -4,18 +4,21 @@ using System.Net.Http; using Ocelot.Values; using System.Linq; - + using Ocelot.Configuration.File; + public class ReRouteBuilder { private UpstreamPathTemplate _upstreamTemplatePattern; private List _upstreamHttpMethod; private string _upstreamHost; private List _downstreamReRoutes; + private List _downstreamReRoutesConfig; private string _aggregator; public ReRouteBuilder() { _downstreamReRoutes = new List(); + _downstreamReRoutesConfig = new List(); } public ReRouteBuilder WithDownstreamReRoute(DownstreamReRoute value) @@ -48,6 +51,12 @@ return this; } + public ReRouteBuilder WithAggregateReRouteConfig(List aggregateReRouteConfigs) + { + _downstreamReRoutesConfig = aggregateReRouteConfigs; + return this; + } + public ReRouteBuilder WithAggregator(string aggregator) { _aggregator = aggregator; @@ -57,7 +66,8 @@ public ReRoute Build() { return new ReRoute( - _downstreamReRoutes, + _downstreamReRoutes, + _downstreamReRoutesConfig, _upstreamHttpMethod, _upstreamTemplatePattern, _upstreamHost, diff --git a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs index ed43f598..9acef561 100644 --- a/src/Ocelot/Configuration/Creator/AggregatesCreator.cs +++ b/src/Ocelot/Configuration/Creator/AggregatesCreator.cs @@ -24,14 +24,18 @@ namespace Ocelot.Configuration.Creator private ReRoute SetUpAggregateReRoute(IEnumerable reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) { - var applicableReRoutes = reRoutes - .SelectMany(x => x.DownstreamReRoute) - .Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key)) - .ToList(); + var applicableReRoutes = new List(); + var allReRoutes = reRoutes.SelectMany(x => x.DownstreamReRoute); - if (applicableReRoutes.Count != aggregateReRoute.ReRouteKeys.Count) + foreach (var reRouteKey in aggregateReRoute.ReRouteKeys) { - return null; + var selec = allReRoutes.FirstOrDefault(q => q.Key == reRouteKey); + if (selec == null) + { + return null; + } + + applicableReRoutes.Add(selec); } var upstreamTemplatePattern = _creator.Create(aggregateReRoute); @@ -40,6 +44,7 @@ namespace Ocelot.Configuration.Creator .WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod) .WithUpstreamPathTemplate(upstreamTemplatePattern) .WithDownstreamReRoutes(applicableReRoutes) + .WithAggregateReRouteConfig(aggregateReRoute.ReRouteKeysConfig) .WithUpstreamHost(aggregateReRoute.UpstreamHost) .WithAggregator(aggregateReRoute.Aggregator) .Build(); diff --git a/src/Ocelot/Configuration/File/AggregateReRouteConfig.cs b/src/Ocelot/Configuration/File/AggregateReRouteConfig.cs new file mode 100644 index 00000000..8a155c1b --- /dev/null +++ b/src/Ocelot/Configuration/File/AggregateReRouteConfig.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ocelot.Configuration.File +{ + public class AggregateReRouteConfig + { + public string ReRouteKey { get; set; } + public string Parameter { get; set; } + public string JsonPath { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileAggregateReRoute.cs b/src/Ocelot/Configuration/File/FileAggregateReRoute.cs index c862094a..5c3b551c 100644 --- a/src/Ocelot/Configuration/File/FileAggregateReRoute.cs +++ b/src/Ocelot/Configuration/File/FileAggregateReRoute.cs @@ -5,6 +5,7 @@ namespace Ocelot.Configuration.File public class FileAggregateReRoute : IReRoute { public List ReRouteKeys { get;set; } + public List ReRouteKeysConfig { get;set; } public string UpstreamPathTemplate { get;set; } public string UpstreamHost { get; set; } public bool ReRouteIsCaseSensitive { get; set; } @@ -17,5 +18,5 @@ namespace Ocelot.Configuration.File } public int Priority {get;set;} = 1; - } + } } diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index f1faf975..31392770 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -2,11 +2,13 @@ { using System.Collections.Generic; using System.Net.Http; + using Ocelot.Configuration.File; using Ocelot.Values; - + public class ReRoute { - public ReRoute(List downstreamReRoute, + public ReRoute(List downstreamReRoute, + List downstreamReRouteConfig, List upstreamHttpMethod, UpstreamPathTemplate upstreamTemplatePattern, string upstreamHost, @@ -14,6 +16,7 @@ { UpstreamHost = upstreamHost; DownstreamReRoute = downstreamReRoute; + DownstreamReRouteConfig = downstreamReRouteConfig; UpstreamHttpMethod = upstreamHttpMethod; UpstreamTemplatePattern = upstreamTemplatePattern; Aggregator = aggregator; @@ -23,6 +26,7 @@ public List UpstreamHttpMethod { get; private set; } public string UpstreamHost { get; private set; } public List DownstreamReRoute { get; private set; } + public List DownstreamReRouteConfig { get; private set; } public string Aggregator {get; private set;} } } diff --git a/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs b/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs index a74386db..b9721e1b 100644 --- a/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs +++ b/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs @@ -5,12 +5,12 @@ namespace Ocelot.Middleware.Multiplexer public class InMemoryResponseAggregatorFactory : IResponseAggregatorFactory { private readonly UserDefinedResponseAggregator _userDefined; - private readonly SimpleJsonResponseAggregator _simple; + private readonly IResponseAggregator _simple; - public InMemoryResponseAggregatorFactory(IDefinedAggregatorProvider provider) + public InMemoryResponseAggregatorFactory(IDefinedAggregatorProvider provider, IResponseAggregator responseAggregator) { _userDefined = new UserDefinedResponseAggregator(provider); - _simple = new SimpleJsonResponseAggregator(); + _simple = responseAggregator; } public IResponseAggregator Get(ReRoute reRoute) diff --git a/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs b/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs index 351f8471..d61678e3 100644 --- a/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs +++ b/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Newtonsoft.Json.Linq; using Ocelot.Configuration; +using Ocelot.DownstreamRouteFinder.UrlMatcher; namespace Ocelot.Middleware.Multiplexer { @@ -16,31 +18,105 @@ namespace Ocelot.Middleware.Multiplexer public async Task Multiplex(DownstreamContext context, ReRoute reRoute, OcelotRequestDelegate next) { - var tasks = new Task[reRoute.DownstreamReRoute.Count]; - - for (var i = 0; i < reRoute.DownstreamReRoute.Count; i++) + var reRouteKeysConfigs = reRoute.DownstreamReRouteConfig; + if (reRouteKeysConfigs == null || !reRouteKeysConfigs.Any()) { - var downstreamContext = new DownstreamContext(context.HttpContext) + var tasks = new Task[reRoute.DownstreamReRoute.Count]; + + for (var i = 0; i < reRoute.DownstreamReRoute.Count; i++) + { + var downstreamContext = new DownstreamContext(context.HttpContext) + { + TemplatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues, + Configuration = context.Configuration, + DownstreamReRoute = reRoute.DownstreamReRoute[i], + }; + + tasks[i] = Fire(downstreamContext, next); + } + + await Task.WhenAll(tasks); + + var contexts = new List(); + + foreach (var task in tasks) + { + var finished = await task; + contexts.Add(finished); + } + + await Map(reRoute, context, contexts); + } + else + { + var downstreamContextMain = new DownstreamContext(context.HttpContext) { TemplatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues, Configuration = context.Configuration, - DownstreamReRoute = reRoute.DownstreamReRoute[i], + DownstreamReRoute = reRoute.DownstreamReRoute[0], }; + var mainResponse = await Fire(downstreamContextMain, next); - tasks[i] = Fire(downstreamContext, next); + if (reRoute.DownstreamReRoute.Count == 1) + { + MapNotAggregate(context, new List() { mainResponse }); + return; + } + + var tasks = new List>(); + if (mainResponse.DownstreamResponse == null) + { + return; + } + + var content = await mainResponse.DownstreamResponse.Content.ReadAsStringAsync(); + var jObject = Newtonsoft.Json.Linq.JToken.Parse(content); + + for (var i = 1; i < reRoute.DownstreamReRoute.Count; i++) + { + var templatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues; + var downstreamReRoute = reRoute.DownstreamReRoute[i]; + var matchAdvancedAgg = reRouteKeysConfigs.FirstOrDefault(q => q.ReRouteKey == downstreamReRoute.Key); + if (matchAdvancedAgg != null) + { + var values = jObject.SelectTokens(matchAdvancedAgg.JsonPath).Select(s => s.ToString()).Distinct().ToList(); + + foreach (var value in values) + { + var downstreamContext = new DownstreamContext(context.HttpContext) + { + TemplatePlaceholderNameAndValues = new List(templatePlaceholderNameAndValues), + Configuration = context.Configuration, + DownstreamReRoute = downstreamReRoute, + }; + downstreamContext.TemplatePlaceholderNameAndValues.Add(new PlaceholderNameAndValue("{" + matchAdvancedAgg.Parameter + "}", value.ToString())); + tasks.Add(Fire(downstreamContext, next)); + } + } + else + { + var downstreamContext = new DownstreamContext(context.HttpContext) + { + TemplatePlaceholderNameAndValues = new List(templatePlaceholderNameAndValues), + Configuration = context.Configuration, + DownstreamReRoute = downstreamReRoute, + }; + tasks.Add(Fire(downstreamContext, next)); + } + } + + await Task.WhenAll(tasks); + + var contexts = new List() { mainResponse }; + + foreach (var task in tasks) + { + var finished = await task; + contexts.Add(finished); + } + + await Map(reRoute, context, contexts); } - - await Task.WhenAll(tasks); - - var contexts = new List(); - - foreach (var task in tasks) - { - var finished = await task; - contexts.Add(finished); - } - - await Map(reRoute, context, contexts); } private async Task Map(ReRoute reRoute, DownstreamContext context, List contexts) @@ -55,7 +131,7 @@ namespace Ocelot.Middleware.Multiplexer 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 diff --git a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs b/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs index ef2a00f5..d505e7b8 100644 --- a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs +++ b/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -21,19 +22,54 @@ namespace Ocelot.Middleware.Multiplexer contentBuilder.Append("{"); - for (var i = 0; i < downstreamContexts.Count; i++) + var responseKeys = downstreamContexts.Select(s => s.DownstreamReRoute.Key).Distinct().ToList(); + + for (var k = 0; k < responseKeys.Count; k++) { - if (downstreamContexts[i].IsError) + var contexts = downstreamContexts.Where(w => w.DownstreamReRoute.Key == responseKeys[k]).ToList(); + if (contexts.Count == 1) { - MapAggregateError(originalContext, downstreamContexts, i); - return; + if (contexts[0].IsError) + { + MapAggregateError(originalContext, contexts[0]); + return; + } + + var content = await contexts[0].DownstreamResponse.Content.ReadAsStringAsync(); + contentBuilder.Append($"\"{responseKeys[k]}\":{content}"); + + } + else + { + contentBuilder.Append($"\"{responseKeys[k]}\":"); + contentBuilder.Append("["); + + for (var i = 0; i < contexts.Count; i++) + { + if (contexts[i].IsError) + { + MapAggregateError(originalContext, contexts[i]); + return; + } + + var content = await contexts[i].DownstreamResponse.Content.ReadAsStringAsync(); + if (string.IsNullOrWhiteSpace(content)) + { + continue; + } + + contentBuilder.Append($"{content}"); + + if (i + 1 < contexts.Count) + { + contentBuilder.Append(","); + } + } + + contentBuilder.Append("]"); } - var content = await downstreamContexts[i].DownstreamResponse.Content.ReadAsStringAsync(); - - contentBuilder.Append($"\"{downstreamContexts[i].DownstreamReRoute.Key}\":{content}"); - - if (i + 1 < downstreamContexts.Count) + if (k + 1 < responseKeys.Count) { contentBuilder.Append(","); } @@ -43,16 +79,16 @@ namespace Ocelot.Middleware.Multiplexer var stringContent = 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>>(), "cannot return from aggregate..which reason phrase would you use?"); } - private static void MapAggregateError(DownstreamContext originalContext, List downstreamContexts, int i) + private static void MapAggregateError(DownstreamContext originalContext, DownstreamContext downstreamContext) { - originalContext.Errors.AddRange(downstreamContexts[i].Errors); - originalContext.DownstreamResponse = downstreamContexts[i].DownstreamResponse; + originalContext.Errors.AddRange(downstreamContext.Errors); + originalContext.DownstreamResponse = downstreamContext.DownstreamResponse; } } } diff --git a/test/Ocelot.AcceptanceTests/AggregateTests.cs b/test/Ocelot.AcceptanceTests/AggregateTests.cs index a98fd3ef..e3d99854 100644 --- a/test/Ocelot.AcceptanceTests/AggregateTests.cs +++ b/test/Ocelot.AcceptanceTests/AggregateTests.cs @@ -141,6 +141,100 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void should_return_response_200_with_advanced_aggregate_configs() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51889, + } + }, + UpstreamPathTemplate = "/Comments", + UpstreamHttpMethod = new List { "Get" }, + Key = "Comments" + }, + new FileReRoute + { + DownstreamPathTemplate = "/users/{userId}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51890, + } + }, + UpstreamPathTemplate = "/UserDetails", + UpstreamHttpMethod = new List { "Get" }, + Key = "UserDetails" + }, + new FileReRoute + { + DownstreamPathTemplate = "/posts/{postId}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51887, + } + }, + UpstreamPathTemplate = "/PostDetails", + UpstreamHttpMethod = new List { "Get" }, + Key = "PostDetails" + } + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Comments", + "UserDetails", + "PostDetails" + }, + ReRouteKeysConfig = new List() + { + new AggregateReRouteConfig(){ReRouteKey = "UserDetails",JsonPath = "$[*].writerId",Parameter = "userId"}, + new AggregateReRouteConfig(){ReRouteKey = "PostDetails",JsonPath = "$[*].postId",Parameter = "postId"} + }, + } + } + }; + + var userDetailsResponseContent = @"{""id"":1,""firstName"":""abolfazl"",""lastName"":""rajabpour""}"; + var postDetailsResponseContent = @"{""id"":1,""title"":""post1""}"; + var commentsResponseContent = @"[{""id"":1,""writerId"":1,""postId"":2,""text"":""text1""},{""id"":2,""writerId"":1,""postId"":2,""text"":""text2""}]"; + + var expected = "{\"Comments\":" + commentsResponseContent + ",\"UserDetails\":" + userDetailsResponseContent + ",\"PostDetails\":" + postDetailsResponseContent + "}"; + + this.Given(x => x.GivenServiceOneIsRunning("http://localhost:51889", "/", 200, commentsResponseContent)) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51890", "/users/1", 200, userDetailsResponseContent)) + .Given(x => x.GivenServiceTwoIsRunning("http://localhost:51887", "/posts/2", 200, postDetailsResponseContent)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .BDDfy(); + } + [Fact] public void should_return_response_200_with_simple_url_user_defined_aggregate() { @@ -189,8 +283,8 @@ namespace Ocelot.AcceptanceTests UpstreamHost = "localhost", ReRouteKeys = new List { - "Tom", - "Laura" + "Laura", + "Tom" }, Aggregator = "FakeDefinedAggregator" } @@ -258,8 +352,8 @@ namespace Ocelot.AcceptanceTests UpstreamHost = "localhost", ReRouteKeys = new List { - "Tom", - "Laura" + "Laura", + "Tom" } } } @@ -326,8 +420,9 @@ namespace Ocelot.AcceptanceTests UpstreamHost = "localhost", ReRouteKeys = new List { - "Tom", - "Laura" + "Laura", + "Tom" + } } } @@ -394,8 +489,8 @@ namespace Ocelot.AcceptanceTests UpstreamHost = "localhost", ReRouteKeys = new List { - "Tom", - "Laura" + "Laura", + "Tom" } } } @@ -462,8 +557,8 @@ namespace Ocelot.AcceptanceTests UpstreamHost = "localhost", ReRouteKeys = new List { - "Tom", - "Laura" + "Laura", + "Tom" } } } diff --git a/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs b/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs index b4413b45..16a682ce 100644 --- a/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs +++ b/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs @@ -18,7 +18,8 @@ namespace Ocelot.UnitTests.Middleware public ResponseAggregatorFactoryTests() { _provider = new Mock(); - _factory = new InMemoryResponseAggregatorFactory(_provider.Object); + _aggregator = new SimpleJsonResponseAggregator(); + _factory = new InMemoryResponseAggregatorFactory(_provider.Object, _aggregator); } [Fact] diff --git a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs b/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs index 7d2e5edd..a391ce59 100644 --- a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs +++ b/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs @@ -7,11 +7,11 @@ using Castle.Components.DictionaryAdapter; using Microsoft.AspNetCore.Http; using Ocelot.Configuration; using Ocelot.Configuration.Builder; -using Ocelot.Errors; +using Ocelot.Configuration.File; using Ocelot.Middleware; using Ocelot.Middleware.Multiplexer; -using Ocelot.Request.Middleware; using Ocelot.UnitTests.Responder; +using Ocelot.Values; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -30,6 +30,58 @@ namespace Ocelot.UnitTests.Middleware _aggregator = new SimpleJsonResponseAggregator(); } + [Fact] + public void should_aggregate_n_responses_and_set_response_content_on_upstream_context_withConfig() + { + var commentsDownstreamReRoute = new DownstreamReRouteBuilder().WithKey("Comments").Build(); + + var userDetailsDownstreamReRoute = new DownstreamReRouteBuilder().WithKey("UserDetails") + .WithUpstreamPathTemplate(new UpstreamPathTemplate("", 0, false, "/v1/users/{userId}")) + .Build(); + + var downstreamReRoutes = new List + { + commentsDownstreamReRoute, + userDetailsDownstreamReRoute + }; + + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoutes(downstreamReRoutes) + .WithAggregateReRouteConfig(new List() + { + new AggregateReRouteConfig(){ReRouteKey = "UserDetails",JsonPath = "$[*].writerId",Parameter = "userId"} + }) + .Build(); + + var commentsResponseContent = @"[{""id"":1,""writerId"":1,""postId"":1,""text"":""text1""},{""id"":2,""writerId"":2,""postId"":2,""text"":""text2""},{""id"":3,""writerId"":2,""postId"":1,""text"":""text21""}]"; + var commentsDownstreamContext = new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = new DownstreamResponse(new StringContent(commentsResponseContent, Encoding.UTF8, "application/json"), HttpStatusCode.OK, new EditableList>>(), "some reason"), + DownstreamReRoute = commentsDownstreamReRoute + }; + + var userDetailsResponseContent = @"[{""id"":1,""firstName"":""abolfazl"",""lastName"":""rajabpour""},{""id"":2,""firstName"":""reza"",""lastName"":""rezaei""}]"; + var userDetailsDownstreamContext = new DownstreamContext(new DefaultHttpContext()) + { + DownstreamResponse = new DownstreamResponse(new StringContent(userDetailsResponseContent, Encoding.UTF8, "application/json"), HttpStatusCode.OK, new List>>(), "some reason"), + DownstreamReRoute = userDetailsDownstreamReRoute + }; + + var downstreamContexts = new List { commentsDownstreamContext, userDetailsDownstreamContext }; + + var expected = "{\"Comments\":" + commentsResponseContent + ",\"UserDetails\":" + userDetailsResponseContent + "}"; + + this.Given(x => GivenTheUpstreamContext(new DownstreamContext(new DefaultHttpContext()))) + .And(x => GivenTheReRoute(reRoute)) + .And(x => GivenTheDownstreamContext(downstreamContexts)) + .When(x => WhenIAggregate()) + .Then(x => ThenTheContentIs(expected)) + .And(x => ThenTheContentTypeIs("application/json")) + .And(x => ThenTheReasonPhraseIs("cannot return from aggregate..which reason phrase would you use?")) + .BDDfy(); + } + + [Fact] public void should_aggregate_n_responses_and_set_response_content_on_upstream_context() { From 292c30fd8f4ca48345e943873e5d7d0c0c9074d3 Mon Sep 17 00:00:00 2001 From: Marcell Toth Date: Wed, 6 Feb 2019 20:59:38 +0100 Subject: [PATCH 2/8] Fix code example for SSL Errors (#780) DangerousAcceptAnyServerCertificateValidator has to be set to "true" to disable certification validation, not "false". --- 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 89caf08b..408eb450 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -219,6 +219,6 @@ If you want to ignore SSL warnings / errors set the following in your ReRoute co .. code-block:: json - "DangerousAcceptAnyServerCertificateValidator": false + "DangerousAcceptAnyServerCertificateValidator": true I don't recommend doing this, I suggest creating your own certificate and then getting it trusted by your local / remote machine if you can. From a9cfc04aa9f9d940f68e29a330be9eb8589f92f6 Mon Sep 17 00:00:00 2001 From: Jonathan Evason Date: Wed, 6 Feb 2019 20:00:23 +0000 Subject: [PATCH 3/8] Changed wording for ease of reading (#776) Just some wording changes for clarification. --- 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 bb997673..6e60400d 100644 --- a/docs/introduction/gettingstarted.rst +++ b/docs/introduction/gettingstarted.rst @@ -32,8 +32,8 @@ The following is a very basic ocelot.json. It won't do anything but should get O The most important thing to note here is BaseUrl. Ocelot needs to know the URL it is running under in order to do Header find & replace and for certain administration configurations. When setting this URL it should be the external URL that clients will see Ocelot running on e.g. If you are running containers Ocelot might run on the url http://123.12.1.1:6543 but has something like nginx in front of it responding on https://api.mybusiness.com. In this case the Ocelot base url should be https://api.mybusiness.com. -If for some reason you are using containers and do want Ocelot to respond to client on http://123.12.1.1:6543 -then you can do this but if you are deploying multiple Ocelot's you will probably want to pass this on the command line in some kind of script. Hopefully whatever scheduler you are using can pass the IP. +If you are using containers and require Ocelot to respond to clients on http://123.12.1.1:6543 +then you can do this, however if you are deploying multiple Ocelot's you will probably want to pass this on the command line in some kind of script. Hopefully whatever scheduler you are using can pass the IP. **Program** From 26ae9948d5248436b8906258b628392518e58b3b Mon Sep 17 00:00:00 2001 From: Chris Swinchatt Date: Wed, 20 Feb 2019 14:16:55 +0000 Subject: [PATCH 4/8] Ignore response content if null (fix #785) (#786) --- src/Ocelot/Responder/HttpContextResponder.cs | 13 +++++++++---- .../Responder/HttpContextResponderTests.cs | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index 0e964c7a..ebb09ab2 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -32,6 +32,15 @@ namespace Ocelot.Responder AddHeaderIfDoesntExist(context, httpResponseHeader); } + SetStatusCode(context, (int)response.StatusCode); + + context.Response.HttpContext.Features.Get().ReasonPhrase = response.ReasonPhrase; + + if (response.Content is null) + { + return; + } + foreach (var httpResponseHeader in response.Content.Headers) { AddHeaderIfDoesntExist(context, new Header(httpResponseHeader.Key, httpResponseHeader.Value)); @@ -44,10 +53,6 @@ namespace Ocelot.Responder AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ response.Content.Headers.ContentLength.ToString() }) ); } - SetStatusCode(context, (int)response.StatusCode); - - context.Response.HttpContext.Features.Get().ReasonPhrase = response.ReasonPhrase; - using(content) { if (response.StatusCode != HttpStatusCode.NotModified && context.Response.ContentLength != 0) diff --git a/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs b/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs index a5716908..89e8a045 100644 --- a/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs +++ b/test/Ocelot.UnitTests/Responder/HttpContextResponderTests.cs @@ -39,6 +39,23 @@ namespace Ocelot.UnitTests.Responder header.ShouldBeEmpty(); } + [Fact] + public void should_ignore_content_if_null() + { + var httpContext = new DefaultHttpContext(); + var response = new DownstreamResponse(null, HttpStatusCode.OK, + new List>>(), "some reason"); + + Should.NotThrow(() => + { + _responder + .SetResponseOnHttpContext(httpContext, response) + .GetAwaiter() + .GetResult() + ; + }); + } + [Fact] public void should_have_content_length() { From 8d99449b08f8717354fc432ed575847fe64261d0 Mon Sep 17 00:00:00 2001 From: Duke Date: Mon, 25 Feb 2019 23:36:25 +0800 Subject: [PATCH 5/8] fix bug #791 (#795) --- src/Ocelot/Configuration/LoadBalancerOptions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ocelot/Configuration/LoadBalancerOptions.cs b/src/Ocelot/Configuration/LoadBalancerOptions.cs index 08dc3b87..cbf9c59e 100644 --- a/src/Ocelot/Configuration/LoadBalancerOptions.cs +++ b/src/Ocelot/Configuration/LoadBalancerOptions.cs @@ -6,7 +6,7 @@ namespace Ocelot.Configuration { public LoadBalancerOptions(string type, string key, int expiryInMs) { - Type = type ?? nameof(NoLoadBalancer); + Type = string.IsNullOrWhiteSpace(type) ? nameof(NoLoadBalancer) : type; Key = key; ExpiryInMs = expiryInMs; } @@ -14,7 +14,7 @@ namespace Ocelot.Configuration public string Type { get; } public string Key { get; } - - public int ExpiryInMs { get; } + + public int ExpiryInMs { get; } } } From eedc47c8504821bc0164a7020acadd4ee091e6cd Mon Sep 17 00:00:00 2001 From: Shengjie Yan Date: Tue, 26 Feb 2019 20:28:27 +0800 Subject: [PATCH 6/8] Update loadbalancer.rst (#796) --- docs/features/loadbalancer.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/loadbalancer.rst b/docs/features/loadbalancer.rst index 841d1661..947a6ab2 100644 --- a/docs/features/loadbalancer.rst +++ b/docs/features/loadbalancer.rst @@ -18,7 +18,7 @@ You must choose in your configuration which load balancer to use. Configuration ^^^^^^^^^^^^^ -The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeadConnection load balancer. This is the simplest way to get load balancing set up. +The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeastConnection load balancer. This is the simplest way to get load balancing set up. .. code-block:: json From b4f5b6861495ee079ea6f140a6f84ae1ee426409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nagy=20B=C3=A1lint?= Date: Tue, 26 Feb 2019 13:39:56 +0100 Subject: [PATCH 7/8] UriBuilder - remove leading question mark #747 (#794) --- src/Ocelot/Request/Middleware/DownstreamRequest.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Ocelot/Request/Middleware/DownstreamRequest.cs b/src/Ocelot/Request/Middleware/DownstreamRequest.cs index 75070bfd..665039d0 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequest.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequest.cs @@ -44,7 +44,7 @@ namespace Ocelot.Request.Middleware Port = Port, Host = Host, Path = AbsolutePath, - Query = Query, + Query = RemoveLeadingQuestionMark(Query), Scheme = Scheme }; @@ -59,7 +59,7 @@ namespace Ocelot.Request.Middleware Port = Port, Host = Host, Path = AbsolutePath, - Query = Query, + Query = RemoveLeadingQuestionMark(Query), Scheme = Scheme }; @@ -70,5 +70,15 @@ namespace Ocelot.Request.Middleware { return ToUri(); } + + private string RemoveLeadingQuestionMark(string query) + { + if (!string.IsNullOrEmpty(query) && query.StartsWith("?")) + { + return query.Substring(1); + } + + return query; + } } } From 3c580e3d9658a2509ef1a0c621e5fb772851289e Mon Sep 17 00:00:00 2001 From: Vincent Lefebvre Date: Thu, 7 Mar 2019 21:18:59 -0500 Subject: [PATCH 8/8] Update qualityofservice.rst (#801) Tiny typo --- docs/features/qualityofservice.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/qualityofservice.rst b/docs/features/qualityofservice.rst index fc3770ed..512ffaad 100644 --- a/docs/features/qualityofservice.rst +++ b/docs/features/qualityofservice.rst @@ -5,7 +5,7 @@ Ocelot supports one QoS capability at the current time. You can set on a per ReR want to use a circuit breaker when making requests to a downstream service. This uses an awesome .NET library called Polly check them out `here `_. -The first thing you need to do if you want to use the administration API is bring in the relavent NuGet package.. +The first thing you need to do if you want to use the administration API is bring in the relevant NuGet package.. ``Install-Package Ocelot.Provider.Polly``