Merge pull request #56 from ThreeMammals/develop

merge
This commit is contained in:
geffzhang 2019-03-11 09:39:46 +08:00 committed by GitHub
commit 247403f72a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 398 additions and 73 deletions

View File

@ -219,6 +219,6 @@ If you want to ignore SSL warnings / errors set the following in your ReRoute co
.. code-block:: json .. 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. 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.

View File

@ -18,7 +18,7 @@ You must choose in your configuration which load balancer to use.
Configuration 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 .. code-block:: json

View File

@ -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 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 <https://github.com/App-vNext/Polly>`_. .NET library called Polly check them out `here <https://github.com/App-vNext/Polly>`_.
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`` ``Install-Package Ocelot.Provider.Polly``

View File

@ -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 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. 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 If you are using containers and require Ocelot to respond to clients 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. 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** **Program**

View File

@ -4,6 +4,7 @@
using System.Net.Http; using System.Net.Http;
using Ocelot.Values; using Ocelot.Values;
using System.Linq; using System.Linq;
using Ocelot.Configuration.File;
public class ReRouteBuilder public class ReRouteBuilder
{ {
@ -11,11 +12,13 @@
private List<HttpMethod> _upstreamHttpMethod; private List<HttpMethod> _upstreamHttpMethod;
private string _upstreamHost; private string _upstreamHost;
private List<DownstreamReRoute> _downstreamReRoutes; private List<DownstreamReRoute> _downstreamReRoutes;
private List<AggregateReRouteConfig> _downstreamReRoutesConfig;
private string _aggregator; private string _aggregator;
public ReRouteBuilder() public ReRouteBuilder()
{ {
_downstreamReRoutes = new List<DownstreamReRoute>(); _downstreamReRoutes = new List<DownstreamReRoute>();
_downstreamReRoutesConfig = new List<AggregateReRouteConfig>();
} }
public ReRouteBuilder WithDownstreamReRoute(DownstreamReRoute value) public ReRouteBuilder WithDownstreamReRoute(DownstreamReRoute value)
@ -48,6 +51,12 @@
return this; return this;
} }
public ReRouteBuilder WithAggregateReRouteConfig(List<AggregateReRouteConfig> aggregateReRouteConfigs)
{
_downstreamReRoutesConfig = aggregateReRouteConfigs;
return this;
}
public ReRouteBuilder WithAggregator(string aggregator) public ReRouteBuilder WithAggregator(string aggregator)
{ {
_aggregator = aggregator; _aggregator = aggregator;
@ -58,6 +67,7 @@
{ {
return new ReRoute( return new ReRoute(
_downstreamReRoutes, _downstreamReRoutes,
_downstreamReRoutesConfig,
_upstreamHttpMethod, _upstreamHttpMethod,
_upstreamTemplatePattern, _upstreamTemplatePattern,
_upstreamHost, _upstreamHost,

View File

@ -24,14 +24,18 @@ namespace Ocelot.Configuration.Creator
private ReRoute SetUpAggregateReRoute(IEnumerable<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) private ReRoute SetUpAggregateReRoute(IEnumerable<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration)
{ {
var applicableReRoutes = reRoutes var applicableReRoutes = new List<DownstreamReRoute>();
.SelectMany(x => x.DownstreamReRoute) var allReRoutes = reRoutes.SelectMany(x => x.DownstreamReRoute);
.Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key))
.ToList();
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); var upstreamTemplatePattern = _creator.Create(aggregateReRoute);
@ -40,6 +44,7 @@ namespace Ocelot.Configuration.Creator
.WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod) .WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod)
.WithUpstreamPathTemplate(upstreamTemplatePattern) .WithUpstreamPathTemplate(upstreamTemplatePattern)
.WithDownstreamReRoutes(applicableReRoutes) .WithDownstreamReRoutes(applicableReRoutes)
.WithAggregateReRouteConfig(aggregateReRoute.ReRouteKeysConfig)
.WithUpstreamHost(aggregateReRoute.UpstreamHost) .WithUpstreamHost(aggregateReRoute.UpstreamHost)
.WithAggregator(aggregateReRoute.Aggregator) .WithAggregator(aggregateReRoute.Aggregator)
.Build(); .Build();

View File

@ -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; }
}
}

View File

