#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
This commit is contained in:
Tom Pallister 2018-04-12 17:35:04 +01:00 committed by GitHub
parent 982eebfc74
commit a15f75dda8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 1203 additions and 410 deletions

View File

@ -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 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. architecture with Ocelot.
This feature was requested as part of `Issue 79 <https://github.com/TomPallister/Ocelot/pull/79>`_ . This feature was requested as part of `Issue 79 <https://github.com/TomPallister/Ocelot/pull/79>`_ and further improvements were made as part of `Issue 298 <https://github.com/TomPallister/Ocelot/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 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. 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). 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<FakeDefinedAggregator>();
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<FooDependency>();
services
.AddOcelot()
.AddSingletonDefinedAggregator<FooAggregator>();
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<FakeDefinedAggregator>();
In order to make an Aggregator you must implement this interface.
.. code-block:: csharp
public interface IDefinedAggregator
{
Task<DownstreamResponse> Aggregate(List<DownstreamResponse> 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 .. 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}} {"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 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 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. 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. 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. You cannot use ReRoutes with specific RequestIdKeys as this would be crazy complicated to track.
Aggregation only supports the GET HTTP Verb. 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?

View File

@ -2,12 +2,10 @@
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using System.IO; using System.IO;
using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Middleware.Multiplexer;
namespace Ocelot.Cache.Middleware namespace Ocelot.Cache.Middleware
{ {
@ -72,38 +70,31 @@ namespace Ocelot.Cache.Middleware
Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}"); 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; context.DownstreamResponse = response;
} }
internal HttpResponseMessage CreateHttpResponseMessage(CachedResponse cached) internal DownstreamResponse CreateHttpResponseMessage(CachedResponse cached)
{ {
if (cached == null) if (cached == null)
{ {
return 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)); var content = new MemoryStream(Convert.FromBase64String(cached.Body));
response.Content = new StreamContent(content); var streamContent = new StreamContent(content);
foreach (var header in cached.ContentHeaders) 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<CachedResponse> CreateCachedResponse(HttpResponseMessage response) internal async Task<CachedResponse> CreateCachedResponse(DownstreamResponse response)
{ {
if (response == null) if (response == null)
{ {
@ -111,7 +102,7 @@ namespace Ocelot.Cache.Middleware
} }
var statusCode = response.StatusCode; 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; string body = null;
if (response.Content != null) if (response.Content != null)

View File

@ -14,6 +14,7 @@ namespace Ocelot.Configuration.Builder
private List<HttpMethod> _upstreamHttpMethod; private List<HttpMethod> _upstreamHttpMethod;
private string _upstreamHost; private string _upstreamHost;
private List<DownstreamReRoute> _downstreamReRoutes; private List<DownstreamReRoute> _downstreamReRoutes;
private string _aggregator;
public ReRouteBuilder() public ReRouteBuilder()
{ {
@ -56,6 +57,12 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public ReRouteBuilder WithAggregator(string aggregator)
{
_aggregator = aggregator;
return this;
}
public ReRoute Build() public ReRoute Build()
{ {
return new ReRoute( return new ReRoute(
@ -63,7 +70,8 @@ namespace Ocelot.Configuration.Builder
new PathTemplate(_upstreamTemplate), new PathTemplate(_upstreamTemplate),
_upstreamHttpMethod, _upstreamHttpMethod,
_upstreamTemplatePattern, _upstreamTemplatePattern,
_upstreamHost _upstreamHost,
_aggregator
); );
} }
} }

View File

@ -132,6 +132,7 @@ namespace Ocelot.Configuration.Creator
.WithUpstreamTemplatePattern(upstreamTemplatePattern) .WithUpstreamTemplatePattern(upstreamTemplatePattern)
.WithDownstreamReRoutes(applicableReRoutes) .WithDownstreamReRoutes(applicableReRoutes)
.WithUpstreamHost(aggregateReRoute.UpstreamHost) .WithUpstreamHost(aggregateReRoute.UpstreamHost)
.WithAggregator(aggregateReRoute.Aggregator)
.Build(); .Build();
return reRoute; return reRoute;

View File

@ -8,6 +8,7 @@ namespace Ocelot.Configuration.File
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; }
public string Aggregator { get; set; }
// Only supports GET..are you crazy!! POST, PUT WOULD BE CRAZY!! :) // Only supports GET..are you crazy!! POST, PUT WOULD BE CRAZY!! :)
public List<string> UpstreamHttpMethod public List<string> UpstreamHttpMethod

View File

@ -12,13 +12,15 @@ namespace Ocelot.Configuration
PathTemplate upstreamPathTemplate, PathTemplate upstreamPathTemplate,
List<HttpMethod> upstreamHttpMethod, List<HttpMethod> upstreamHttpMethod,
UpstreamPathTemplate upstreamTemplatePattern, UpstreamPathTemplate upstreamTemplatePattern,
string upstreamHost) string upstreamHost,
string aggregator)
{ {
UpstreamHost = upstreamHost; UpstreamHost = upstreamHost;
DownstreamReRoute = downstreamReRoute; DownstreamReRoute = downstreamReRoute;
UpstreamPathTemplate = upstreamPathTemplate; UpstreamPathTemplate = upstreamPathTemplate;
UpstreamHttpMethod = upstreamHttpMethod; UpstreamHttpMethod = upstreamHttpMethod;
UpstreamTemplatePattern = upstreamTemplatePattern; UpstreamTemplatePattern = upstreamTemplatePattern;
Aggregator = aggregator;
} }
public PathTemplate UpstreamPathTemplate { get; private set; } public PathTemplate UpstreamPathTemplate { get; private set; }
@ -26,5 +28,6 @@ namespace Ocelot.Configuration
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 string Aggregator {get; private set;}
} }
} }

View File

@ -3,6 +3,7 @@ using CacheManager.Core;
using System; using System;
using System.Net.Http; using System.Net.Http;
using IdentityServer4.AccessTokenValidation; using IdentityServer4.AccessTokenValidation;
using Ocelot.Middleware.Multiplexer;
namespace Ocelot.DependencyInjection namespace Ocelot.DependencyInjection
{ {
@ -23,5 +24,10 @@ namespace Ocelot.DependencyInjection
IOcelotBuilder AddTransientDelegatingHandler<T>(bool global = false) IOcelotBuilder AddTransientDelegatingHandler<T>(bool global = false)
where T : DelegatingHandler; where T : DelegatingHandler;
IOcelotBuilder AddSingletonDefinedAggregator<T>()
where T : class, IDefinedAggregator;
IOcelotBuilder AddTransientDefinedAggregator<T>()
where T : class, IDefinedAggregator;
} }
} }

View File

@ -154,6 +154,8 @@ namespace Ocelot.DependencyInjection
_services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>(); _services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();
_services.TryAddSingleton<IPlaceholders, Placeholders>(); _services.TryAddSingleton<IPlaceholders, Placeholders>();
_services.TryAddSingleton<IConsulClientFactory, ConsulClientFactory>(); _services.TryAddSingleton<IConsulClientFactory, ConsulClientFactory>();
_services.TryAddSingleton<IResponseAggregatorFactory, InMemoryResponseAggregatorFactory>();
_services.TryAddSingleton<IDefinedAggregatorProvider, ServiceLocatorDefinedAggregatorProvider>();
} }
public IOcelotAdministrationBuilder AddAdministration(string path, string secret) public IOcelotAdministrationBuilder AddAdministration(string path, string secret)
@ -188,6 +190,20 @@ namespace Ocelot.DependencyInjection
return new OcelotAdministrationBuilder(_services, _configurationRoot); return new OcelotAdministrationBuilder(_services, _configurationRoot);
} }
public IOcelotBuilder AddSingletonDefinedAggregator<T>()
where T : class, IDefinedAggregator
{
_services.AddSingleton<IDefinedAggregator, T>();
return this;
}
public IOcelotBuilder AddTransientDefinedAggregator<T>()
where T : class, IDefinedAggregator
{
_services.AddTransient<IDefinedAggregator, T>();
return this;
}
public IOcelotBuilder AddSingletonDelegatingHandler<THandler>(bool global = false) public IOcelotBuilder AddSingletonDelegatingHandler<THandler>(bool global = false)
where THandler : DelegatingHandler where THandler : DelegatingHandler
{ {

View File

@ -35,6 +35,7 @@
PathTemplateDoesntStartWithForwardSlash, PathTemplateDoesntStartWithForwardSlash,
FileValidationFailedError, FileValidationFailedError,
UnableToFindDelegatingHandlerProviderError, UnableToFindDelegatingHandlerProviderError,
CouldNotFindPlaceholderError CouldNotFindPlaceholderError,
CouldNotFindAggregatorError
} }
} }

View File

@ -1,10 +1,10 @@
namespace Ocelot.Headers namespace Ocelot.Headers
{ {
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
using Ocelot.Infrastructure; using Ocelot.Infrastructure;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware;
public class AddHeadersToResponse : IAddHeadersToResponse public class AddHeadersToResponse : IAddHeadersToResponse
{ {
@ -17,7 +17,7 @@ namespace Ocelot.Headers
_placeholders = placeholders; _placeholders = placeholders;
} }
public void Add(List<AddHeader> addHeaders, HttpResponseMessage response) public void Add(List<AddHeader> addHeaders, DownstreamResponse response)
{ {
foreach(var add in addHeaders) foreach(var add in addHeaders)
{ {
@ -31,11 +31,11 @@ namespace Ocelot.Headers
continue; continue;
} }
response.Headers.TryAddWithoutValidation(add.Key, value.Data); response.Headers.Add(new Header(add.Key, new List<string> { value.Data }));
} }
else else
{ {
response.Headers.TryAddWithoutValidation(add.Key, add.Value); response.Headers.Add(new Header(add.Key, new List<string> { add.Value }));
} }
} }
} }

View File

@ -1,10 +1,10 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Infrastructure; using Ocelot.Infrastructure;
using Ocelot.Infrastructure.Extensions; using Ocelot.Infrastructure.Extensions;
using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Request.Middleware; using Ocelot.Request.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
@ -12,19 +12,21 @@ namespace Ocelot.Headers
{ {
public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer
{ {
private IPlaceholders _placeholders; private readonly IPlaceholders _placeholders;
public HttpResponseHeaderReplacer(IPlaceholders placeholders) public HttpResponseHeaderReplacer(IPlaceholders placeholders)
{ {
_placeholders = placeholders; _placeholders = placeholders;
} }
public Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs, DownstreamRequest request) public Response Replace(DownstreamResponse response, List<HeaderFindAndReplace> fAndRs, DownstreamRequest request)
{ {
foreach (var f in fAndRs) foreach (var f in fAndRs)
{ {
var dict = response.Headers.ToDictionary(x => x.Key);
//if the response headers contain a matching find and replace //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... //check to see if it is a placeholder in the find...
var placeholderValue = _placeholders.Get(f.Find, request); var placeholderValue = _placeholders.Get(f.Find, request);
@ -32,16 +34,17 @@ namespace Ocelot.Headers
if(!placeholderValue.IsError) if(!placeholderValue.IsError)
{ {
//if it is we need to get the value of the placeholder //if it is we need to get the value of the placeholder
//var find = replacePlaceholder(httpRequestMessage); var replaced = values.Values.ToList()[f.Index].Replace(placeholderValue.Data, f.Replace.LastCharAsForwardSlash());
var replaced = values.ToList()[f.Index].Replace(placeholderValue.Data, f.Replace.LastCharAsForwardSlash());
response.Headers.Remove(f.Key); response.Headers.Remove(response.Headers.First(item => item.Key == f.Key));
response.Headers.Add(f.Key, replaced); response.Headers.Add(new Header(f.Key, new List<string> { replaced }));
} }
else else
{ {
var replaced = values.ToList()[f.Index].Replace(f.Find, f.Replace); var replaced = values.Values.ToList()[f.Index].Replace(f.Find, f.Replace);
response.Headers.Remove(f.Key);
response.Headers.Add(f.Key, replaced); response.Headers.Remove(response.Headers.First(item => item.Key == f.Key));
response.Headers.Add(new Header(f.Key, new List<string> { replaced }));
} }
} }
} }

View File

@ -1,11 +1,13 @@
using Ocelot.Middleware;
namespace Ocelot.Headers namespace Ocelot.Headers
{ {
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
using Ocelot.Middleware.Multiplexer;
public interface IAddHeadersToResponse public interface IAddHeadersToResponse
{ {
void Add(List<AddHeader> addHeaders, HttpResponseMessage response); void Add(List<AddHeader> addHeaders, DownstreamResponse response);
} }
} }

View File

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Request.Middleware; using Ocelot.Request.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
@ -8,6 +9,6 @@ namespace Ocelot.Headers
{ {
public interface IHttpResponseHeaderReplacer public interface IHttpResponseHeaderReplacer
{ {
Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs, DownstreamRequest httpRequestMessage); Response Replace(DownstreamResponse response, List<HeaderFindAndReplace> fAndRs, DownstreamRequest httpRequestMessage);
} }
} }

View File

@ -1,10 +1,12 @@
using System.Net.Http.Headers; using System.Collections.Generic;
using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Responses; using Ocelot.Responses;
namespace Ocelot.Headers namespace Ocelot.Headers
{ {
public interface IRemoveOutputHeaders public interface IRemoveOutputHeaders
{ {
Response Remove(HttpResponseHeaders headers); Response Remove(List<Header> headers);
} }
} }

View File

@ -1,7 +1,4 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;

View File

@ -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; using Ocelot.Responses;
namespace Ocelot.Headers namespace Ocelot.Headers
@ -14,13 +17,10 @@ namespace Ocelot.Headers
{ {
"Transfer-Encoding" "Transfer-Encoding"
}; };
public Response Remove(HttpResponseHeaders headers)
{
foreach (var unsupported in _unsupportedRequestHeaders)
{
headers.Remove(unsupported);
}
public Response Remove(List<Header> headers)
{
headers.RemoveAll(x => _unsupportedRequestHeaders.Contains(x.Key));
return new OkResponse(); return new OkResponse();
} }
} }

View File

@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Http;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.DownstreamRouteFinder.UrlMatcher;
using Ocelot.Errors; using Ocelot.Errors;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Request.Middleware; using Ocelot.Request.Middleware;
namespace Ocelot.Middleware namespace Ocelot.Middleware
@ -27,9 +28,9 @@ namespace Ocelot.Middleware
public DownstreamRequest DownstreamRequest { get; set; } public DownstreamRequest DownstreamRequest { get; set; }
public HttpResponseMessage DownstreamResponse { get; set; } public DownstreamResponse DownstreamResponse { get; set; }
public List<Error> Errors { get;set; } public List<Error> Errors { get; }
public bool IsError => Errors.Count > 0; public bool IsError => Errors.Count > 0;
} }

View File

@ -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<Header> headers)
{
Content = content;
StatusCode = statusCode;
Headers = headers ?? new List<Header>();
}
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<KeyValuePair<string, IEnumerable<string>>> headers)
:this(content, statusCode, headers.Select(x => new Header(x.Key, x.Value)).ToList())
{
}
public HttpContent Content { get; }
public HttpStatusCode StatusCode { get; }
public List<Header> Headers { get; }
}
}

View File

@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace Ocelot.Middleware
{
public class Header
{
public Header(string key, IEnumerable<string> values)
{
Key = key;
Values = values ?? new List<string>();
}
public string Key { get; }
public IEnumerable<string> Values { get; }
}
}

View File

@ -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)
{
}
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Ocelot.Middleware.Multiplexer
{
public interface IDefinedAggregator
{
Task<DownstreamResponse> Aggregate(List<DownstreamResponse> responses);
}
}

View File

@ -0,0 +1,10 @@
using Ocelot.Configuration;
using Ocelot.Responses;
namespace Ocelot.Middleware.Multiplexer
{
public interface IDefinedAggregatorProvider
{
Response<IDefinedAggregator> Get(ReRoute reRoute);
}
}

View File

@ -6,6 +6,6 @@ namespace Ocelot.Middleware.Multiplexer
{ {
public interface IResponseAggregator public interface IResponseAggregator
{ {
Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List<DownstreamContext> downstreamContexts); Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List<DownstreamContext> downstreamResponses);
} }
} }

View File

@ -0,0 +1,9 @@
using Ocelot.Configuration;
namespace Ocelot.Middleware.Multiplexer
{
public interface IResponseAggregatorFactory
{
IResponseAggregator Get(ReRoute reRoute);
}
}

View File

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

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Configuration; using Ocelot.Configuration;
@ -6,11 +7,11 @@ namespace Ocelot.Middleware.Multiplexer
{ {
public class Multiplexer : IMultiplexer 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) public async Task Multiplex(DownstreamContext context, ReRoute reRoute, OcelotRequestDelegate next)
@ -31,15 +32,40 @@ namespace Ocelot.Middleware.Multiplexer
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
var downstreamContexts = new List<DownstreamContext>(); var contexts = new List<DownstreamContext>();
foreach (var task in tasks) foreach (var task in tasks)
{ {
var finished = await task; 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<DownstreamContext> 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<DownstreamContext> 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<DownstreamContext> Fire(DownstreamContext context, OcelotRequestDelegate next) private async Task<DownstreamContext> Fire(DownstreamContext context, OcelotRequestDelegate next)

View File

@ -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<string, IDefinedAggregator> _aggregators;
public ServiceLocatorDefinedAggregatorProvider(IServiceProvider services)
{
_aggregators = services.GetServices<IDefinedAggregator>().ToDictionary(x => x.GetType().Name);
}
public Response<IDefinedAggregator> Get(ReRoute reRoute)
{
if(_aggregators.ContainsKey(reRoute.Aggregator))
{
return new OkResponse<IDefinedAggregator>(_aggregators[reRoute.Aggregator]);
}
return new ErrorResponse<IDefinedAggregator>(new CouldNotFindAggregatorError(reRoute.Aggregator));
}
}
}

View File

@ -1,5 +1,4 @@
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;
@ -12,18 +11,6 @@ namespace Ocelot.Middleware.Multiplexer
public class SimpleJsonResponseAggregator : IResponseAggregator public class SimpleJsonResponseAggregator : IResponseAggregator
{ {
public async Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List<DownstreamContext> downstreamContexts) public async Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List<DownstreamContext> downstreamContexts)
{
if (reRoute.DownstreamReRoute.Count > 1)
{
await MapAggregtes(originalContext, downstreamContexts);
}
else
{
MapNotAggregate(originalContext, downstreamContexts);
}
}
private async Task MapAggregtes(DownstreamContext originalContext, List<DownstreamContext> downstreamContexts)
{ {
await MapAggregateContent(originalContext, downstreamContexts); await MapAggregateContent(originalContext, downstreamContexts);
} }
@ -34,7 +21,7 @@ namespace Ocelot.Middleware.Multiplexer
contentBuilder.Append("{"); contentBuilder.Append("{");
for (int i = 0; i < downstreamContexts.Count; i++) for (var i = 0; i < downstreamContexts.Count; i++)
{ {
if (downstreamContexts[i].IsError) if (downstreamContexts[i].IsError)
{ {
@ -54,13 +41,12 @@ namespace Ocelot.Middleware.Multiplexer
contentBuilder.Append("}"); 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<KeyValuePair<string, IEnumerable<string>>>());
} }
private static void MapAggregateError(DownstreamContext originalContext, List<DownstreamContext> downstreamContexts, int i) private static void MapAggregateError(DownstreamContext originalContext, List<DownstreamContext> downstreamContexts, int i)
@ -68,17 +54,5 @@ namespace Ocelot.Middleware.Multiplexer
originalContext.Errors.AddRange(downstreamContexts[i].Errors); originalContext.Errors.AddRange(downstreamContexts[i].Errors);
originalContext.DownstreamResponse = downstreamContexts[i].DownstreamResponse; originalContext.DownstreamResponse = downstreamContexts[i].DownstreamResponse;
} }
private void MapNotAggregate(DownstreamContext originalContext, List<DownstreamContext> 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;
}
} }
} }

View File

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

View File

@ -22,11 +22,6 @@
public static class OcelotMiddlewareExtensions public static class OcelotMiddlewareExtensions
{ {
/// <summary>
/// Registers the Ocelot default middlewares
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder) public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder)
{ {
await builder.UseOcelot(new OcelotPipelineConfiguration()); await builder.UseOcelot(new OcelotPipelineConfiguration());
@ -34,12 +29,6 @@
return builder; return builder;
} }
/// <summary>
/// Registers Ocelot with a combination of default middlewares and optional middlewares in the configuration
/// </summary>
/// <param name="builder"></param>
/// <param name="pipelineConfiguration"></param>
/// <returns></returns>
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
{ {
var configuration = await CreateConfiguration(builder); var configuration = await CreateConfiguration(builder);
@ -231,18 +220,6 @@
} }
} }
private static void UseIfNotNull(this IApplicationBuilder builder, Func<HttpContext, Func<Task>, Task> middleware)
{
if (middleware != null)
{
builder.Use(middleware);
}
}
/// <summary>
/// Configure a DiagnosticListener to listen for diagnostic events when the middleware starts and ends
/// </summary>
/// <param name="builder"></param>
private static void ConfigureDiagnosticListener(IApplicationBuilder builder) private static void ConfigureDiagnosticListener(IApplicationBuilder builder)
{ {
var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment)); var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment));

View File

@ -5,9 +5,7 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
@ -83,7 +81,6 @@
{ {
foreach (var header in request.Headers) foreach (var header in request.Headers)
{ {
//todo get rid of if..
if (IsSupportedHeader(header)) if (IsSupportedHeader(header))
{ {
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());

View File

@ -1,10 +1,6 @@
using Microsoft.AspNetCore.Http;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.Requester.QoS;
namespace Ocelot.Requester.Middleware namespace Ocelot.Requester.Middleware
{ {
@ -36,7 +32,7 @@ namespace Ocelot.Requester.Middleware
Logger.LogDebug("setting http response message"); Logger.LogDebug("setting http response message");
context.DownstreamResponse = response.Data; context.DownstreamResponse = new DownstreamResponse(response.Data);
} }
} }
} }

View File

@ -1,17 +1,14 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Ocelot.Headers; using Ocelot.Headers;
using Ocelot.Responses; using Ocelot.Middleware;
namespace Ocelot.Responder namespace Ocelot.Responder
{ {
using System.Collections.Generic;
/// <summary> /// <summary>
/// Cannot unit test things in this class due to methods not being implemented /// Cannot unit test things in this class due to methods not being implemented
/// on .net concretes used for testing /// on .net concretes used for testing
@ -25,7 +22,7 @@ namespace Ocelot.Responder
_removeOutputHeaders = removeOutputHeaders; _removeOutputHeaders = removeOutputHeaders;
} }
public async Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response) public async Task SetResponseOnHttpContext(HttpContext context, DownstreamResponse response)
{ {
_removeOutputHeaders.Remove(response.Headers); _removeOutputHeaders.Remove(response.Headers);
@ -36,12 +33,12 @@ namespace Ocelot.Responder
foreach (var httpResponseHeader in response.Content.Headers) foreach (var httpResponseHeader in response.Content.Headers)
{ {
AddHeaderIfDoesntExist(context, httpResponseHeader); AddHeaderIfDoesntExist(context, new Header(httpResponseHeader.Key, httpResponseHeader.Value));
} }
var content = await response.Content.ReadAsByteArrayAsync(); var content = await response.Content.ReadAsByteArrayAsync();
AddHeaderIfDoesntExist(context, new KeyValuePair<string, IEnumerable<string>>("Content-Length", new []{ content.Length.ToString() }) ); AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ content.Length.ToString() }) );
context.Response.OnStarting(state => context.Response.OnStarting(state =>
{ {
@ -70,11 +67,11 @@ namespace Ocelot.Responder
}, context); }, context);
} }
private static void AddHeaderIfDoesntExist(HttpContext context, KeyValuePair<string, IEnumerable<string>> httpResponseHeader) private static void AddHeaderIfDoesntExist(HttpContext context, Header httpResponseHeader)
{ {
if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key)) 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()));
} }
} }
} }

View File

@ -1,12 +1,13 @@
using System.Net.Http; using System.Threading.Tasks;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
namespace Ocelot.Responder namespace Ocelot.Responder
{ {
public interface IHttpResponder public interface IHttpResponder
{ {
Task SetResponseOnHttpContext(HttpContext context, HttpResponseMessage response); Task SetResponseOnHttpContext(HttpContext context, DownstreamResponse response);
void SetErrorResponseOnContext(HttpContext context, int statusCode); void SetErrorResponseOnContext(HttpContext context, int statusCode);
} }
} }

View File

@ -1,11 +1,9 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Errors; using Ocelot.Errors;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware; using Ocelot.Middleware;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.DownstreamRouteFinder.Middleware;
using Ocelot.Infrastructure.Extensions; using Ocelot.Infrastructure.Extensions;
namespace Ocelot.Responder.Middleware namespace Ocelot.Responder.Middleware

View File

@ -1,11 +1,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
@ -15,6 +21,7 @@ namespace Ocelot.AcceptanceTests
public class AggregateTests : IDisposable public class AggregateTests : IDisposable
{ {
private IWebHost _serviceOneBuilder; private IWebHost _serviceOneBuilder;
private IWebHost _serviceTwoBuilder;
private readonly Steps _steps; private readonly Steps _steps;
private string _downstreamPathOne; private string _downstreamPathOne;
private string _downstreamPathTwo; private string _downstreamPathTwo;
@ -24,6 +31,75 @@ namespace Ocelot.AcceptanceTests
_steps = new Steps(); _steps = new Steps();
} }
[Fact]
public void should_return_response_200_with_simple_url_user_defined_aggregate()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51885,
}
},
UpstreamPathTemplate = "/laura",
UpstreamHttpMethod = new List<string> { "Get" },
Key = "Laura"
},
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 51886,
}
},
UpstreamPathTemplate = "/tom",
UpstreamHttpMethod = new List<string> { "Get" },
Key = "Tom"
}
},
Aggregates = new List<FileAggregateReRoute>
{
new FileAggregateReRoute
{
UpstreamPathTemplate = "/",
UpstreamHost = "localhost",
ReRouteKeys = new List<string>
{
"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<FakeDefinedAggregator, FakeDepdendency>())
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.And(x => _steps.ThenTheResponseBodyShouldBe(expected))
.And(x => ThenTheDownstreamUrlPathShouldBe("/", "/"))
.BDDfy();
}
[Fact] [Fact]
public void should_return_response_200_with_simple_url() 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) private void GivenServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody)
{ {
_serviceOneBuilder = new WebHostBuilder() _serviceTwoBuilder = new WebHostBuilder()
.UseUrls(baseUrl) .UseUrls(baseUrl)
.UseKestrel() .UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(Directory.GetCurrentDirectory())
@ -351,7 +427,7 @@ namespace Ocelot.AcceptanceTests
}) })
.Build(); .Build();
_serviceOneBuilder.Start(); _serviceTwoBuilder.Start();
} }
internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPathOne, string expectedDownstreamPath) internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPathOne, string expectedDownstreamPath)
@ -363,7 +439,33 @@ namespace Ocelot.AcceptanceTests
public void Dispose() public void Dispose()
{ {
_serviceOneBuilder?.Dispose(); _serviceOneBuilder?.Dispose();
_serviceTwoBuilder?.Dispose();
_steps.Dispose(); _steps.Dispose();
} }
} }
public class FakeDepdendency
{
}
public class FakeDefinedAggregator : IDefinedAggregator
{
private readonly FakeDepdendency _dep;
public FakeDefinedAggregator(FakeDepdendency dep)
{
_dep = dep;
}
public async Task<DownstreamResponse> Aggregate(List<DownstreamResponse> 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);
}
}
} }

View File

@ -91,9 +91,9 @@ namespace Ocelot.AcceptanceTests
var butterflyUrl = "http://localhost:9618"; 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 => GivenServiceTwoIsRunning("http://localhost:51388", "/api/values", 200, "Hello from Tom", butterflyUrl))
.And(x => GivenFakeButterfly(butterflyUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl)) .And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values"))
@ -151,8 +151,8 @@ namespace Ocelot.AcceptanceTests
var butterflyUrl = "http://localhost:9618"; var butterflyUrl = "http://localhost:9618";
this.Given(x => GivenServiceOneIsRunning("http://localhost:51387", "/api/values", 200, "Hello from Laura", butterflyUrl)) this.Given(x => GivenFakeButterfly(butterflyUrl))
.And(x => GivenFakeButterfly(butterflyUrl)) .And(x => GivenServiceOneIsRunning("http://localhost:51387", "/api/values", 200, "Hello from Laura", butterflyUrl))
.And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl)) .And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values")) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values"))

View File

@ -26,6 +26,7 @@ using System.IO.Compression;
using System.Text; using System.Text;
using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests;
using Ocelot.Requester; using Ocelot.Requester;
using Ocelot.Middleware.Multiplexer;
namespace Ocelot.AcceptanceTests namespace Ocelot.AcceptanceTests
{ {
@ -178,12 +179,6 @@ namespace Ocelot.AcceptanceTests
_ocelotClient = _ocelotServer.CreateClient(); _ocelotClient = _ocelotServer.CreateClient();
} }
/*
public void GivenIHaveAddedXForwardedForHeader(string value)
{
_ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation("X-Forwarded-For", value);
}*/
public void GivenOcelotIsRunningWithMiddleareBeforePipeline<T>(Func<object, Task> callback) public void GivenOcelotIsRunningWithMiddleareBeforePipeline<T>(Func<object, Task> callback)
{ {
_webHostBuilder = new WebHostBuilder(); _webHostBuilder = new WebHostBuilder();
@ -246,6 +241,39 @@ namespace Ocelot.AcceptanceTests
_ocelotClient = _ocelotServer.CreateClient(); _ocelotClient = _ocelotServer.CreateClient();
} }
public void GivenOcelotIsRunningWithSpecficAggregatorsRegisteredInDi<TAggregator, TDepedency>()
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<TDepedency>();
s.AddOcelot()
.AddSingletonDefinedAggregator<TAggregator>();
})
.Configure(a =>
{
a.UseOcelot().Wait();
});
_ocelotServer = new TestServer(_webHostBuilder);
_ocelotClient = _ocelotServer.CreateClient();
}
public void GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi<TOne, TWo>() public void GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi<TOne, TWo>()
where TOne : DelegatingHandler where TOne : DelegatingHandler
where TWo : DelegatingHandler where TWo : DelegatingHandler

View File

@ -1,7 +1,4 @@
using Ocelot.Errors; namespace Ocelot.UnitTests.Cache
using Ocelot.Middleware;
namespace Ocelot.UnitTests.Cache
{ {
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@ -17,15 +14,17 @@ namespace Ocelot.UnitTests.Cache
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Builder; using Ocelot.Configuration.Builder;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Middleware.Multiplexer;
public class OutputCacheMiddlewareRealCacheTests public class OutputCacheMiddlewareRealCacheTests
{ {
private IOcelotCache<CachedResponse> _cacheManager; private readonly IOcelotCache<CachedResponse> _cacheManager;
private OutputCacheMiddleware _middleware; private readonly OutputCacheMiddleware _middleware;
private DownstreamContext _downstreamContext; private readonly DownstreamContext _downstreamContext;
private OcelotRequestDelegate _next; private OcelotRequestDelegate _next;
private Mock<IOcelotLoggerFactory> _loggerFactory; private Mock<IOcelotLoggerFactory> _loggerFactory;
private IRegionCreator _regionCreator; private IRegionCreator _regionCreator;
@ -56,14 +55,10 @@ namespace Ocelot.UnitTests.Cache
Headers = { ContentType = new MediaTypeHeaderValue("application/json")} Headers = { ContentType = new MediaTypeHeaderValue("application/json")}
}; };
var response = new HttpResponseMessage(HttpStatusCode.OK) var response = new DownstreamResponse(content, HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>());
{
Content = content,
};
this.Given(x => x.GivenResponseIsNotCached(response)) this.Given(x => x.GivenResponseIsNotCached(response))
.And(x => x.GivenTheDownstreamRouteIs()) .And(x => x.GivenTheDownstreamRouteIs())
.And(x => x.GivenThereAreNoErrors())
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheContentTypeHeaderIsCached()) .Then(x => x.ThenTheContentTypeHeaderIsCached())
.BDDfy(); .BDDfy();
@ -81,9 +76,9 @@ namespace Ocelot.UnitTests.Cache
header.First().ShouldBe("application/json"); header.First().ShouldBe("application/json");
} }
private void GivenResponseIsNotCached(HttpResponseMessage message) private void GivenResponseIsNotCached(DownstreamResponse response)
{ {
_downstreamContext.DownstreamResponse = message; _downstreamContext.DownstreamResponse = response;
} }
private void GivenTheDownstreamRouteIs() private void GivenTheDownstreamRouteIs()
@ -96,10 +91,5 @@ namespace Ocelot.UnitTests.Cache
_downstreamContext.DownstreamReRoute = reRoute; _downstreamContext.DownstreamReRoute = reRoute;
} }
private void GivenThereAreNoErrors()
{
_downstreamContext.Errors = new List<Error>();
}
} }
} }

View File

@ -1,8 +1,4 @@
using System.Net; namespace Ocelot.UnitTests.Cache
using Ocelot.Errors;
using Ocelot.Middleware;
namespace Ocelot.UnitTests.Cache
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -19,17 +15,20 @@ namespace Ocelot.UnitTests.Cache
using Ocelot.Logging; using Ocelot.Logging;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
using System.Net;
using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
public class OutputCacheMiddlewareTests public class OutputCacheMiddlewareTests
{ {
private readonly Mock<IOcelotCache<CachedResponse>> _cacheManager; private readonly Mock<IOcelotCache<CachedResponse>> _cacheManager;
private Mock<IOcelotLoggerFactory> _loggerFactory; private readonly Mock<IOcelotLoggerFactory> _loggerFactory;
private Mock<IOcelotLogger> _logger; private Mock<IOcelotLogger> _logger;
private OutputCacheMiddleware _middleware; private OutputCacheMiddleware _middleware;
private DownstreamContext _downstreamContext; private readonly DownstreamContext _downstreamContext;
private OcelotRequestDelegate _next; private readonly OcelotRequestDelegate _next;
private CachedResponse _response; private CachedResponse _response;
private IRegionCreator _regionCreator; private readonly IRegionCreator _regionCreator;
public OutputCacheMiddlewareTests() public OutputCacheMiddlewareTests()
{ {
@ -46,7 +45,17 @@ namespace Ocelot.UnitTests.Cache
[Fact] [Fact]
public void should_returned_cached_item_when_it_is_in_cache() public void should_returned_cached_item_when_it_is_in_cache()
{ {
var cachedResponse = new CachedResponse(HttpStatusCode.OK, new Dictionary<string, IEnumerable<string>>(), "", new Dictionary<string, IEnumerable<string>>()); var headers = new Dictionary<string, IEnumerable<string>>
{
{ "test", new List<string> { "test" } }
};
var contentHeaders = new Dictionary<string, IEnumerable<string>>
{
{ "content-type", new List<string> { "application/json" } }
};
var cachedResponse = new CachedResponse(HttpStatusCode.OK, headers, "", contentHeaders);
this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) this.Given(x => x.GivenThereIsACachedResponse(cachedResponse))
.And(x => x.GivenTheDownstreamRouteIs()) .And(x => x.GivenTheDownstreamRouteIs())
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())
@ -59,7 +68,6 @@ namespace Ocelot.UnitTests.Cache
{ {
this.Given(x => x.GivenResponseIsNotCached()) this.Given(x => x.GivenResponseIsNotCached())
.And(x => x.GivenTheDownstreamRouteIs()) .And(x => x.GivenTheDownstreamRouteIs())
.And(x => x.GivenThereAreNoErrors())
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheCacheAddIsCalledCorrectly()) .Then(x => x.ThenTheCacheAddIsCalledCorrectly())
.BDDfy(); .BDDfy();
@ -81,7 +89,7 @@ namespace Ocelot.UnitTests.Cache
private void GivenResponseIsNotCached() private void GivenResponseIsNotCached()
{ {
_downstreamContext.DownstreamResponse = new HttpResponseMessage(); _downstreamContext.DownstreamResponse = new DownstreamResponse(new HttpResponseMessage());
} }
private void GivenTheDownstreamRouteIs() private void GivenTheDownstreamRouteIs()
@ -101,11 +109,6 @@ namespace Ocelot.UnitTests.Cache
_downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0];
} }
private void GivenThereAreNoErrors()
{
_downstreamContext.Errors = new List<Error>();
}
private void ThenTheCacheGetIsCalledCorrectly() private void ThenTheCacheGetIsCalledCorrectly()
{ {
_cacheManager _cacheManager

View File

@ -134,7 +134,8 @@ namespace Ocelot.UnitTests.Configuration
{ {
"Tom", "Tom",
"Laura" "Laura"
} },
Aggregator = "asdf"
} }
} }
}; };

View File

@ -150,7 +150,6 @@ namespace Ocelot.UnitTests.Configuration
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_add_trace_id_header() public void should_add_trace_id_header()
{ {

View File

@ -18,6 +18,8 @@ using Shouldly;
using IdentityServer4.AccessTokenValidation; using IdentityServer4.AccessTokenValidation;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
using static Ocelot.UnitTests.Middleware.UserDefinedResponseAggregatorTests;
using Ocelot.Middleware.Multiplexer;
namespace Ocelot.UnitTests.DependencyInjection namespace Ocelot.UnitTests.DependencyInjection
{ {
@ -177,6 +179,40 @@ namespace Ocelot.UnitTests.DependencyInjection
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_add_singleton_defined_aggregators()
{
this.Given(x => WhenISetUpOcelotServices())
.When(x => AddSingletonDefinedAggregator<TestDefinedAggregator>())
.When(x => AddSingletonDefinedAggregator<TestDefinedAggregator>())
.Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificAggregators<TestDefinedAggregator, TestDefinedAggregator>())
.And(x => ThenTheAggregatorsAreSingleton<TestDefinedAggregator, TestDefinedAggregator>())
.BDDfy();
}
[Fact]
public void should_add_transient_defined_aggregators()
{
this.Given(x => WhenISetUpOcelotServices())
.When(x => AddTransientDefinedAggregator<TestDefinedAggregator>())
.When(x => AddTransientDefinedAggregator<TestDefinedAggregator>())
.Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificAggregators<TestDefinedAggregator, TestDefinedAggregator>())
.And(x => ThenTheAggregatorsAreTransient<TestDefinedAggregator, TestDefinedAggregator>())
.BDDfy();
}
private void AddSingletonDefinedAggregator<T>()
where T : class, IDefinedAggregator
{
_ocelotBuilder.AddSingletonDefinedAggregator<T>();
}
private void AddTransientDefinedAggregator<T>()
where T : class, IDefinedAggregator
{
_ocelotBuilder.AddTransientDefinedAggregator<T>();
}
private void ThenTheSpecificHandlersAreSingleton() private void ThenTheSpecificHandlersAreSingleton()
{ {
var handlers = _serviceProvider.GetServices<DelegatingHandler>().ToList(); var handlers = _serviceProvider.GetServices<DelegatingHandler>().ToList();
@ -258,6 +294,32 @@ namespace Ocelot.UnitTests.DependencyInjection
handlers[1].ShouldBeOfType<TWo>(); handlers[1].ShouldBeOfType<TWo>();
} }
private void ThenTheProviderIsRegisteredAndReturnsSpecificAggregators<TOne, TWo>()
{
_serviceProvider = _services.BuildServiceProvider();
var handlers = _serviceProvider.GetServices<IDefinedAggregator>().ToList();
handlers[0].ShouldBeOfType<TOne>();
handlers[1].ShouldBeOfType<TWo>();
}
private void ThenTheAggregatorsAreTransient<TOne, TWo>()
{
var aggregators = _serviceProvider.GetServices<IDefinedAggregator>().ToList();
var first = aggregators[0];
aggregators = _serviceProvider.GetServices<IDefinedAggregator>().ToList();
var second = aggregators[0];
first.ShouldNotBe(second);
}
private void ThenTheAggregatorsAreSingleton<TOne, TWo>()
{
var aggregators = _serviceProvider.GetServices<IDefinedAggregator>().ToList();
var first = aggregators[0];
aggregators = _serviceProvider.GetServices<IDefinedAggregator>().ToList();
var second = aggregators[0];
first.ShouldBe(second);
}
private void OnlyOneVersionOfEachCacheIsRegistered() private void OnlyOneVersionOfEachCacheIsRegistered()
{ {
var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache<CachedResponse>)); var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache<CachedResponse>));

View File

@ -1,7 +1,4 @@
using Ocelot.Configuration; namespace Ocelot.UnitTests.DownstreamUrlCreator
using Ocelot.Middleware;
namespace Ocelot.UnitTests.DownstreamUrlCreator
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -21,17 +18,19 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
using Shouldly; using Shouldly;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Request.Middleware; using Ocelot.Request.Middleware;
using Ocelot.Configuration;
using Ocelot.Middleware;
public class DownstreamUrlCreatorMiddlewareTests public class DownstreamUrlCreatorMiddlewareTests
{ {
private readonly Mock<IDownstreamPathPlaceholderReplacer> _downstreamUrlTemplateVariableReplacer; private readonly Mock<IDownstreamPathPlaceholderReplacer> _downstreamUrlTemplateVariableReplacer;
private OkResponse<DownstreamPath> _downstreamPath; private OkResponse<DownstreamPath> _downstreamPath;
private Mock<IOcelotLoggerFactory> _loggerFactory; private readonly Mock<IOcelotLoggerFactory> _loggerFactory;
private Mock<IOcelotLogger> _logger; private Mock<IOcelotLogger> _logger;
private DownstreamUrlCreatorMiddleware _middleware; private DownstreamUrlCreatorMiddleware _middleware;
private DownstreamContext _downstreamContext; private readonly DownstreamContext _downstreamContext;
private OcelotRequestDelegate _next; private readonly OcelotRequestDelegate _next;
private HttpRequestMessage _request; private readonly HttpRequestMessage _request;
public DownstreamUrlCreatorMiddlewareTests() public DownstreamUrlCreatorMiddlewareTests()
{ {
@ -212,7 +211,6 @@ namespace Ocelot.UnitTests.DownstreamUrlCreator
private void GivenTheDownstreamRequestUriIs(string uri) private void GivenTheDownstreamRequestUriIs(string uri)
{ {
_request.RequestUri = new Uri(uri); _request.RequestUri = new Uri(uri);
//todo - not sure if needed
_downstreamContext.DownstreamRequest = new DownstreamRequest(_request); _downstreamContext.DownstreamRequest = new DownstreamRequest(_request);
} }

View File

@ -1,29 +1,29 @@
using Xunit; using Xunit;
using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Ocelot.Headers; using Ocelot.Headers;
using System.Net.Http; using System.Net.Http;
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.Configuration.Creator;
using System.Linq; using System.Linq;
using Ocelot.Configuration.Creator;
using Moq; using Moq;
using Ocelot.Infrastructure.RequestData;
using Ocelot.Responses; using Ocelot.Responses;
using Ocelot.Infrastructure; using Ocelot.Infrastructure;
using Ocelot.UnitTests.Responder; using Ocelot.UnitTests.Responder;
using System;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
using Shouldly;
namespace Ocelot.UnitTests.Headers namespace Ocelot.UnitTests.Headers
{ {
public class AddHeadersToResponseTests public class AddHeadersToResponseTests
{ {
private IAddHeadersToResponse _adder; private readonly IAddHeadersToResponse _adder;
private Mock<IPlaceholders> _placeholders; private readonly Mock<IPlaceholders> _placeholders;
private HttpResponseMessage _response; private DownstreamResponse _response;
private List<AddHeader> _addHeaders; private List<AddHeader> _addHeaders;
private Mock<IOcelotLoggerFactory> _factory; private Mock<IOcelotLoggerFactory> _factory;
private Mock<IOcelotLogger> _logger; private readonly Mock<IOcelotLogger> _logger;
public AddHeadersToResponseTests() public AddHeadersToResponseTests()
{ {
@ -111,7 +111,7 @@ namespace Ocelot.UnitTests.Headers
private void ThenTheHeaderIsNotAdded(string key) 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) private void GivenTheTraceIdIs(string traceId)
@ -126,8 +126,8 @@ namespace Ocelot.UnitTests.Headers
private void ThenTheHeaderIsReturned(string key, string value) private void ThenTheHeaderIsReturned(string key, string value)
{ {
var values = _response.Headers.GetValues(key); var values = _response.Headers.First(x => x.Key == key);
values.First().ShouldBe(value); values.Values.First().ShouldBe(value);
} }
private void WhenIAdd() private void WhenIAdd()
@ -137,7 +137,7 @@ namespace Ocelot.UnitTests.Headers
private void GivenAResponseMessage() private void GivenAResponseMessage()
{ {
_response = new HttpResponseMessage(); _response = new DownstreamResponse(new HttpResponseMessage());
} }
private void GivenTheAddHeaders(List<AddHeader> addHeaders) private void GivenTheAddHeaders(List<AddHeader> addHeaders)

View File

@ -1,3 +1,5 @@
namespace Ocelot.UnitTests.Headers
{
using Xunit; using Xunit;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Headers.Middleware; using Ocelot.Headers.Middleware;
@ -12,22 +14,20 @@ using Ocelot.Headers;
using System.Net.Http; using System.Net.Http;
using Ocelot.Authorisation.Middleware; using Ocelot.Authorisation.Middleware;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
namespace Ocelot.UnitTests.Headers
{
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Request.Middleware; using Ocelot.Request.Middleware;
public class HttpHeadersTransformationMiddlewareTests public class HttpHeadersTransformationMiddlewareTests
{ {
private Mock<IHttpContextRequestHeaderReplacer> _preReplacer; private readonly Mock<IHttpContextRequestHeaderReplacer> _preReplacer;
private Mock<IHttpResponseHeaderReplacer> _postReplacer; private readonly Mock<IHttpResponseHeaderReplacer> _postReplacer;
private Mock<IOcelotLoggerFactory> _loggerFactory; private Mock<IOcelotLoggerFactory> _loggerFactory;
private Mock<IOcelotLogger> _logger; private Mock<IOcelotLogger> _logger;
private HttpHeadersTransformationMiddleware _middleware; private readonly HttpHeadersTransformationMiddleware _middleware;
private DownstreamContext _downstreamContext; private readonly DownstreamContext _downstreamContext;
private OcelotRequestDelegate _next; private OcelotRequestDelegate _next;
private Mock<IAddHeadersToResponse> _addHeaders; private readonly Mock<IAddHeadersToResponse> _addHeaders;
public HttpHeadersTransformationMiddlewareTests() public HttpHeadersTransformationMiddlewareTests()
{ {
@ -74,7 +74,7 @@ namespace Ocelot.UnitTests.Headers
private void GivenTheHttpResponseMessageIs() private void GivenTheHttpResponseMessageIs()
{ {
_downstreamContext.DownstreamResponse = new HttpResponseMessage(); _downstreamContext.DownstreamResponse = new DownstreamResponse(new HttpResponseMessage());
} }
private void GivenTheReRouteHasPreFindAndReplaceSetUp() private void GivenTheReRouteHasPreFindAndReplaceSetUp()
@ -98,7 +98,7 @@ namespace Ocelot.UnitTests.Headers
private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly() private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly()
{ {
_postReplacer.Verify(x => x.Replace(It.IsAny<HttpResponseMessage>(), It.IsAny<List<HeaderFindAndReplace>>(), It.IsAny<DownstreamRequest>()), Times.Once); _postReplacer.Verify(x => x.Replace(It.IsAny<DownstreamResponse>(), It.IsAny<List<HeaderFindAndReplace>>(), It.IsAny<DownstreamRequest>()), Times.Once);
} }
private void GivenTheFollowingRequest() private void GivenTheFollowingRequest()

View File

@ -7,19 +7,21 @@ using Ocelot.Configuration;
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.Responses; using Ocelot.Responses;
using System.Linq; using System.Linq;
using System.Net;
using Moq; using Moq;
using Ocelot.Infrastructure; using Ocelot.Infrastructure;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Infrastructure.RequestData; using Ocelot.Infrastructure.RequestData;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Request.Middleware; using Ocelot.Request.Middleware;
namespace Ocelot.UnitTests.Headers namespace Ocelot.UnitTests.Headers
{ {
public class HttpResponseHeaderReplacerTests public class HttpResponseHeaderReplacerTests
{ {
private HttpResponseMessage _response; private DownstreamResponse _response;
private Placeholders _placeholders; private Placeholders _placeholders;
private HttpResponseHeaderReplacer _replacer; private readonly HttpResponseHeaderReplacer _replacer;
private List<HeaderFindAndReplace> _headerFindAndReplaces; private List<HeaderFindAndReplace> _headerFindAndReplaces;
private Response _result; private Response _result;
private DownstreamRequest _request; private DownstreamRequest _request;
@ -37,11 +39,13 @@ namespace Ocelot.UnitTests.Headers
[Fact] [Fact]
public void should_replace_headers() public void should_replace_headers()
{ {
var response = new HttpResponseMessage(); var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted,
response.Headers.Add("test", "test"); new List<KeyValuePair<string, IEnumerable<string>>>()
{
new KeyValuePair<string, IEnumerable<string>>("test", new List<string> {"test"})
});
var fAndRs = new List<HeaderFindAndReplace>(); var fAndRs = new List<HeaderFindAndReplace> {new HeaderFindAndReplace("test", "test", "chiken", 0)};
fAndRs.Add(new HeaderFindAndReplace("test", "test", "chiken", 0));
this.Given(x => GivenTheHttpResponse(response)) this.Given(x => GivenTheHttpResponse(response))
.And(x => GivenTheFollowingHeaderReplacements(fAndRs)) .And(x => GivenTheFollowingHeaderReplacements(fAndRs))
@ -53,8 +57,11 @@ namespace Ocelot.UnitTests.Headers
[Fact] [Fact]
public void should_not_replace_headers() public void should_not_replace_headers()
{ {
var response = new HttpResponseMessage(); var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted,
response.Headers.Add("test", "test"); new List<KeyValuePair<string, IEnumerable<string>>>()
{
new KeyValuePair<string, IEnumerable<string>>("test", new List<string> {"test"})
});
var fAndRs = new List<HeaderFindAndReplace>(); var fAndRs = new List<HeaderFindAndReplace>();
@ -68,16 +75,21 @@ namespace Ocelot.UnitTests.Headers
[Fact] [Fact]
public void should_replace_downstream_base_url_with_ocelot_base_url() 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"); var request =
request.RequestUri = new System.Uri(downstreamUrl); new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)};
var response = new HttpResponseMessage(); var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted,
response.Headers.Add("Location", downstreamUrl); new List<KeyValuePair<string, IEnumerable<string>>>()
{
new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl})
});
var fAndRs = new List<HeaderFindAndReplace>(); var fAndRs = new List<HeaderFindAndReplace>
fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); {
new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)
};
this.Given(x => GivenTheHttpResponse(response)) this.Given(x => GivenTheHttpResponse(response))
.And(x => GivenTheRequestIs(request)) .And(x => GivenTheRequestIs(request))
@ -90,16 +102,21 @@ namespace Ocelot.UnitTests.Headers
[Fact] [Fact]
public void should_replace_downstream_base_url_with_ocelot_base_url_with_port() 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"); var request =
request.RequestUri = new System.Uri(downstreamUrl); new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)};
var response = new HttpResponseMessage(); var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted,
response.Headers.Add("Location", downstreamUrl); new List<KeyValuePair<string, IEnumerable<string>>>()
{
new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl})
});
var fAndRs = new List<HeaderFindAndReplace>(); var fAndRs = new List<HeaderFindAndReplace>
fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0)); {
new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0)
};
this.Given(x => GivenTheHttpResponse(response)) this.Given(x => GivenTheHttpResponse(response))
.And(x => GivenTheRequestIs(request)) .And(x => GivenTheRequestIs(request))
@ -112,16 +129,21 @@ namespace Ocelot.UnitTests.Headers
[Fact] [Fact]
public void should_replace_downstream_base_url_with_ocelot_base_url_and_path() 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"); var request =
request.RequestUri = new System.Uri(downstreamUrl); new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)};
var response = new HttpResponseMessage(); var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted,
response.Headers.Add("Location", downstreamUrl); new List<KeyValuePair<string, IEnumerable<string>>>()
{
new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl})
});
var fAndRs = new List<HeaderFindAndReplace>(); var fAndRs = new List<HeaderFindAndReplace>
fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); {
new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)
};
this.Given(x => GivenTheHttpResponse(response)) this.Given(x => GivenTheHttpResponse(response))
.And(x => GivenTheRequestIs(request)) .And(x => GivenTheRequestIs(request))
@ -134,16 +156,21 @@ namespace Ocelot.UnitTests.Headers
[Fact] [Fact]
public void should_replace_downstream_base_url_with_ocelot_base_url_with_path_and_port() 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"); var request =
request.RequestUri = new System.Uri(downstreamUrl); new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)};
var response = new HttpResponseMessage(); var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted,
response.Headers.Add("Location", downstreamUrl); new List<KeyValuePair<string, IEnumerable<string>>>()
{
new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl})
});
var fAndRs = new List<HeaderFindAndReplace>(); var fAndRs = new List<HeaderFindAndReplace>
fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0)); {
new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:123/", 0)
};
this.Given(x => GivenTheHttpResponse(response)) this.Given(x => GivenTheHttpResponse(response))
.And(x => GivenTheRequestIs(request)) .And(x => GivenTheRequestIs(request))
@ -156,16 +183,21 @@ namespace Ocelot.UnitTests.Headers
[Fact] [Fact]
public void should_replace_downstream_base_url_and_port_with_ocelot_base_url() 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"); var request =
request.RequestUri = new System.Uri(downstreamUrl); new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)};
var response = new HttpResponseMessage(); var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted,
response.Headers.Add("Location", downstreamUrl); new List<KeyValuePair<string, IEnumerable<string>>>()
{
new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl})
});
var fAndRs = new List<HeaderFindAndReplace>(); var fAndRs = new List<HeaderFindAndReplace>
fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)); {
new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com/", 0)
};
this.Given(x => GivenTheHttpResponse(response)) this.Given(x => GivenTheHttpResponse(response))
.And(x => GivenTheRequestIs(request)) .And(x => GivenTheRequestIs(request))
@ -178,16 +210,21 @@ namespace Ocelot.UnitTests.Headers
[Fact] [Fact]
public void should_replace_downstream_base_url_and_port_with_ocelot_base_url_and_port() 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"); var request =
request.RequestUri = new System.Uri(downstreamUrl); new HttpRequestMessage(HttpMethod.Get, "http://test.com") {RequestUri = new System.Uri(downstreamUrl)};
var response = new HttpResponseMessage(); var response = new DownstreamResponse(new StringContent(string.Empty), HttpStatusCode.Accepted,
response.Headers.Add("Location", downstreamUrl); new List<KeyValuePair<string, IEnumerable<string>>>()
{
new KeyValuePair<string, IEnumerable<string>>("Location", new List<string> {downstreamUrl})
});
var fAndRs = new List<HeaderFindAndReplace>(); var fAndRs = new List<HeaderFindAndReplace>
fAndRs.Add(new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:321/", 0)); {
new HeaderFindAndReplace("Location", "{DownstreamBaseUrl}", "http://ocelot.com:321/", 0)
};
this.Given(x => GivenTheHttpResponse(response)) this.Given(x => GivenTheHttpResponse(response))
.And(x => GivenTheRequestIs(request)) .And(x => GivenTheRequestIs(request))
@ -207,8 +244,8 @@ namespace Ocelot.UnitTests.Headers
_result.ShouldBeOfType<OkResponse>(); _result.ShouldBeOfType<OkResponse>();
foreach (var f in _headerFindAndReplaces) foreach (var f in _headerFindAndReplaces)
{ {
_response.Headers.TryGetValues(f.Key, out var values); var values = _response.Headers.First(x => x.Key == f.Key);
values.ToList()[f.Index].ShouldBe("test"); values.Values.ToList()[f.Index].ShouldBe("test");
} }
} }
@ -217,7 +254,7 @@ namespace Ocelot.UnitTests.Headers
_headerFindAndReplaces = fAndRs; _headerFindAndReplaces = fAndRs;
} }
private void GivenTheHttpResponse(HttpResponseMessage response) private void GivenTheHttpResponse(DownstreamResponse response)
{ {
_response = response; _response = response;
} }
@ -229,8 +266,8 @@ namespace Ocelot.UnitTests.Headers
private void ThenTheHeaderShouldBe(string key, string value) private void ThenTheHeaderShouldBe(string key, string value)
{ {
var test = _response.Headers.GetValues(key); var test = _response.Headers.First(x => x.Key == key);
test.First().ShouldBe(value); test.Values.First().ShouldBe(value);
} }
private void ThenTheHeadersAreReplaced() private void ThenTheHeadersAreReplaced()
@ -238,8 +275,8 @@ namespace Ocelot.UnitTests.Headers
_result.ShouldBeOfType<OkResponse>(); _result.ShouldBeOfType<OkResponse>();
foreach (var f in _headerFindAndReplaces) foreach (var f in _headerFindAndReplaces)
{ {
_response.Headers.TryGetValues(f.Key, out var values); var values = _response.Headers.First(x => x.Key == f.Key);
values.ToList()[f.Index].ShouldBe(f.Replace); values.Values.ToList()[f.Index].ShouldBe(f.Replace);
} }
} }
} }

View File

@ -1,5 +1,5 @@
using System.Net.Http; using System.Collections.Generic;
using System.Net.Http.Headers; using Ocelot.Middleware;
using Ocelot.Responses; using Ocelot.Responses;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
@ -9,7 +9,7 @@ namespace Ocelot.UnitTests.Headers
{ {
public class RemoveHeadersTests public class RemoveHeadersTests
{ {
private HttpResponseHeaders _headers; private List<Header> _headers;
private readonly Ocelot.Headers.RemoveOutputHeaders _removeOutputHeaders; private readonly Ocelot.Headers.RemoveOutputHeaders _removeOutputHeaders;
private Response _result; private Response _result;
@ -21,18 +21,18 @@ namespace Ocelot.UnitTests.Headers
[Fact] [Fact]
public void should_remove_header() public void should_remove_header()
{ {
var httpResponse = new HttpResponseMessage() var headers = new List<Header>()
{ {
Headers = {{ "Transfer-Encoding", "chunked"}} new Header("Transfer-Encoding", new List<string> {"chunked"})
}; };
this.Given(x => x.GivenAHttpContext(httpResponse.Headers)) this.Given(x => x.GivenAHttpContext(headers))
.When(x => x.WhenIRemoveTheHeaders()) .When(x => x.WhenIRemoveTheHeaders())
.Then(x => x.TheHeaderIsNoLongerInTheContext()) .Then(x => x.TheHeaderIsNoLongerInTheContext())
.BDDfy(); .BDDfy();
} }
private void GivenAHttpContext(HttpResponseHeaders headers) private void GivenAHttpContext(List<Header> headers)
{ {
_headers = headers; _headers = headers;
} }

View File

@ -9,9 +9,9 @@ namespace Ocelot.UnitTests.Infrastructure
{ {
public class HttpDataRepositoryTests public class HttpDataRepositoryTests
{ {
private HttpContext _httpContext; private readonly HttpContext _httpContext;
private IHttpContextAccessor _httpContextAccessor; private IHttpContextAccessor _httpContextAccessor;
private HttpDataRepository _httpDataRepository; private readonly HttpDataRepository _httpDataRepository;
private object _result; private object _result;
public HttpDataRepositoryTests() public HttpDataRepositoryTests()

View File

@ -51,7 +51,6 @@ namespace Ocelot.UnitTests.Infrastructure
result.Data.ShouldBe("http://www.bbc.co.uk/"); result.Data.ShouldBe("http://www.bbc.co.uk/");
} }
[Fact] [Fact]
public void should_return_downstream_base_url_when_port_is_80_or_443() public void should_return_downstream_base_url_when_port_is_80_or_443()
{ {

View File

@ -53,7 +53,6 @@ namespace Ocelot.UnitTests.Logging
[Fact] [Fact]
public void should_log_error() public void should_log_error()
{ {
_logger.LogError($"a message from {_a} to {_b}", _ex); _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); ThenLevelIsLogged("requestId: no request id, previousRequestId: no previous request id, message: a message from tom to laura, exception: System.Exception: oh no", LogLevel.Error);

View File

@ -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<IDefinedAggregator> _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<IDefinedAggregator, TestDefinedAggregator>();
var services = serviceCollection.BuildServiceProvider();
_provider = new ServiceLocatorDefinedAggregatorProvider(services);
}
private void ThenTheAggregatorIsReturned()
{
_aggregator.Data.ShouldNotBeNull();
_aggregator.Data.ShouldBeOfType<TestDefinedAggregator>();
_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<CouldNotFindAggregatorError>();
}
}
}

View File

@ -19,13 +19,16 @@ namespace Ocelot.UnitTests.Middleware
private readonly OcelotRequestDelegate _pipeline; private readonly OcelotRequestDelegate _pipeline;
private int _count; private int _count;
private Mock<IResponseAggregator> _aggregator; private Mock<IResponseAggregator> _aggregator;
private Mock<IResponseAggregatorFactory> _factory;
public MultiplexerTests() public MultiplexerTests()
{ {
_factory = new Mock<IResponseAggregatorFactory>();
_aggregator = new Mock<IResponseAggregator>(); _aggregator = new Mock<IResponseAggregator>();
_context = new DownstreamContext(new DefaultHttpContext()); _context = new DownstreamContext(new DefaultHttpContext());
_pipeline = context => Task.FromResult(_count++); _pipeline = context => Task.FromResult(_count++);
_multiplexer = new Multiplexer(_aggregator.Object); _factory.Setup(x => x.Get(It.IsAny<ReRoute>())).Returns(_aggregator.Object);
_multiplexer = new Multiplexer(_factory.Object);
} }
[Fact] [Fact]

View File

@ -62,7 +62,6 @@ namespace Ocelot.UnitTests.Middleware
{ {
_errors.Add(error); _errors.Add(error);
} }
} }
public class FakeMiddleware : OcelotMiddleware public class FakeMiddleware : OcelotMiddleware

View File

@ -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<IDefinedAggregatorProvider> _provider;
private ReRoute _reRoute;
private IResponseAggregator _aggregator;
public ResponseAggregatorFactoryTests()
{
_provider = new Mock<IDefinedAggregatorProvider>();
_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<SimpleJsonResponseAggregator>())
.BDDfy();
}
[Fact]
public void should_return_user_defined_aggregator()
{
var reRoute = new ReRouteBuilder()
.WithAggregator("doesntmatter")
.Build();
this.Given(_ => GivenReRoute(reRoute))
.When(_ => WhenIGet())
.Then(_ => ThenTheAggregatorIs<UserDefinedResponseAggregator>())
.BDDfy();
}
private void GivenReRoute(ReRoute reRoute)
{
_reRoute = reRoute;
}
private void WhenIGet()
{
_aggregator = _factory.Get(_reRoute);
}
private void ThenTheAggregatorIs<T>()
{
_aggregator.ShouldBeOfType<T>();
}
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
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;
@ -29,40 +30,6 @@ namespace Ocelot.UnitTests.Middleware
_aggregator = new SimpleJsonResponseAggregator(); _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<DownstreamReRoute>
{
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<Error> { new AnyError() },
DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, new Uri("http://www.bbc.co.uk"))),
};
var downstreamContexts = new List<DownstreamContext> { 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] [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()
{ {
@ -82,15 +49,13 @@ namespace Ocelot.UnitTests.Middleware
var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) var billDownstreamContext = new DownstreamContext(new DefaultHttpContext())
{ {
DownstreamResponse = DownstreamResponse = new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new EditableList<KeyValuePair<string, IEnumerable<string>>>()),
new HttpResponseMessage(HttpStatusCode.OK) {Content = new StringContent("Bill says hi")},
DownstreamReRoute = billDownstreamReRoute DownstreamReRoute = billDownstreamReRoute
}; };
var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext()) var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext())
{ {
DownstreamResponse = DownstreamResponse = new DownstreamResponse(new StringContent("George says hi"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>()),
new HttpResponseMessage(HttpStatusCode.OK) {Content = new StringContent("George says hi")},
DownstreamReRoute = georgeDownstreamReRoute DownstreamReRoute = georgeDownstreamReRoute
}; };
@ -126,19 +91,18 @@ namespace Ocelot.UnitTests.Middleware
var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) var billDownstreamContext = new DownstreamContext(new DefaultHttpContext())
{ {
DownstreamResponse = DownstreamResponse = new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>()),
new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Bill says hi") },
DownstreamReRoute = billDownstreamReRoute DownstreamReRoute = billDownstreamReRoute
}; };
var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext()) var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext())
{ {
DownstreamResponse = DownstreamResponse = new DownstreamResponse(new StringContent("Error"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>()),
new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Error") },
DownstreamReRoute = georgeDownstreamReRoute, DownstreamReRoute = georgeDownstreamReRoute,
Errors = new List<Error>() { new AnyError() }
}; };
georgeDownstreamContext.Errors.Add(new AnyError());
var downstreamContexts = new List<DownstreamContext> { billDownstreamContext, georgeDownstreamContext }; var downstreamContexts = new List<DownstreamContext> { billDownstreamContext, georgeDownstreamContext };
var expected = "Error"; var expected = "Error";

View File

@ -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<IDefinedAggregatorProvider> _provider;
private ReRoute _reRoute;
private List<DownstreamContext> _contexts;
private DownstreamContext _context;
public UserDefinedResponseAggregatorTests()
{
_provider = new Mock<IDefinedAggregatorProvider>();
_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<DownstreamContext>
{
new DownstreamContext(new DefaultHttpContext())
{
DownstreamResponse = new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>())
},
new DownstreamContext(new DefaultHttpContext())
{
DownstreamResponse = new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>())
}
};
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<DownstreamContext>
{
new DownstreamContext(new DefaultHttpContext())
{
DownstreamResponse = new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>())
},
new DownstreamContext(new DefaultHttpContext())
{
DownstreamResponse = new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>())
}
};
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<ReRoute>())).Returns(new ErrorResponse<IDefinedAggregator>(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<DownstreamContext> 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<ReRoute>())).Returns(new OkResponse<IDefinedAggregator>(aggregator));
}
private void GivenReRoute(ReRoute reRoute)
{
_reRoute = reRoute;
}
public class TestDefinedAggregator : IDefinedAggregator
{
public async Task<DownstreamResponse> Aggregate(List<DownstreamResponse> 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);
}
}
}
}

View File

@ -153,8 +153,6 @@ namespace Ocelot.UnitTests.QueryStrings
.AddQueryString(_downstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString, key, value); .AddQueryString(_downstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString, key, value);
_request.RequestUri = new Uri(newUri); _request.RequestUri = new Uri(newUri);
//todo - might not need to instanciate
_downstreamRequest = new DownstreamRequest(_request);
} }
private void GivenTheClaimParserReturns(Response<string> claimValue) private void GivenTheClaimParserReturns(Response<string> claimValue)

View File

@ -140,25 +140,28 @@ namespace Ocelot.UnitTests.Requester
.UseIISIntegration() .UseIISIntegration()
.Configure(app => .Configure(app =>
{ {
app.Run(async context => app.Run(context =>
{ {
if (_count == 0) if (_count == 0)
{ {
context.Response.Cookies.Append("test", "0"); context.Response.Cookies.Append("test", "0");
context.Response.StatusCode = 200; context.Response.StatusCode = 200;
_count++; _count++;
return; return Task.CompletedTask;
} }
if (_count == 1) if (_count == 1)
{ {
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))
{ {
context.Response.StatusCode = 200; context.Response.StatusCode = 200;
return; return Task.CompletedTask;
} }
context.Response.StatusCode = 500; context.Response.StatusCode = 500;
} }
return Task.CompletedTask;
}); });
}) })
.Build(); .Build();
@ -200,6 +203,7 @@ namespace Ocelot.UnitTests.Requester
.Setup(x => x.Get(It.IsAny<DownstreamReRoute>())) .Setup(x => x.Get(It.IsAny<DownstreamReRoute>()))
.Returns(new OkResponse<List<Func<DelegatingHandler>>>(handlers)); .Returns(new OkResponse<List<Func<DelegatingHandler>>>(handlers));
} }
private void GivenTheFactoryReturnsNothing() private void GivenTheFactoryReturnsNothing()
{ {
var handlers = new List<Func<DelegatingHandler>>(); var handlers = new List<Func<DelegatingHandler>>();

View File

@ -1,6 +1,3 @@
using Ocelot.Configuration.Builder;
using Ocelot.Middleware;
namespace Ocelot.UnitTests.Requester namespace Ocelot.UnitTests.Requester
{ {
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -14,11 +11,16 @@ namespace Ocelot.UnitTests.Requester
using Xunit; using Xunit;
using Shouldly; using Shouldly;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ocelot.Configuration.Builder;
using Ocelot.Middleware;
using System;
using System.Linq;
using Ocelot.UnitTests.Responder;
public class HttpRequesterMiddlewareTests public class HttpRequesterMiddlewareTests
{ {
private readonly Mock<IHttpRequester> _requester; private readonly Mock<IHttpRequester> _requester;
private OkResponse<HttpResponseMessage> _response; private Response<HttpResponseMessage> _response;
private Mock<IOcelotLoggerFactory> _loggerFactory; private Mock<IOcelotLoggerFactory> _loggerFactory;
private Mock<IOcelotLogger> _logger; private Mock<IOcelotLogger> _logger;
private readonly HttpRequesterMiddleware _middleware; private readonly HttpRequesterMiddleware _middleware;
@ -39,12 +41,27 @@ namespace Ocelot.UnitTests.Requester
public void should_call_services_correctly() public void should_call_services_correctly()
{ {
this.Given(x => x.GivenTheRequestIs()) this.Given(x => x.GivenTheRequestIs())
.And(x => x.GivenTheRequesterReturns(new HttpResponseMessage())) .And(x => x.GivenTheRequesterReturns(new OkResponse<HttpResponseMessage>(new HttpResponseMessage())))
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheScopedRepoIsCalledCorrectly()) .Then(x => x.ThenTheDownstreamResponseIsSet())
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_set_error()
{
this.Given(x => x.GivenTheRequestIs())
.And(x => x.GivenTheRequesterReturns(new ErrorResponse<HttpResponseMessage>(new AnyError())))
.When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenTheErrorIsSet())
.BDDfy();
}
private void ThenTheErrorIsSet()
{
_downstreamContext.IsError.ShouldBeTrue();
}
private void WhenICallTheMiddleware() private void WhenICallTheMiddleware()
{ {
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
@ -52,21 +69,34 @@ namespace Ocelot.UnitTests.Requester
private void GivenTheRequestIs() private void GivenTheRequestIs()
{ {
_downstreamContext = new DownstreamContext(new DefaultHttpContext()); _downstreamContext =
_downstreamContext.DownstreamReRoute = new DownstreamReRouteBuilder().Build(); new DownstreamContext(new DefaultHttpContext())
{
DownstreamReRoute = new DownstreamReRouteBuilder().Build()
};
} }
private void GivenTheRequesterReturns(HttpResponseMessage response) private void GivenTheRequesterReturns(Response<HttpResponseMessage> response)
{ {
_response = new OkResponse<HttpResponseMessage>(response); _response = response;
_requester _requester
.Setup(x => x.GetResponse(It.IsAny<DownstreamContext>())) .Setup(x => x.GetResponse(It.IsAny<DownstreamContext>()))
.ReturnsAsync(_response); .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);
} }
} }
} }

View File

@ -120,7 +120,7 @@ namespace Ocelot.UnitTests.Responder
// If this test fails then it's because the number of error codes has changed. // 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 // You should make the appropriate changes to the test cases here to ensure
// they cover all the error codes, and then modify this assertion. // 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) private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode)

View File

@ -1,10 +1,11 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Text;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Ocelot.Headers; using Ocelot.Headers;
using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
using Ocelot.Responder; using Ocelot.Responder;
using Shouldly; using Shouldly;
using Xunit; using Xunit;
@ -26,9 +27,13 @@ namespace Ocelot.UnitTests.Responder
public void should_remove_transfer_encoding_header() public void should_remove_transfer_encoding_header()
{ {
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
var httpResponseMessage = new HttpResponseMessage {Content = new StringContent("")}; var response = new DownstreamResponse(new StringContent(""), HttpStatusCode.OK,
httpResponseMessage.Headers.Add("Transfer-Encoding", "woop"); new List<KeyValuePair<string, IEnumerable<string>>>
_responder.SetResponseOnHttpContext(httpContext, httpResponseMessage).GetAwaiter().GetResult(); {
new KeyValuePair<string, IEnumerable<string>>("Transfer-Encoding", new List<string> {"woop"})
});
_responder.SetResponseOnHttpContext(httpContext, response).GetAwaiter().GetResult();
var header = httpContext.Response.Headers["Transfer-Encoding"]; var header = httpContext.Response.Headers["Transfer-Encoding"];
header.ShouldBeEmpty(); header.ShouldBeEmpty();
} }
@ -37,8 +42,10 @@ namespace Ocelot.UnitTests.Responder
public void should_have_content_length() public void should_have_content_length()
{ {
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
var httpResponseMessage = new HttpResponseMessage { Content = new StringContent("test") }; var response = new DownstreamResponse(new StringContent("test"), HttpStatusCode.OK,
_responder.SetResponseOnHttpContext(httpContext, httpResponseMessage).GetAwaiter().GetResult(); new List<KeyValuePair<string, IEnumerable<string>>>());
_responder.SetResponseOnHttpContext(httpContext, response).GetAwaiter().GetResult();
var header = httpContext.Response.Headers["Content-Length"]; var header = httpContext.Response.Headers["Content-Length"];
header.First().ShouldBe("4"); header.First().ShouldBe("4");
} }
@ -47,9 +54,13 @@ namespace Ocelot.UnitTests.Responder
public void should_add_header() public void should_add_header()
{ {
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
var httpResponseMessage = new HttpResponseMessage { Content = new StringContent("test") }; var response = new DownstreamResponse(new StringContent(""), HttpStatusCode.OK,
httpResponseMessage.Headers.Add("test", "test"); new List<KeyValuePair<string, IEnumerable<string>>>
_responder.SetResponseOnHttpContext(httpContext, httpResponseMessage).GetAwaiter().GetResult(); {
new KeyValuePair<string, IEnumerable<string>>("test", new List<string> {"test"})
});
_responder.SetResponseOnHttpContext(httpContext, response).GetAwaiter().GetResult();
var header = httpContext.Response.Headers["test"]; var header = httpContext.Response.Headers["test"];
header.First().ShouldBe("test"); header.First().ShouldBe("test");
} }

View File

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Middleware.Multiplexer;
namespace Ocelot.UnitTests.Responder namespace Ocelot.UnitTests.Responder
{ {
@ -40,8 +41,7 @@ namespace Ocelot.UnitTests.Responder
[Fact] [Fact]
public void should_not_return_any_errors() public void should_not_return_any_errors()
{ {
this.Given(x => x.GivenTheHttpResponseMessageIs(new HttpResponseMessage())) this.Given(x => x.GivenTheHttpResponseMessageIs(new DownstreamResponse(new HttpResponseMessage())))
.And(x => x.GivenThereAreNoPipelineErrors())
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenThereAreNoErrors()) .Then(x => x.ThenThereAreNoErrors())
.BDDfy(); .BDDfy();
@ -50,7 +50,7 @@ namespace Ocelot.UnitTests.Responder
[Fact] [Fact]
public void should_return_any_errors() 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"))) .And(x => x.GivenThereArePipelineErrors(new UnableToFindDownstreamRouteError("/path", "GET")))
.When(x => x.WhenICallTheMiddleware()) .When(x => x.WhenICallTheMiddleware())
.Then(x => x.ThenThereAreNoErrors()) .Then(x => x.ThenThereAreNoErrors())
@ -62,16 +62,11 @@ namespace Ocelot.UnitTests.Responder
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
} }
private void GivenTheHttpResponseMessageIs(HttpResponseMessage response) private void GivenTheHttpResponseMessageIs(DownstreamResponse response)
{ {
_downstreamContext.DownstreamResponse = response; _downstreamContext.DownstreamResponse = response;
} }
private void GivenThereAreNoPipelineErrors()
{
_downstreamContext.Errors = new List<Error>();
}
private void ThenThereAreNoErrors() private void ThenThereAreNoErrors()
{ {
//todo a better assert? //todo a better assert?
@ -79,7 +74,7 @@ namespace Ocelot.UnitTests.Responder
private void GivenThereArePipelineErrors(Error error) private void GivenThereArePipelineErrors(Error error)
{ {
_downstreamContext.Errors = new List<Error>(){error}; _downstreamContext.Errors.Add(error);
} }
} }
} }