@ -5,6 +5,7 @@ namespace Ocelot.Configuration.File
public class FileAggregateReRoute : IReRoute public class FileAggregateReRoute : IReRoute
{ {
public List<string> ReRouteKeys { get;set; } public List<string> ReRouteKeys { get;set; }
public List<AggregateReRouteConfig> ReRouteKeysConfig { get;set; }
public string UpstreamPathTemplate { get;set; } public string UpstreamPathTemplate { get;set; }
public string UpstreamHost { get; set; } public string UpstreamHost { get; set; }
public bool ReRouteIsCaseSensitive { get; set; } public bool ReRouteIsCaseSensitive { get; set; }

View File

@ -6,7 +6,7 @@ namespace Ocelot.Configuration
{ {
public LoadBalancerOptions(string type, string key, int expiryInMs) public LoadBalancerOptions(string type, string key, int expiryInMs)
{ {
Type = type ?? nameof(NoLoadBalancer); Type = string.IsNullOrWhiteSpace(type) ? nameof(NoLoadBalancer) : type;
Key = key; Key = key;
ExpiryInMs = expiryInMs; ExpiryInMs = expiryInMs;
} }

View File

@ -2,11 +2,13 @@
{ {
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using Ocelot.Configuration.File;
using Ocelot.Values; using Ocelot.Values;
public class ReRoute public class ReRoute
{ {
public ReRoute(List<DownstreamReRoute> downstreamReRoute, public ReRoute(List<DownstreamReRoute> downstreamReRoute,
List<AggregateReRouteConfig> downstreamReRouteConfig,
List<HttpMethod> upstreamHttpMethod, List<HttpMethod> upstreamHttpMethod,
UpstreamPathTemplate upstreamTemplatePattern, UpstreamPathTemplate upstreamTemplatePattern,
string upstreamHost, string upstreamHost,
@ -14,6 +16,7 @@
{ {
UpstreamHost = upstreamHost; UpstreamHost = upstreamHost;
DownstreamReRoute = downstreamReRoute; DownstreamReRoute = downstreamReRoute;
DownstreamReRouteConfig = downstreamReRouteConfig;
UpstreamHttpMethod = upstreamHttpMethod; UpstreamHttpMethod = upstreamHttpMethod;
UpstreamTemplatePattern = upstreamTemplatePattern; UpstreamTemplatePattern = upstreamTemplatePattern;
Aggregator = aggregator; Aggregator = aggregator;
@ -23,6 +26,7 @@
public List<HttpMethod> UpstreamHttpMethod { get; private set; } public List<HttpMethod> UpstreamHttpMethod { get; private set; }
public string UpstreamHost { get; private set; } public string UpstreamHost { get; private set; }
public List<DownstreamReRoute> DownstreamReRoute { get; private set; } public List<DownstreamReRoute> DownstreamReRoute { get; private set; }
public List<AggregateReRouteConfig> DownstreamReRouteConfig { get; private set; }
public string Aggregator {get; private set;} public string Aggregator {get; private set;}
} }
} }

View File

@ -5,12 +5,12 @@ namespace Ocelot.Middleware.Multiplexer
public class InMemoryResponseAggregatorFactory : IResponseAggregatorFactory public class InMemoryResponseAggregatorFactory : IResponseAggregatorFactory
{ {
private readonly UserDefinedResponseAggregator _userDefined; 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); _userDefined = new UserDefinedResponseAggregator(provider);
_simple = new SimpleJsonResponseAggregator(); _simple = responseAggregator;
} }
public IResponseAggregator Get(ReRoute reRoute) public IResponseAggregator Get(ReRoute reRoute)

View File

@ -1,7 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.UrlMatcher;
namespace Ocelot.Middleware.Multiplexer namespace Ocelot.Middleware.Multiplexer
{ {
@ -16,31 +18,105 @@ namespace Ocelot.Middleware.Multiplexer
public async Task Multiplex(DownstreamContext context, ReRoute reRoute, OcelotRequestDelegate next) public async Task Multiplex(DownstreamContext context, ReRoute reRoute, OcelotRequestDelegate next)
{ {
var tasks = new Task<DownstreamContext>[reRoute.DownstreamReRoute.Count]; var reRouteKeysConfigs = reRoute.DownstreamReRouteConfig;
if (reRouteKeysConfigs == null || !reRouteKeysConfigs.Any())
for (var i = 0; i < reRoute.DownstreamReRoute.Count; i++)
{ {
var downstreamContext = new DownstreamContext(context.HttpContext) var tasks = new Task<DownstreamContext>[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<DownstreamContext>();
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, TemplatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues,
Configuration = context.Configuration, 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<DownstreamContext>() { mainResponse });
return;
}
var tasks = new List<Task<DownstreamContext>>();
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<PlaceholderNameAndValue>(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<PlaceholderNameAndValue>(templatePlaceholderNameAndValues),
Configuration = context.Configuration,
DownstreamReRoute = downstreamReRoute,
};
tasks.Add(Fire(downstreamContext, next));
}
}
await Task.WhenAll(tasks);
var contexts = new List<DownstreamContext>() { 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<DownstreamContext>();
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<DownstreamContext> contexts) private async Task Map(ReRoute reRoute, DownstreamContext context, List<DownstreamContext> contexts)

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
@ -21,19 +22,54 @@ namespace Ocelot.Middleware.Multiplexer
contentBuilder.Append("{"); 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); if (contexts[0].IsError)
return; {
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(); if (k + 1 < responseKeys.Count)
contentBuilder.Append($"\"{downstreamContexts[i].DownstreamReRoute.Key}\":{content}");
if (i + 1 < downstreamContexts.Count)
{ {
contentBuilder.Append(","); contentBuilder.Append(",");
} }
@ -43,16 +79,16 @@ namespace Ocelot.Middleware.Multiplexer
var stringContent = new StringContent(contentBuilder.ToString()) 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<KeyValuePair<string, IEnumerable<string>>>(), "cannot return from aggregate..which reason phrase would you use?"); originalContext.DownstreamResponse = new DownstreamResponse(stringContent, HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>(), "cannot return from aggregate..which reason phrase would you use?");
} }
private static void MapAggregateError(DownstreamContext originalContext, List<DownstreamContext> downstreamContexts, int i) private static void MapAggregateError(DownstreamContext originalContext, DownstreamContext downstreamContext)
{ {
originalContext.Errors.AddRange(downstreamContexts[i].Errors); originalContext.Errors.AddRange(downstreamContext.Errors);
originalContext.DownstreamResponse = downstreamContexts[i].DownstreamResponse; originalContext.DownstreamResponse = downstreamContext.DownstreamResponse;
} }
} }
} }

View File

@ -44,7 +44,7 @@ namespace Ocelot.Request.Middleware
Port = Port, Port = Port,
Host = Host, Host = Host,
Path = AbsolutePath, Path = AbsolutePath,
Query = Query, Query = RemoveLeadingQuestionMark(Query),
Scheme = Scheme Scheme = Scheme
}; };
@ -59,7 +59,7 @@ namespace Ocelot.Request.Middleware
Port = Port, Port = Port,
Host = Host, Host = Host,
Path = AbsolutePath, Path = AbsolutePath,
Query = Query, Query = RemoveLeadingQuestionMark(Query),
Scheme = Scheme Scheme = Scheme
}; };
@ -70,5 +70,15 @@ namespace Ocelot.Request.Middleware
{ {
return ToUri(); return ToUri();
} }
private string RemoveLeadingQuestionMark(string query)
{
if (!string.IsNullOrEmpty(query) && query.StartsWith("?"))
{
return query.Substring(1);
}
return query;
}
} }
} }

View File

@ -32,6 +32,15 @@ namespace Ocelot.Responder
AddHeaderIfDoesntExist(context, httpResponseHeader); AddHeaderIfDoesntExist(context, httpResponseHeader);
} }
SetStatusCode(context, (int)response.StatusCode);
context.Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = response.ReasonPhrase;
if (response.Content is null)
{
return;
}
foreach (var httpResponseHeader in response.Content.Headers) foreach (var httpResponseHeader in response.Content.Headers)
{ {
AddHeaderIfDoesntExist(context, new Header(httpResponseHeader.Key, httpResponseHeader.Value)); 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() }) ); AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ response.Content.Headers.ContentLength.ToString() }) );
} }
SetStatusCode(context, (int)response.StatusCode);
context.Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = response.ReasonPhrase;
using(content) using(content)
{ {
if (response.StatusCode != HttpStatusCode.NotModified && context.Response.ContentLength != 0) if (response.StatusCode != HttpStatusCode.NotModified && context.Response.ContentLength != 0)

View File

@ -141,6 +141,100 @@ namespace Ocelot.AcceptanceTests
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_return_response_200_with_advanced_aggregate_configs()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51889,
}
},
UpstreamPathTemplate = "/Comments",
UpstreamHttpMethod = new List<string> { "Get" },
Key = "Comments"
},
new FileReRoute
{
DownstreamPathTemplate = "/users/{userId}",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51890,
}
},
UpstreamPathTemplate = "/UserDetails",
UpstreamHttpMethod = new List<string> { "Get" },
Key = "UserDetails"
},
new FileReRoute
{
DownstreamPathTemplate = "/posts/{postId}",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51887,
}
},
UpstreamPathTemplate = "/PostDetails",
UpstreamHttpMethod = new List<string> { "Get" },
Key = "PostDetails"
}
},
Aggregates = new List<FileAggregateReRoute>
{
new FileAggregateReRoute
{
UpstreamPathTemplate = "/",
UpstreamHost = "localhost",
ReRouteKeys = new List<string>
{
"Comments",
"UserDetails",
"PostDetails"
},
ReRouteKeysConfig = new List<AggregateReRouteConfig>()
{
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] [Fact]
public void should_return_response_200_with_simple_url_user_defined_aggregate() public void should_return_response_200_with_simple_url_user_defined_aggregate()
{ {
@ -189,8 +283,8 @@ namespace Ocelot.AcceptanceTests
UpstreamHost = "localhost", UpstreamHost = "localhost",
ReRouteKeys = new List<string> ReRouteKeys = new List<string>
{ {
"Tom", "Laura",
"Laura" "Tom"
}, },
Aggregator = "FakeDefinedAggregator" Aggregator = "FakeDefinedAggregator"
} }
@ -258,8 +352,8 @@ namespace Ocelot.AcceptanceTests
UpstreamHost = "localhost", UpstreamHost = "localhost",
ReRouteKeys = new List<string> ReRouteKeys = new List<string>
{ {
"Tom", "Laura",
"Laura" "Tom"
} }
} }
} }
@ -326,8 +420,9 @@ namespace Ocelot.AcceptanceTests
UpstreamHost = "localhost", UpstreamHost = "localhost",
ReRouteKeys = new List<string> ReRouteKeys = new List<string>
{ {
"Tom", "Laura",
"Laura" "Tom"
} }
} }
} }
@ -394,8 +489,8 @@ namespace Ocelot.AcceptanceTests
UpstreamHost = "localhost", UpstreamHost = "localhost",
ReRouteKeys = new List<string> ReRouteKeys = new List<string>
{ {
"Tom", "Laura",
"Laura" "Tom"
} }
} }
} }
@ -462,8 +557,8 @@ namespace Ocelot.AcceptanceTests
UpstreamHost = "localhost", UpstreamHost = "localhost",
ReRouteKeys = new List<string> ReRouteKeys = new List<string>
{ {
"Tom", "Laura",
"Laura" "Tom"
} }
} }
} }

View File

@ -18,7 +18,8 @@ namespace Ocelot.UnitTests.Middleware
public ResponseAggregatorFactoryTests() public ResponseAggregatorFactoryTests()
{ {
_provider = new Mock<IDefinedAggregatorProvider>(); _provider = new Mock<IDefinedAggregatorProvider>();
_factory = new InMemoryResponseAggregatorFactory(_provider.Object); _aggregator = new SimpleJsonResponseAggregator();
_factory = new InMemoryResponseAggregatorFactory(_provider.Object, _aggregator);
} }
[Fact] [Fact]

View File

@ -7,11 +7,11 @@ using Castle.Components.DictionaryAdapter;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Builder; using Ocelot.Configuration.Builder;
using Ocelot.Errors; using Ocelot.Configuration.File;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer; using Ocelot.Middleware.Multiplexer;
using Ocelot.Request.Middleware;
using Ocelot.UnitTests.Responder; using Ocelot.UnitTests.Responder;
using Ocelot.Values;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
@ -30,6 +30,58 @@ namespace Ocelot.UnitTests.Middleware
_aggregator = new SimpleJsonResponseAggregator(); _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<DownstreamReRoute>
{
commentsDownstreamReRoute,
userDetailsDownstreamReRoute
};
var reRoute = new ReRouteBuilder()
.WithDownstreamReRoutes(downstreamReRoutes)
.WithAggregateReRouteConfig(new List<AggregateReRouteConfig>()
{
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<KeyValuePair<string, IEnumerable<string>>>(), "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<KeyValuePair<string, IEnumerable<string>>>(), "some reason"),
DownstreamReRoute = userDetailsDownstreamReRoute
};
var downstreamContexts = new List<DownstreamContext> { 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] [Fact]
public void should_aggregate_n_responses_and_set_response_content_on_upstream_context() public void should_aggregate_n_responses_and_set_response_content_on_upstream_context()
{ {

View File

@ -39,6 +39,23 @@ namespace Ocelot.UnitTests.Responder
header.ShouldBeEmpty(); header.ShouldBeEmpty();
} }
[Fact]
public void should_ignore_content_if_null()
{
var httpContext = new DefaultHttpContext();
var response = new DownstreamResponse(null, HttpStatusCode.OK,
new List<KeyValuePair<string, IEnumerable<string>>>(), "some reason");
Should.NotThrow(() =>
{
_responder
.SetResponseOnHttpContext(httpContext, response)
.GetAwaiter()
.GetResult()
;
});
}
[Fact] [Fact]
public void should_have_content_length() public void should_have_content_length()
{ {