mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 06:42:50 +08:00
Merge branch 'release-5.1.0'
This commit is contained in:
commit
600002dde4
31
.travis.yml
Normal file
31
.travis.yml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
language: csharp
|
||||||
|
os:
|
||||||
|
- osx
|
||||||
|
- linux
|
||||||
|
|
||||||
|
# Ubuntu 14.04
|
||||||
|
sudo: required
|
||||||
|
dist: trusty
|
||||||
|
|
||||||
|
# OS X 10.12
|
||||||
|
osx_image: xcode9.2
|
||||||
|
|
||||||
|
mono:
|
||||||
|
- 4.4.2
|
||||||
|
|
||||||
|
dotnet: 2.1.4
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- git fetch --unshallow # Travis always does a shallow clone, but GitVersion needs the full history including branches and tags
|
||||||
|
- git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
|
||||||
|
- git fetch origin
|
||||||
|
|
||||||
|
script:
|
||||||
|
- ./build.sh
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- .packages
|
||||||
|
- tools/Addins
|
||||||
|
- tools/gitreleasemanager
|
||||||
|
- tools/GitVersion.CommandLine
|
@ -1,6 +1,7 @@
|
|||||||
[<img src="http://threemammals.com/images/ocelot_logo.png">](http://threemammals.com/ocelot)
|
[<img src="http://threemammals.com/images/ocelot_logo.png">](http://threemammals.com/ocelot)
|
||||||
|
|
||||||
[](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb)
|
[](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb) Windows (AppVeyor)
|
||||||
|
[](https://travis-ci.org/ThreeMammals/Ocelot) Linux & OSX (Travis)
|
||||||
|
|
||||||
[](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb/history?branch=develop)
|
[](https://ci.appveyor.com/project/TomPallister/ocelot-fcfpb/history?branch=develop)
|
||||||
|
|
||||||
|
38
build.cake
38
build.cake
@ -17,7 +17,7 @@ var artifactsDir = Directory("artifacts");
|
|||||||
// unit testing
|
// unit testing
|
||||||
var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests");
|
var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests");
|
||||||
var unitTestAssemblies = @"./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj";
|
var unitTestAssemblies = @"./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj";
|
||||||
var minCodeCoverage = 76.4d;
|
var minCodeCoverage = 82d;
|
||||||
var coverallsRepoToken = "coveralls-repo-token-ocelot";
|
var coverallsRepoToken = "coveralls-repo-token-ocelot";
|
||||||
var coverallsRepo = "https://coveralls.io/github/TomPallister/Ocelot";
|
var coverallsRepo = "https://coveralls.io/github/TomPallister/Ocelot";
|
||||||
|
|
||||||
@ -189,6 +189,24 @@ Task("RunAcceptanceTests")
|
|||||||
.IsDependentOn("Compile")
|
.IsDependentOn("Compile")
|
||||||
.Does(() =>
|
.Does(() =>
|
||||||
{
|
{
|
||||||
|
if(TravisCI.IsRunningOnTravisCI)
|
||||||
|
{
|
||||||
|
Information(
|
||||||
|
@"Job:
|
||||||
|
JobId: {0}
|
||||||
|
JobNumber: {1}
|
||||||
|
OSName: {2}",
|
||||||
|
BuildSystem.TravisCI.Environment.Job.JobId,
|
||||||
|
BuildSystem.TravisCI.Environment.Job.JobNumber,
|
||||||
|
BuildSystem.TravisCI.Environment.Job.OSName
|
||||||
|
);
|
||||||
|
|
||||||
|
if(TravisCI.Environment.Job.OSName.ToLower() == "osx")
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var settings = new DotNetCoreTestSettings
|
var settings = new DotNetCoreTestSettings
|
||||||
{
|
{
|
||||||
Configuration = compileConfig,
|
Configuration = compileConfig,
|
||||||
@ -205,6 +223,24 @@ Task("RunIntegrationTests")
|
|||||||
.IsDependentOn("Compile")
|
.IsDependentOn("Compile")
|
||||||
.Does(() =>
|
.Does(() =>
|
||||||
{
|
{
|
||||||
|
if(TravisCI.IsRunningOnTravisCI)
|
||||||
|
{
|
||||||
|
Information(
|
||||||
|
@"Job:
|
||||||
|
JobId: {0}
|
||||||
|
JobNumber: {1}
|
||||||
|
OSName: {2}",
|
||||||
|
BuildSystem.TravisCI.Environment.Job.JobId,
|
||||||
|
BuildSystem.TravisCI.Environment.Job.JobNumber,
|
||||||
|
BuildSystem.TravisCI.Environment.Job.OSName
|
||||||
|
);
|
||||||
|
|
||||||
|
if(TravisCI.Environment.Job.OSName.ToLower() == "osx")
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var settings = new DotNetCoreTestSettings
|
var settings = new DotNetCoreTestSettings
|
||||||
{
|
{
|
||||||
Configuration = compileConfig,
|
Configuration = compileConfig,
|
||||||
|
@ -73,10 +73,17 @@ Follow Redirects / Use CookieContainer
|
|||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior:
|
Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior:
|
||||||
- _AllowAutoRedirect_ is a value that indicates whether the request should follow redirection responses.
|
|
||||||
Set it true if the request should automatically follow redirection responses from the Downstream resource; otherwise false. The default value is true.
|
1. AllowAutoRedirect is a value that indicates whether the request should follow redirection responses. Set it true if the request should automatically
|
||||||
- _UseCookieContainer_ is a value that indicates whether the handler uses the CookieContainer property to store server cookies and uses these cookies when sending requests.
|
follow redirection responses from the Downstream resource; otherwise false. The default value is false.
|
||||||
The default value is true.
|
2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer
|
||||||
|
property to store server cookies and uses these cookies when sending requests. The default value is false. Please note
|
||||||
|
that if you are using the CookieContainer Ocelot caches the HttpClient for each downstream service. This means that all requests
|
||||||
|
to that DownstreamService will share the same cookies. `Issue 274 <https://github.com/ThreeMammals/Ocelot/issues/274>`_ was created because a user
|
||||||
|
noticed that the cookies were being shared. I tried to think of a nice way to handle this but I think it is impossible. If you don't cache the clients
|
||||||
|
that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight
|
||||||
|
requests. This would also mean that subsequent requests dont use the cookies from the previous response! All in all not a great situation. I would avoid setting
|
||||||
|
UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request!
|
||||||
|
|
||||||
Multiple environments
|
Multiple environments
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -3,8 +3,32 @@ Headers Transformation
|
|||||||
|
|
||||||
Ocelot allows the user to transform headers pre and post downstream request. At the moment Ocelot only supports find and replace. This feature was requested `GitHub #190 <https://github.com/TomPallister/Ocelot/issues/190>`_ and I decided that it was going to be useful in various ways.
|
Ocelot allows the user to transform headers pre and post downstream request. At the moment Ocelot only supports find and replace. This feature was requested `GitHub #190 <https://github.com/TomPallister/Ocelot/issues/190>`_ and I decided that it was going to be useful in various ways.
|
||||||
|
|
||||||
Syntax
|
Add to Response
|
||||||
^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
This feature was requested in `GitHub #280 <https://github.com/TomPallister/Ocelot/issues/280>`_. I have only implemented
|
||||||
|
for responses but could add for requests in the future.
|
||||||
|
|
||||||
|
If you want to add a header to your downstream response please add the following to a ReRoute in configuration.json..
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
"DownstreamHeaderTransform": {
|
||||||
|
"Uncle": "Bob"
|
||||||
|
},
|
||||||
|
|
||||||
|
In the example above a header with the key Uncle and value Bob would be returned by Ocelot when requesting the specific ReRoute.
|
||||||
|
|
||||||
|
If you want to return the Butterfly APM trace id then do something like the following..
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
"DownstreamHeaderTransform": {
|
||||||
|
"AnyKey": "{TraceId}"
|
||||||
|
},
|
||||||
|
|
||||||
|
Find and Replace
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
In order to transform a header first we specify the header key and then the type of transform we want e.g.
|
In order to transform a header first we specify the header key and then the type of transform we want e.g.
|
||||||
|
|
||||||
@ -43,6 +67,7 @@ Ocelot allows placeholders that can be used in header transformation.
|
|||||||
|
|
||||||
{BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value.
|
{BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value.
|
||||||
{DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment.
|
{DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment.
|
||||||
|
{TraceId} - This will use the Butterfly APM Trace Id. This only works for DownstreamHeaderTransform at the moment.
|
||||||
|
|
||||||
Handling 302 Redirects
|
Handling 302 Redirects
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Ocelot.Configuration.Creator;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Builder
|
namespace Ocelot.Configuration.Builder
|
||||||
{
|
{
|
||||||
@ -37,11 +38,13 @@ namespace Ocelot.Configuration.Builder
|
|||||||
private string _upstreamHost;
|
private string _upstreamHost;
|
||||||
private string _key;
|
private string _key;
|
||||||
private List<string> _delegatingHandlers;
|
private List<string> _delegatingHandlers;
|
||||||
|
private List<AddHeader> _addHeadersToDownstream;
|
||||||
|
|
||||||
public DownstreamReRouteBuilder()
|
public DownstreamReRouteBuilder()
|
||||||
{
|
{
|
||||||
_downstreamAddresses = new List<DownstreamHostAndPort>();
|
_downstreamAddresses = new List<DownstreamHostAndPort>();
|
||||||
_delegatingHandlers = new List<string>();
|
_delegatingHandlers = new List<string>();
|
||||||
|
_addHeadersToDownstream = new List<AddHeader>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownstreamReRouteBuilder WithDownstreamAddresses(List<DownstreamHostAndPort> downstreamAddresses)
|
public DownstreamReRouteBuilder WithDownstreamAddresses(List<DownstreamHostAndPort> downstreamAddresses)
|
||||||
@ -224,6 +227,12 @@ namespace Ocelot.Configuration.Builder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DownstreamReRouteBuilder WithAddHeadersToDownstream(List<AddHeader> addHeadersToDownstream)
|
||||||
|
{
|
||||||
|
_addHeadersToDownstream = addHeadersToDownstream;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public DownstreamReRoute Build()
|
public DownstreamReRoute Build()
|
||||||
{
|
{
|
||||||
return new DownstreamReRoute(
|
return new DownstreamReRoute(
|
||||||
@ -253,7 +262,8 @@ namespace Ocelot.Configuration.Builder
|
|||||||
_authenticationOptions,
|
_authenticationOptions,
|
||||||
new PathTemplate(_downstreamPathTemplate),
|
new PathTemplate(_downstreamPathTemplate),
|
||||||
_reRouteKey,
|
_reRouteKey,
|
||||||
_delegatingHandlers);
|
_delegatingHandlers,
|
||||||
|
_addHeadersToDownstream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,6 +213,7 @@ namespace Ocelot.Configuration.Creator
|
|||||||
.WithDownstreamHeaderFindAndReplace(hAndRs.Downstream)
|
.WithDownstreamHeaderFindAndReplace(hAndRs.Downstream)
|
||||||
.WithUpstreamHost(fileReRoute.UpstreamHost)
|
.WithUpstreamHost(fileReRoute.UpstreamHost)
|
||||||
.WithDelegatingHandlers(fileReRoute.DelegatingHandlers)
|
.WithDelegatingHandlers(fileReRoute.DelegatingHandlers)
|
||||||
|
.WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
return reRoute;
|
return reRoute;
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
|
using Ocelot.Infrastructure;
|
||||||
|
using Ocelot.Logging;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
namespace Ocelot.Configuration.Creator
|
namespace Ocelot.Configuration.Creator
|
||||||
{
|
{
|
||||||
public class HeaderFindAndReplaceCreator : IHeaderFindAndReplaceCreator
|
public class HeaderFindAndReplaceCreator : IHeaderFindAndReplaceCreator
|
||||||
{
|
{
|
||||||
private IBaseUrlFinder _finder;
|
private IPlaceholders _placeholders;
|
||||||
private readonly Dictionary<string, Func<string>> _placeholders;
|
private IOcelotLogger _logger;
|
||||||
|
|
||||||
public HeaderFindAndReplaceCreator(IBaseUrlFinder finder)
|
public HeaderFindAndReplaceCreator(IPlaceholders placeholders, IOcelotLoggerFactory factory)
|
||||||
{
|
{
|
||||||
_finder = finder;
|
_logger = factory.CreateLogger<HeaderFindAndReplaceCreator>();;
|
||||||
_placeholders = new Dictionary<string, Func<string>>();
|
_placeholders = placeholders;
|
||||||
_placeholders.Add("{BaseUrl}", () => _finder.Find());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public HeaderTransformations Create(FileReRoute fileReRoute)
|
public HeaderTransformations Create(FileReRoute fileReRoute)
|
||||||
@ -24,21 +26,43 @@ namespace Ocelot.Configuration.Creator
|
|||||||
foreach(var input in fileReRoute.UpstreamHeaderTransform)
|
foreach(var input in fileReRoute.UpstreamHeaderTransform)
|
||||||
{
|
{
|
||||||
var hAndr = Map(input);
|
var hAndr = Map(input);
|
||||||
upstream.Add(hAndr);
|
if(!hAndr.IsError)
|
||||||
|
{
|
||||||
|
upstream.Add(hAndr.Data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogError($"Unable to add UpstreamHeaderTransform {input.Key}: {input.Value}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var downstream = new List<HeaderFindAndReplace>();
|
var downstream = new List<HeaderFindAndReplace>();
|
||||||
|
var addHeadersToDownstream = new List<AddHeader>();
|
||||||
|
|
||||||
foreach(var input in fileReRoute.DownstreamHeaderTransform)
|
foreach(var input in fileReRoute.DownstreamHeaderTransform)
|
||||||
{
|
{
|
||||||
var hAndr = Map(input);
|
if(input.Value.Contains(","))
|
||||||
downstream.Add(hAndr);
|
{
|
||||||
|
var hAndr = Map(input);
|
||||||
|
if(!hAndr.IsError)
|
||||||
|
{
|
||||||
|
downstream.Add(hAndr.Data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogError($"Unable to add DownstreamHeaderTransform {input.Key}: {input.Value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
addHeadersToDownstream.Add(new AddHeader(input.Key, input.Value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HeaderTransformations(upstream, downstream);
|
return new HeaderTransformations(upstream, downstream, addHeadersToDownstream);
|
||||||
}
|
}
|
||||||
|
|
||||||
private HeaderFindAndReplace Map(KeyValuePair<string,string> input)
|
private Response<HeaderFindAndReplace> Map(KeyValuePair<string,string> input)
|
||||||
{
|
{
|
||||||
var findAndReplace = input.Value.Split(",");
|
var findAndReplace = input.Value.Split(",");
|
||||||
|
|
||||||
@ -51,16 +75,19 @@ namespace Ocelot.Configuration.Creator
|
|||||||
|
|
||||||
var placeholder = replace.Substring(startOfPlaceholder, startOfPlaceholder + (endOfPlaceholder + 1));
|
var placeholder = replace.Substring(startOfPlaceholder, startOfPlaceholder + (endOfPlaceholder + 1));
|
||||||
|
|
||||||
if(_placeholders.ContainsKey(placeholder))
|
var value = _placeholders.Get(placeholder);
|
||||||
|
|
||||||
|
if(value.IsError)
|
||||||
{
|
{
|
||||||
var value = _placeholders[placeholder].Invoke();
|
return new ErrorResponse<HeaderFindAndReplace>(value.Errors);
|
||||||
replace = replace.Replace(placeholder, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
replace = replace.Replace(placeholder, value.Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
var hAndr = new HeaderFindAndReplace(input.Key, findAndReplace[0], replace, 0);
|
var hAndr = new HeaderFindAndReplace(input.Key, findAndReplace[0], replace, 0);
|
||||||
|
|
||||||
return hAndr;
|
return new OkResponse<HeaderFindAndReplace>(hAndr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,31 @@ namespace Ocelot.Configuration.Creator
|
|||||||
{
|
{
|
||||||
public class HeaderTransformations
|
public class HeaderTransformations
|
||||||
{
|
{
|
||||||
public HeaderTransformations(List<HeaderFindAndReplace> upstream, List<HeaderFindAndReplace> downstream)
|
public HeaderTransformations(
|
||||||
|
List<HeaderFindAndReplace> upstream,
|
||||||
|
List<HeaderFindAndReplace> downstream,
|
||||||
|
List<AddHeader> addHeader)
|
||||||
{
|
{
|
||||||
|
AddHeadersToDownstream = addHeader;
|
||||||
Upstream = upstream;
|
Upstream = upstream;
|
||||||
Downstream = downstream;
|
Downstream = downstream;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<HeaderFindAndReplace> Upstream {get;private set;}
|
public List<HeaderFindAndReplace> Upstream { get; private set; }
|
||||||
|
|
||||||
public List<HeaderFindAndReplace> Downstream {get;private set;}
|
public List<HeaderFindAndReplace> Downstream { get; private set; }
|
||||||
|
public List<AddHeader> AddHeadersToDownstream {get;private set;}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddHeader
|
||||||
|
{
|
||||||
|
public AddHeader(string key, string value)
|
||||||
|
{
|
||||||
|
this.Key = key;
|
||||||
|
this.Value = value;
|
||||||
|
|
||||||
|
}
|
||||||
|
public string Key { get; private set; }
|
||||||
|
public string Value { get; private set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Ocelot.Configuration.Creator;
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
|
|
||||||
namespace Ocelot.Configuration
|
namespace Ocelot.Configuration
|
||||||
@ -32,8 +33,10 @@ namespace Ocelot.Configuration
|
|||||||
AuthenticationOptions authenticationOptions,
|
AuthenticationOptions authenticationOptions,
|
||||||
PathTemplate downstreamPathTemplate,
|
PathTemplate downstreamPathTemplate,
|
||||||
string reRouteKey,
|
string reRouteKey,
|
||||||
List<string> delegatingHandlers)
|
List<string> delegatingHandlers,
|
||||||
|
List<AddHeader> addHeadersToDownstream)
|
||||||
{
|
{
|
||||||
|
AddHeadersToDownstream = addHeadersToDownstream;
|
||||||
DelegatingHandlers = delegatingHandlers;
|
DelegatingHandlers = delegatingHandlers;
|
||||||
Key = key;
|
Key = key;
|
||||||
UpstreamPathTemplate = upstreamPathTemplate;
|
UpstreamPathTemplate = upstreamPathTemplate;
|
||||||
@ -90,5 +93,6 @@ namespace Ocelot.Configuration
|
|||||||
public PathTemplate DownstreamPathTemplate { get; private set; }
|
public PathTemplate DownstreamPathTemplate { get; private set; }
|
||||||
public string ReRouteKey { get; private set; }
|
public string ReRouteKey { get; private set; }
|
||||||
public List<string> DelegatingHandlers {get;private set;}
|
public List<string> DelegatingHandlers {get;private set;}
|
||||||
|
public List<AddHeader> AddHeadersToDownstream {get;private set;}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
{
|
{
|
||||||
public FileHttpHandlerOptions()
|
public FileHttpHandlerOptions()
|
||||||
{
|
{
|
||||||
AllowAutoRedirect = true;
|
AllowAutoRedirect = false;
|
||||||
UseCookieContainer = true;
|
UseCookieContainer = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AllowAutoRedirect { get; set; }
|
public bool AllowAutoRedirect { get; set; }
|
||||||
|
@ -17,10 +17,16 @@ namespace Ocelot.Configuration.Repository
|
|||||||
private string _previousAsJson;
|
private string _previousAsJson;
|
||||||
private readonly Timer _timer;
|
private readonly Timer _timer;
|
||||||
private bool _polling;
|
private bool _polling;
|
||||||
|
private readonly IConsulPollerConfiguration _config;
|
||||||
|
|
||||||
public ConsulFileConfigurationPoller(IOcelotLoggerFactory factory, IFileConfigurationRepository repo, IFileConfigurationSetter setter)
|
public ConsulFileConfigurationPoller(
|
||||||
|
IOcelotLoggerFactory factory,
|
||||||
|
IFileConfigurationRepository repo,
|
||||||
|
IFileConfigurationSetter setter,
|
||||||
|
IConsulPollerConfiguration config)
|
||||||
{
|
{
|
||||||
_setter = setter;
|
_setter = setter;
|
||||||
|
_config = config;
|
||||||
_logger = factory.CreateLogger<ConsulFileConfigurationPoller>();
|
_logger = factory.CreateLogger<ConsulFileConfigurationPoller>();
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
_previousAsJson = "";
|
_previousAsJson = "";
|
||||||
@ -34,7 +40,7 @@ namespace Ocelot.Configuration.Repository
|
|||||||
_polling = true;
|
_polling = true;
|
||||||
await Poll();
|
await Poll();
|
||||||
_polling = false;
|
_polling = false;
|
||||||
}, null, 0, 1000);
|
}, null, 0, _config.Delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Poll()
|
private async Task Poll()
|
||||||
@ -63,8 +69,7 @@ namespace Ocelot.Configuration.Repository
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// We could do object comparison here but performance isnt really a problem. This might be an issue one day!
|
/// We could do object comparison here but performance isnt really a problem. This might be an issue one day!
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="config"></param>
|
/// <returns>hash of the config</returns>
|
||||||
/// <returns></returns>
|
|
||||||
private string ToJson(FileConfiguration config)
|
private string ToJson(FileConfiguration config)
|
||||||
{
|
{
|
||||||
var currentHash = JsonConvert.SerializeObject(config);
|
var currentHash = JsonConvert.SerializeObject(config);
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
namespace Ocelot.Configuration.Repository
|
||||||
|
{
|
||||||
|
public interface IConsulPollerConfiguration
|
||||||
|
{
|
||||||
|
int Delay { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
namespace Ocelot.Configuration.Repository
|
||||||
|
{
|
||||||
|
public class InMemoryConsulPollerConfiguration : IConsulPollerConfiguration
|
||||||
|
{
|
||||||
|
public int Delay => 1000;
|
||||||
|
}
|
||||||
|
}
|
@ -52,6 +52,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Butterfly.Client.AspNetCore;
|
using Butterfly.Client.AspNetCore;
|
||||||
|
using Ocelot.Infrastructure;
|
||||||
|
|
||||||
public class OcelotBuilder : IOcelotBuilder
|
public class OcelotBuilder : IOcelotBuilder
|
||||||
{
|
{
|
||||||
@ -148,6 +149,9 @@ namespace Ocelot.DependencyInjection
|
|||||||
|
|
||||||
// We add this here so that we can always inject something into the factory for IoC..
|
// We add this here so that we can always inject something into the factory for IoC..
|
||||||
_services.AddSingleton<IServiceTracer, FakeServiceTracer>();
|
_services.AddSingleton<IServiceTracer, FakeServiceTracer>();
|
||||||
|
_services.TryAddSingleton<IConsulPollerConfiguration, InMemoryConsulPollerConfiguration>();
|
||||||
|
_services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();
|
||||||
|
_services.TryAddSingleton<IPlaceholders, Placeholders>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IOcelotAdministrationBuilder AddAdministration(string path, string secret)
|
public IOcelotAdministrationBuilder AddAdministration(string path, string secret)
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
RateLimitOptionsError,
|
RateLimitOptionsError,
|
||||||
PathTemplateDoesntStartWithForwardSlash,
|
PathTemplateDoesntStartWithForwardSlash,
|
||||||
FileValidationFailedError,
|
FileValidationFailedError,
|
||||||
UnableToFindDelegatingHandlerProviderError
|
UnableToFindDelegatingHandlerProviderError,
|
||||||
|
CouldNotFindPlaceholderError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ using Ocelot.Configuration;
|
|||||||
using Ocelot.Infrastructure.Claims.Parser;
|
using Ocelot.Infrastructure.Claims.Parser;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using Ocelot.Configuration.Creator;
|
||||||
|
|
||||||
namespace Ocelot.Headers
|
namespace Ocelot.Headers
|
||||||
{
|
{
|
||||||
|
44
src/Ocelot/Headers/AddHeadersToResponse.cs
Normal file
44
src/Ocelot/Headers/AddHeadersToResponse.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
namespace Ocelot.Headers
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Ocelot.Configuration.Creator;
|
||||||
|
using Ocelot.Infrastructure;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
|
||||||
|
public class AddHeadersToResponse : IAddHeadersToResponse
|
||||||
|
{
|
||||||
|
private IPlaceholders _placeholders;
|
||||||
|
private IOcelotLogger _logger;
|
||||||
|
|
||||||
|
public AddHeadersToResponse(IPlaceholders placeholders, IOcelotLoggerFactory factory)
|
||||||
|
{
|
||||||
|
_logger = factory.CreateLogger<AddHeadersToResponse>();
|
||||||
|
_placeholders = placeholders;
|
||||||
|
}
|
||||||
|
public void Add(List<AddHeader> addHeaders, HttpResponseMessage response)
|
||||||
|
{
|
||||||
|
foreach(var add in addHeaders)
|
||||||
|
{
|
||||||
|
if(add.Value.StartsWith('{') && add.Value.EndsWith('}'))
|
||||||
|
{
|
||||||
|
var value = _placeholders.Get(add.Value);
|
||||||
|
|
||||||
|
if(value.IsError)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Unable to add header to response {add.Key}: {add.Value}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Headers.TryAddWithoutValidation(add.Key, value.Data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response.Headers.TryAddWithoutValidation(add.Key, add.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Infrastructure;
|
||||||
using Ocelot.Infrastructure.Extensions;
|
using Ocelot.Infrastructure.Extensions;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
|
||||||
@ -10,24 +11,14 @@ namespace Ocelot.Headers
|
|||||||
{
|
{
|
||||||
public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer
|
public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer
|
||||||
{
|
{
|
||||||
private Dictionary<string, Func<HttpRequestMessage, string>> _placeholders;
|
private IPlaceholders _placeholders;
|
||||||
|
|
||||||
public HttpResponseHeaderReplacer()
|
public HttpResponseHeaderReplacer(IPlaceholders placeholders)
|
||||||
{
|
{
|
||||||
_placeholders = new Dictionary<string, Func<HttpRequestMessage, string>>();
|
_placeholders = placeholders;
|
||||||
_placeholders.Add("{DownstreamBaseUrl}", x => {
|
|
||||||
var downstreamUrl = $"{x.RequestUri.Scheme}://{x.RequestUri.Host}";
|
|
||||||
|
|
||||||
if(x.RequestUri.Port != 80 && x.RequestUri.Port != 443)
|
|
||||||
{
|
|
||||||
downstreamUrl = $"{downstreamUrl}:{x.RequestUri.Port}";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"{downstreamUrl}/";
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs, HttpRequestMessage httpRequestMessage)
|
public Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs, HttpRequestMessage request)
|
||||||
{
|
{
|
||||||
foreach (var f in fAndRs)
|
foreach (var f in fAndRs)
|
||||||
{
|
{
|
||||||
@ -35,11 +26,13 @@ namespace Ocelot.Headers
|
|||||||
if(response.Headers.TryGetValues(f.Key, out var values))
|
if(response.Headers.TryGetValues(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...
|
||||||
if(_placeholders.TryGetValue(f.Find, out var replacePlaceholder))
|
var placeholderValue = _placeholders.Get(f.Find, request);
|
||||||
|
|
||||||
|
if(!placeholderValue.IsError)
|
||||||
{
|
{
|
||||||
//if it is we need to get the value of the placeholder
|
//if it is we need to get the value of the placeholder
|
||||||
var find = replacePlaceholder(httpRequestMessage);
|
//var find = replacePlaceholder(httpRequestMessage);
|
||||||
var replaced = values.ToList()[f.Index].Replace(find, f.Replace.LastCharAsForwardSlash());
|
var replaced = values.ToList()[f.Index].Replace(placeholderValue.Data, f.Replace.LastCharAsForwardSlash());
|
||||||
response.Headers.Remove(f.Key);
|
response.Headers.Remove(f.Key);
|
||||||
response.Headers.Add(f.Key, replaced);
|
response.Headers.Add(f.Key, replaced);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Configuration.Creator;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
|
||||||
public interface IAddHeadersToRequest
|
public interface IAddHeadersToRequest
|
||||||
|
11
src/Ocelot/Headers/IAddHeadersToResponse.cs
Normal file
11
src/Ocelot/Headers/IAddHeadersToResponse.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace Ocelot.Headers
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Ocelot.Configuration.Creator;
|
||||||
|
|
||||||
|
public interface IAddHeadersToResponse
|
||||||
|
{
|
||||||
|
void Add(List<AddHeader> addHeaders, HttpResponseMessage response);
|
||||||
|
}
|
||||||
|
}
|
@ -13,12 +13,15 @@ namespace Ocelot.Headers.Middleware
|
|||||||
private readonly IOcelotLogger _logger;
|
private readonly IOcelotLogger _logger;
|
||||||
private readonly IHttpContextRequestHeaderReplacer _preReplacer;
|
private readonly IHttpContextRequestHeaderReplacer _preReplacer;
|
||||||
private readonly IHttpResponseHeaderReplacer _postReplacer;
|
private readonly IHttpResponseHeaderReplacer _postReplacer;
|
||||||
|
private readonly IAddHeadersToResponse _addHeaders;
|
||||||
|
|
||||||
public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next,
|
public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next,
|
||||||
IOcelotLoggerFactory loggerFactory,
|
IOcelotLoggerFactory loggerFactory,
|
||||||
IHttpContextRequestHeaderReplacer preReplacer,
|
IHttpContextRequestHeaderReplacer preReplacer,
|
||||||
IHttpResponseHeaderReplacer postReplacer)
|
IHttpResponseHeaderReplacer postReplacer,
|
||||||
|
IAddHeadersToResponse addHeaders)
|
||||||
{
|
{
|
||||||
|
_addHeaders = addHeaders;
|
||||||
_next = next;
|
_next = next;
|
||||||
_postReplacer = postReplacer;
|
_postReplacer = postReplacer;
|
||||||
_preReplacer = preReplacer;
|
_preReplacer = preReplacer;
|
||||||
@ -37,6 +40,8 @@ namespace Ocelot.Headers.Middleware
|
|||||||
var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace;
|
var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace;
|
||||||
|
|
||||||
_postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest);
|
_postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest);
|
||||||
|
|
||||||
|
_addHeaders.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
src/Ocelot/Infrastructure/CouldNotFindPlaceholderError.cs
Normal file
12
src/Ocelot/Infrastructure/CouldNotFindPlaceholderError.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using Ocelot.Errors;
|
||||||
|
|
||||||
|
namespace Ocelot.Infrastructure
|
||||||
|
{
|
||||||
|
public class CouldNotFindPlaceholderError : Error
|
||||||
|
{
|
||||||
|
public CouldNotFindPlaceholderError(string placeholder)
|
||||||
|
: base($"Unable to find placeholder called {placeholder}", OcelotErrorCode.CouldNotFindPlaceholderError)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
src/Ocelot/Infrastructure/IPlaceholders.cs
Normal file
11
src/Ocelot/Infrastructure/IPlaceholders.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Infrastructure
|
||||||
|
{
|
||||||
|
public interface IPlaceholders
|
||||||
|
{
|
||||||
|
Response<string> Get(string key);
|
||||||
|
Response<string> Get(string key, HttpRequestMessage request);
|
||||||
|
}
|
||||||
|
}
|
70
src/Ocelot/Infrastructure/Placeholders.cs
Normal file
70
src/Ocelot/Infrastructure/Placeholders.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Infrastructure
|
||||||
|
{
|
||||||
|
public class Placeholders : IPlaceholders
|
||||||
|
{
|
||||||
|
private Dictionary<string, Func<Response<string>>> _placeholders;
|
||||||
|
private Dictionary<string, Func<HttpRequestMessage, string>> _requestPlaceholders;
|
||||||
|
private readonly IBaseUrlFinder _finder;
|
||||||
|
private readonly IRequestScopedDataRepository _repo;
|
||||||
|
|
||||||
|
public Placeholders(IBaseUrlFinder finder, IRequestScopedDataRepository repo)
|
||||||
|
{
|
||||||
|
_repo = repo;
|
||||||
|
_finder = finder;
|
||||||
|
_placeholders = new Dictionary<string, Func<Response<string>>>();
|
||||||
|
_placeholders.Add("{BaseUrl}", () => new OkResponse<string>(_finder.Find()));
|
||||||
|
_placeholders.Add("{TraceId}", () => {
|
||||||
|
var traceId = _repo.Get<string>("TraceId");
|
||||||
|
if(traceId.IsError)
|
||||||
|
{
|
||||||
|
return new ErrorResponse<string>(traceId.Errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OkResponse<string>(traceId.Data);
|
||||||
|
});
|
||||||
|
|
||||||
|
_requestPlaceholders = new Dictionary<string, Func<HttpRequestMessage, string>>();
|
||||||
|
_requestPlaceholders.Add("{DownstreamBaseUrl}", x => {
|
||||||
|
var downstreamUrl = $"{x.RequestUri.Scheme}://{x.RequestUri.Host}";
|
||||||
|
|
||||||
|
if(x.RequestUri.Port != 80 && x.RequestUri.Port != 443)
|
||||||
|
{
|
||||||
|
downstreamUrl = $"{downstreamUrl}:{x.RequestUri.Port}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{downstreamUrl}/";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response<string> Get(string key)
|
||||||
|
{
|
||||||
|
if(_placeholders.ContainsKey(key))
|
||||||
|
{
|
||||||
|
var response = _placeholders[key].Invoke();
|
||||||
|
if(!response.IsError)
|
||||||
|
{
|
||||||
|
return new OkResponse<string>(response.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ErrorResponse<string>(new CouldNotFindPlaceholderError(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response<string> Get(string key, HttpRequestMessage request)
|
||||||
|
{
|
||||||
|
if(_requestPlaceholders.ContainsKey(key))
|
||||||
|
{
|
||||||
|
return new OkResponse<string>(_requestPlaceholders[key].Invoke(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ErrorResponse<string>(new CouldNotFindPlaceholderError(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
namespace Ocelot.UnitTests
|
namespace Ocelot.Infrastructure
|
||||||
{
|
{
|
||||||
public class Wait
|
public class Wait
|
||||||
{
|
{
|
@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Ocelot.UnitTests
|
namespace Ocelot.Infrastructure
|
||||||
{
|
{
|
||||||
public class Waiter
|
public class Waiter
|
||||||
{
|
{
|
@ -3,6 +3,7 @@
|
|||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
<NETStandardImplicitPackageVersion>2.0.0</NETStandardImplicitPackageVersion>
|
||||||
|
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||||
<Description>This project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. We have been unable to find this in my current workplace without having to write our own Javascript middlewares to handle the IdentityServer reference tokens. We would rather use the IdentityServer code that already exists to do this. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features.</Description>
|
<Description>This project is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. In particular I want easy integration with IdentityServer reference and bearer tokens. We have been unable to find this in my current workplace without having to write our own Javascript middlewares to handle the IdentityServer reference tokens. We would rather use the IdentityServer code that already exists to do this. Ocelot is a bunch of middlewares in a specific order. Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is stored in a per request scoped repository and retrived as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features.</Description>
|
||||||
<AssemblyTitle>Ocelot</AssemblyTitle>
|
<AssemblyTitle>Ocelot</AssemblyTitle>
|
||||||
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
public class UnmappableRequestError : Error
|
public class UnmappableRequestError : Error
|
||||||
{
|
{
|
||||||
public UnmappableRequestError(Exception ex) : base($"Error when parsing incoming request, exception: {ex.Message}", OcelotErrorCode.UnmappableRequestError)
|
public UnmappableRequestError(Exception exception) : base($"Error when parsing incoming request, exception: {exception}", OcelotErrorCode.UnmappableRequestError)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
src/Ocelot/Requester/GlobalDelegatingHandler.cs
Normal file
14
src/Ocelot/Requester/GlobalDelegatingHandler.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Ocelot.Requester
|
||||||
|
{
|
||||||
|
public class GlobalDelegatingHandler
|
||||||
|
{
|
||||||
|
public GlobalDelegatingHandler(DelegatingHandler delegatingHandler)
|
||||||
|
{
|
||||||
|
DelegatingHandler = delegatingHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DelegatingHandler DelegatingHandler { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +1,61 @@
|
|||||||
using System.Linq;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
namespace Ocelot.Requester
|
namespace Ocelot.Requester
|
||||||
{
|
{
|
||||||
public class HttpClientBuilder : IHttpClientBuilder
|
public class HttpClientBuilder : IHttpClientBuilder
|
||||||
{
|
{
|
||||||
private readonly IDelegatingHandlerHandlerFactory _factory;
|
private readonly IDelegatingHandlerHandlerFactory _factory;
|
||||||
|
private readonly IHttpClientCache _cacheHandlers;
|
||||||
|
private readonly IOcelotLogger _logger;
|
||||||
|
private string _cacheKey;
|
||||||
|
private HttpClient _httpClient;
|
||||||
|
private IHttpClient _client;
|
||||||
|
private HttpClientHandler _httpclientHandler;
|
||||||
|
|
||||||
public HttpClientBuilder(IDelegatingHandlerHandlerFactory house)
|
public HttpClientBuilder(
|
||||||
|
IDelegatingHandlerHandlerFactory factory,
|
||||||
|
IHttpClientCache cacheHandlers,
|
||||||
|
IOcelotLogger logger)
|
||||||
{
|
{
|
||||||
_factory = house;
|
_factory = factory;
|
||||||
|
_cacheHandlers = cacheHandlers;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IHttpClient Create(DownstreamReRoute request)
|
public IHttpClient Create(DownstreamContext request)
|
||||||
{
|
{
|
||||||
var httpclientHandler = new HttpClientHandler { AllowAutoRedirect = request.HttpHandlerOptions.AllowAutoRedirect, UseCookies = request.HttpHandlerOptions.UseCookieContainer};
|
_cacheKey = GetCacheKey(request);
|
||||||
|
|
||||||
var client = new HttpClient(CreateHttpMessageHandler(httpclientHandler, request));
|
var httpClient = _cacheHandlers.Get(_cacheKey);
|
||||||
|
|
||||||
return new HttpClientWrapper(client);
|
if (httpClient != null)
|
||||||
|
{
|
||||||
|
return httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
_httpclientHandler = new HttpClientHandler
|
||||||
|
{
|
||||||
|
AllowAutoRedirect = request.DownstreamReRoute.HttpHandlerOptions.AllowAutoRedirect,
|
||||||
|
UseCookies = request.DownstreamReRoute.HttpHandlerOptions.UseCookieContainer,
|
||||||
|
CookieContainer = new CookieContainer()
|
||||||
|
};
|
||||||
|
|
||||||
|
_httpClient = new HttpClient(CreateHttpMessageHandler(_httpclientHandler, request.DownstreamReRoute));
|
||||||
|
|
||||||
|
_client = new HttpClientWrapper(_httpClient);
|
||||||
|
|
||||||
|
return _client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
_cacheHandlers.Set(_cacheKey, _client, TimeSpan.FromHours(24));
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler, DownstreamReRoute request)
|
private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler, DownstreamReRoute request)
|
||||||
@ -39,5 +75,12 @@ namespace Ocelot.Requester
|
|||||||
});
|
});
|
||||||
return httpMessageHandler;
|
return httpMessageHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetCacheKey(DownstreamContext request)
|
||||||
|
{
|
||||||
|
var baseUrl = $"{request.DownstreamRequest.RequestUri.Scheme}://{request.DownstreamRequest.RequestUri.Authority}{request.DownstreamRequest.RequestUri.AbsolutePath}";
|
||||||
|
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,28 +24,24 @@ namespace Ocelot.Requester
|
|||||||
_factory = house;
|
_factory = house;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Response<HttpResponseMessage>> GetResponse(DownstreamContext request)
|
public async Task<Response<HttpResponseMessage>> GetResponse(DownstreamContext context)
|
||||||
{
|
{
|
||||||
var builder = new HttpClientBuilder(_factory);
|
var builder = new HttpClientBuilder(_factory, _cacheHandlers, _logger);
|
||||||
|
|
||||||
var cacheKey = GetCacheKey(request);
|
var httpClient = builder.Create(context);
|
||||||
|
|
||||||
var httpClient = GetHttpClient(cacheKey, builder, request);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await httpClient.SendAsync(request.DownstreamRequest);
|
var response = await httpClient.SendAsync(context.DownstreamRequest);
|
||||||
return new OkResponse<HttpResponseMessage>(response);
|
return new OkResponse<HttpResponseMessage>(response);
|
||||||
}
|
}
|
||||||
catch (TimeoutRejectedException exception)
|
catch (TimeoutRejectedException exception)
|
||||||
{
|
{
|
||||||
return
|
return new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
|
||||||
new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
|
|
||||||
}
|
}
|
||||||
catch (BrokenCircuitException exception)
|
catch (BrokenCircuitException exception)
|
||||||
{
|
{
|
||||||
return
|
return new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
|
||||||
new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
|
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
@ -53,42 +49,8 @@ namespace Ocelot.Requester
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_cacheHandlers.Set(cacheKey, httpClient, TimeSpan.FromHours(24));
|
builder.Save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IHttpClient GetHttpClient(string cacheKey, IHttpClientBuilder builder, DownstreamContext request)
|
|
||||||
{
|
|
||||||
var httpClient = _cacheHandlers.Get(cacheKey);
|
|
||||||
|
|
||||||
if (httpClient == null)
|
|
||||||
{
|
|
||||||
httpClient = builder.Create(request.DownstreamReRoute);
|
|
||||||
}
|
|
||||||
|
|
||||||
return httpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetCacheKey(DownstreamContext request)
|
|
||||||
{
|
|
||||||
var baseUrl = $"{request.DownstreamRequest.RequestUri.Scheme}://{request.DownstreamRequest.RequestUri.Authority}";
|
|
||||||
|
|
||||||
return baseUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ReRouteDelegatingHandler<T> where T : DelegatingHandler
|
|
||||||
{
|
|
||||||
public T DelegatingHandler { get; private set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class GlobalDelegatingHandler
|
|
||||||
{
|
|
||||||
public GlobalDelegatingHandler(DelegatingHandler delegatingHandler)
|
|
||||||
{
|
|
||||||
DelegatingHandler = delegatingHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DelegatingHandler DelegatingHandler { get; private set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ namespace Ocelot.Requester
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class was made to make unit testing easier when HttpClient is used.
|
/// This class was made to make unit testing easier when HttpClient is used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class HttpClientWrapper : IHttpClient
|
public class HttpClientWrapper : IHttpClient
|
||||||
{
|
{
|
||||||
public HttpClient Client { get; }
|
public HttpClient Client { get; }
|
||||||
|
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
using System.Net.Http;
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Configuration;
|
|
||||||
|
|
||||||
namespace Ocelot.Requester
|
namespace Ocelot.Requester
|
||||||
{
|
{
|
||||||
public interface IHttpClientBuilder
|
public interface IHttpClientBuilder
|
||||||
{
|
{
|
||||||
/// <summary>
|
IHttpClient Create(DownstreamContext request);
|
||||||
/// Creates the <see cref="HttpClient"/>
|
void Save();
|
||||||
/// </summary>
|
|
||||||
/// <param name="request"></param>
|
|
||||||
IHttpClient Create(DownstreamReRoute request);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,6 @@ namespace Ocelot.Requester
|
|||||||
{
|
{
|
||||||
public interface IHttpRequester
|
public interface IHttpRequester
|
||||||
{
|
{
|
||||||
Task<Response<HttpResponseMessage>> GetResponse(DownstreamContext request);
|
Task<Response<HttpResponseMessage>> GetResponse(DownstreamContext context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,26 +5,37 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Butterfly.Client.Tracing;
|
using Butterfly.Client.Tracing;
|
||||||
using Butterfly.OpenTracing;
|
using Butterfly.OpenTracing;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
|
||||||
namespace Ocelot.Requester
|
namespace Ocelot.Requester
|
||||||
{
|
{
|
||||||
public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler
|
public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler
|
||||||
{
|
{
|
||||||
private readonly IServiceTracer _tracer;
|
private readonly IServiceTracer _tracer;
|
||||||
|
private readonly IRequestScopedDataRepository _repo;
|
||||||
private const string prefix_spanId = "ot-spanId";
|
private const string prefix_spanId = "ot-spanId";
|
||||||
|
|
||||||
public OcelotHttpTracingHandler(IServiceTracer tracer, HttpMessageHandler httpMessageHandler = null)
|
public OcelotHttpTracingHandler(
|
||||||
|
IServiceTracer tracer,
|
||||||
|
IRequestScopedDataRepository repo,
|
||||||
|
HttpMessageHandler httpMessageHandler = null)
|
||||||
{
|
{
|
||||||
|
_repo = repo;
|
||||||
_tracer = tracer ?? throw new ArgumentNullException(nameof(tracer));
|
_tracer = tracer ?? throw new ArgumentNullException(nameof(tracer));
|
||||||
InnerHandler = httpMessageHandler ?? new HttpClientHandler();
|
InnerHandler = httpMessageHandler ?? new HttpClientHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
protected override Task<HttpResponseMessage> SendAsync(
|
||||||
|
HttpRequestMessage request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return _tracer.ChildTraceAsync($"httpclient {request.Method}", DateTimeOffset.UtcNow, span => TracingSendAsync(span, request, cancellationToken));
|
return _tracer.ChildTraceAsync($"httpclient {request.Method}", DateTimeOffset.UtcNow, span => TracingSendAsync(span, request, cancellationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual async Task<HttpResponseMessage> TracingSendAsync(ISpan span, HttpRequestMessage request, CancellationToken cancellationToken)
|
protected virtual async Task<HttpResponseMessage> TracingSendAsync(
|
||||||
|
ISpan span,
|
||||||
|
HttpRequestMessage request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
IEnumerable<string> traceIdVals = null;
|
IEnumerable<string> traceIdVals = null;
|
||||||
if (request.Headers.TryGetValues(prefix_spanId, out traceIdVals))
|
if (request.Headers.TryGetValues(prefix_spanId, out traceIdVals))
|
||||||
@ -33,6 +44,8 @@ namespace Ocelot.Requester
|
|||||||
request.Headers.TryAddWithoutValidation(prefix_spanId, span.SpanContext.SpanId);
|
request.Headers.TryAddWithoutValidation(prefix_spanId, span.SpanContext.SpanId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_repo.Add("TraceId", span.SpanContext.TraceId);
|
||||||
|
|
||||||
span.Tags.Client().Component("HttpClient")
|
span.Tags.Client().Component("HttpClient")
|
||||||
.HttpMethod(request.Method.Method)
|
.HttpMethod(request.Method.Method)
|
||||||
.HttpUrl(request.RequestUri.OriginalString)
|
.HttpUrl(request.RequestUri.OriginalString)
|
||||||
|
10
src/Ocelot/Requester/ReRouteDelegatingHandler.cs
Normal file
10
src/Ocelot/Requester/ReRouteDelegatingHandler.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Ocelot.Requester
|
||||||
|
{
|
||||||
|
public class ReRouteDelegatingHandler<T>
|
||||||
|
where T : DelegatingHandler
|
||||||
|
{
|
||||||
|
public T DelegatingHandler { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@ namespace Ocelot.Requester
|
|||||||
public class RequestTimedOutError : Error
|
public class RequestTimedOutError : Error
|
||||||
{
|
{
|
||||||
public RequestTimedOutError(Exception exception)
|
public RequestTimedOutError(Exception exception)
|
||||||
: base($"Timeout making http request, exception: {exception.Message}", OcelotErrorCode.RequestTimedOutError)
|
: base($"Timeout making http request, exception: {exception}", OcelotErrorCode.RequestTimedOutError)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
using Butterfly.Client.Tracing;
|
using Butterfly.Client.Tracing;
|
||||||
using Butterfly.OpenTracing;
|
using Butterfly.OpenTracing;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
|
||||||
namespace Ocelot.Requester
|
namespace Ocelot.Requester
|
||||||
{
|
{
|
||||||
public class TracingHandlerFactory : ITracingHandlerFactory
|
public class TracingHandlerFactory : ITracingHandlerFactory
|
||||||
{
|
{
|
||||||
private readonly IServiceTracer _tracer;
|
private readonly IServiceTracer _tracer;
|
||||||
|
private readonly IRequestScopedDataRepository _repo;
|
||||||
|
|
||||||
public TracingHandlerFactory(IServiceTracer tracer)
|
public TracingHandlerFactory(
|
||||||
|
IServiceTracer tracer,
|
||||||
|
IRequestScopedDataRepository repo)
|
||||||
{
|
{
|
||||||
|
_repo = repo;
|
||||||
_tracer = tracer;
|
_tracer = tracer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITracingHandler Get()
|
public ITracingHandler Get()
|
||||||
{
|
{
|
||||||
return new OcelotHttpTracingHandler(_tracer);
|
return new OcelotHttpTracingHandler(_tracer, _repo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ namespace Ocelot.Requester
|
|||||||
public class UnableToCompleteRequestError : Error
|
public class UnableToCompleteRequestError : Error
|
||||||
{
|
{
|
||||||
public UnableToCompleteRequestError(Exception exception)
|
public UnableToCompleteRequestError(Exception exception)
|
||||||
: base($"Error making http request, exception: {exception.Message}", OcelotErrorCode.UnableToCompleteRequestError)
|
: base($"Error making http request, exception: {exception}", OcelotErrorCode.UnableToCompleteRequestError)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,12 @@ namespace Ocelot.Responder.Middleware
|
|||||||
{
|
{
|
||||||
var errors = context.Errors;
|
var errors = context.Errors;
|
||||||
_logger.LogError($"{errors.Count} pipeline errors found in {MiddlewareName}. Setting error response status code");
|
_logger.LogError($"{errors.Count} pipeline errors found in {MiddlewareName}. Setting error response status code");
|
||||||
|
|
||||||
|
foreach(var error in errors)
|
||||||
|
{
|
||||||
|
_logger.LogError(error.Message);
|
||||||
|
}
|
||||||
|
|
||||||
SetErrorResponse(context.HttpContext, errors);
|
SetErrorResponse(context.HttpContext, errors);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -70,7 +70,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 51888,
|
Port = 51388,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UpstreamPathTemplate = "/api002/values",
|
UpstreamPathTemplate = "/api002/values",
|
||||||
@ -92,7 +92,7 @@ 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 => GivenServiceOneIsRunning("http://localhost:51887", "/api/values", 200, "Hello from Laura", butterflyUrl))
|
||||||
.And(x => GivenServiceTwoIsRunning("http://localhost:51888", "/api/values", 200, "Hello from Tom", butterflyUrl))
|
.And(x => GivenServiceTwoIsRunning("http://localhost:51388", "/api/values", 200, "Hello from Tom", butterflyUrl))
|
||||||
.And(x => GivenFakeButterfly(butterflyUrl))
|
.And(x => GivenFakeButterfly(butterflyUrl))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl))
|
.And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl))
|
||||||
@ -109,6 +109,60 @@ namespace Ocelot.AcceptanceTests
|
|||||||
commandOnAllStateMachines.ShouldBeTrue();
|
commandOnAllStateMachines.ShouldBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_tracing_header()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/api/values",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = 51387,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UpstreamPathTemplate = "/api001/values",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
HttpHandlerOptions = new FileHttpHandlerOptions
|
||||||
|
{
|
||||||
|
UseTracing = true
|
||||||
|
},
|
||||||
|
QoSOptions = new FileQoSOptions
|
||||||
|
{
|
||||||
|
ExceptionsAllowedBeforeBreaking = 3,
|
||||||
|
DurationOfBreak = 10,
|
||||||
|
TimeoutValue = 5000
|
||||||
|
},
|
||||||
|
DownstreamHeaderTransform = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{"Trace-Id", "{TraceId}"},
|
||||||
|
{"Tom", "Laura"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var butterflyUrl = "http://localhost:9618";
|
||||||
|
|
||||||
|
this.Given(x => GivenServiceOneIsRunning("http://localhost:51387", "/api/values", 200, "Hello from Laura", butterflyUrl))
|
||||||
|
.And(x => GivenFakeButterfly(butterflyUrl))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl))
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
|
.And(x => _steps.ThenTheTraceHeaderIsSet("Trace-Id"))
|
||||||
|
.And(x => _steps.ThenTheResponseHeaderIs("Tom", "Laura"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody, string butterflyUrl)
|
private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody, string butterflyUrl)
|
||||||
{
|
{
|
||||||
_serviceOneBuilder = new WebHostBuilder()
|
_serviceOneBuilder = new WebHostBuilder()
|
||||||
|
@ -9,8 +9,10 @@ using Microsoft.AspNetCore.Hosting;
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
|
using Shouldly;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using static Ocelot.Infrastructure.Wait;
|
||||||
|
|
||||||
namespace Ocelot.AcceptanceTests
|
namespace Ocelot.AcceptanceTests
|
||||||
{
|
{
|
||||||
@ -261,21 +263,27 @@ namespace Ocelot.AcceptanceTests
|
|||||||
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status"))
|
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status"))
|
||||||
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
.And(x => GivenTheConsulConfigurationIs(secondConsulConfig))
|
.When(x => GivenTheConsulConfigurationIs(secondConsulConfig))
|
||||||
.And(x => GivenIWaitForTheConfigToReplicateToOcelot())
|
.Then(x => ThenTheConfigIsUpdatedInOcelot())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome"))
|
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenIWaitForTheConfigToReplicateToOcelot()
|
private void ThenTheConfigIsUpdatedInOcelot()
|
||||||
{
|
{
|
||||||
var stopWatch = Stopwatch.StartNew();
|
var result = WaitFor(20000).Until(() => {
|
||||||
while (stopWatch.ElapsedMilliseconds < 10000)
|
try
|
||||||
{
|
{
|
||||||
//do nothing!
|
_steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome");
|
||||||
}
|
_steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK);
|
||||||
|
_steps.ThenTheResponseBodyShouldBe("Hello from Laura");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
result.ShouldBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheConsulConfigurationIs(FileConfiguration config)
|
private void GivenTheConsulConfigurationIs(FileConfiguration config)
|
||||||
|
@ -16,6 +16,8 @@ namespace Ocelot.AcceptanceTests
|
|||||||
public class HeaderTests : IDisposable
|
public class HeaderTests : IDisposable
|
||||||
{
|
{
|
||||||
private IWebHost _builder;
|
private IWebHost _builder;
|
||||||
|
private string _cookieValue;
|
||||||
|
private int _count;
|
||||||
private readonly Steps _steps;
|
private readonly Steps _steps;
|
||||||
|
|
||||||
public HeaderTests()
|
public HeaderTests()
|
||||||
@ -184,6 +186,125 @@ namespace Ocelot.AcceptanceTests
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void request_should_reuse_cookies_with_cookie_container()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/sso/{everything}",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = 6774,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UpstreamPathTemplate = "/sso/{everything}",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get", "Post", "Options" },
|
||||||
|
HttpHandlerOptions = new FileHttpHandlerOptions
|
||||||
|
{
|
||||||
|
UseCookieContainer = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6774", "/sso/test", 200))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
|
||||||
|
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseHeaderIs("Set-Cookie", "test=0; path=/"))
|
||||||
|
.And(x => _steps.GivenIAddCookieToMyRequest("test=1; path=/"))
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void request_should_have_own_cookies_no_cookie_container()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/sso/{everything}",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = 6775,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UpstreamPathTemplate = "/sso/{everything}",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get", "Post", "Options" },
|
||||||
|
HttpHandlerOptions = new FileHttpHandlerOptions
|
||||||
|
{
|
||||||
|
UseCookieContainer = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6775", "/sso/test", 200))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
|
||||||
|
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseHeaderIs("Set-Cookie", "test=0; path=/"))
|
||||||
|
.And(x => _steps.GivenIAddCookieToMyRequest("test=1; path=/"))
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/sso/test"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode)
|
||||||
|
{
|
||||||
|
_builder = new WebHostBuilder()
|
||||||
|
.UseUrls(baseUrl)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UsePathBase(basePath);
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
if(_count == 0)
|
||||||
|
{
|
||||||
|
context.Response.Cookies.Append("test", "0");
|
||||||
|
_count++;
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue))
|
||||||
|
{
|
||||||
|
if(cookieValue == "0" || headerValue == "test=1; path=/")
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.StatusCode = 500;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builder.Start();
|
||||||
|
}
|
||||||
|
|
||||||
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey)
|
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey)
|
||||||
{
|
{
|
||||||
_builder = new WebHostBuilder()
|
_builder = new WebHostBuilder()
|
||||||
|
@ -31,7 +31,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
public void should_use_service_discovery_and_load_balance_request()
|
public void should_use_service_discovery_and_load_balance_request()
|
||||||
{
|
{
|
||||||
var downstreamServiceOneUrl = "http://localhost:50881";
|
var downstreamServiceOneUrl = "http://localhost:50881";
|
||||||
var downstreamServiceTwoUrl = "http://localhost:50882";
|
var downstreamServiceTwoUrl = "http://localhost:50892";
|
||||||
|
|
||||||
var configuration = new FileConfiguration
|
var configuration = new FileConfiguration
|
||||||
{
|
{
|
||||||
@ -54,7 +54,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 50882
|
Port = 50892
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,9 +50,4 @@
|
|||||||
<PackageReference Include="xunit" Version="2.3.1" />
|
<PackageReference Include="xunit" Version="2.3.1" />
|
||||||
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
|
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="Castle.Core">
|
|
||||||
<HintPath>..\..\..\..\Users\TGP\.nuget\packages\castle.core\4.2.1\lib\netstandard1.3\Castle.Core.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -41,7 +41,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
new FileHostAndPort
|
new FileHostAndPort
|
||||||
{
|
{
|
||||||
Host = "localhost",
|
Host = "localhost",
|
||||||
Port = 51872,
|
Port = 51892,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UpstreamPathTemplate = "/",
|
UpstreamPathTemplate = "/",
|
||||||
@ -56,7 +56,7 @@ namespace Ocelot.AcceptanceTests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51872", "Hello from Laura"))
|
this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51892", "Hello from Laura"))
|
||||||
.Given(x => _steps.GivenThereIsAConfiguration(configuration))
|
.Given(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.Given(x => _steps.GivenOcelotIsRunning())
|
.Given(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
|
@ -922,6 +922,55 @@ namespace Ocelot.AcceptanceTests
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_fix_issue_271()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/api/v1/{everything}",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
UpstreamPathTemplate = "/api/v1/{everything}",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get", "Put", "Post" },
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = 54879,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/connect/token",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
UpstreamPathTemplate = "/connect/token",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Post" },
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = 5001,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:54879/", "/api/v1/modules/Test", 200, "Hello from Laura"))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/v1/modules/Test"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody)
|
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody)
|
||||||
{
|
{
|
||||||
_builder = new WebHostBuilder()
|
_builder = new WebHostBuilder()
|
||||||
|
@ -274,6 +274,11 @@ namespace Ocelot.AcceptanceTests
|
|||||||
_ocelotClient = _ocelotServer.CreateClient();
|
_ocelotClient = _ocelotServer.CreateClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void GivenIAddCookieToMyRequest(string cookie)
|
||||||
|
{
|
||||||
|
_ocelotClient.DefaultRequestHeaders.Add("Set-Cookie", cookie);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
|
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -313,6 +318,12 @@ namespace Ocelot.AcceptanceTests
|
|||||||
header.First().ShouldBe(value);
|
header.First().ShouldBe(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ThenTheTraceHeaderIsSet(string key)
|
||||||
|
{
|
||||||
|
var header = _response.Headers.GetValues(key);
|
||||||
|
header.First().ShouldNotBeNullOrEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
public void GivenOcelotIsRunningUsingJsonSerializedCache()
|
public void GivenOcelotIsRunningUsingJsonSerializedCache()
|
||||||
{
|
{
|
||||||
_webHostBuilder = new WebHostBuilder();
|
_webHostBuilder = new WebHostBuilder();
|
||||||
|
@ -342,7 +342,9 @@ namespace Ocelot.IntegrationTests
|
|||||||
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
|
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
|
||||||
config.AddJsonFile("configuration.json");
|
config.AddJsonFile("configuration.json");
|
||||||
config.AddJsonFile("peers.json", optional: true, reloadOnChange: true);
|
config.AddJsonFile("peers.json", optional: true, reloadOnChange: true);
|
||||||
|
#pragma warning disable CS0618
|
||||||
config.AddOcelotBaseUrl(url);
|
config.AddOcelotBaseUrl(url);
|
||||||
|
#pragma warning restore CS0618
|
||||||
config.AddEnvironmentVariables();
|
config.AddEnvironmentVariables();
|
||||||
})
|
})
|
||||||
.ConfigureServices(x =>
|
.ConfigureServices(x =>
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Threading;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Configuration.Repository;
|
using Ocelot.Configuration.Repository;
|
||||||
using Ocelot.Configuration.Setter;
|
using Ocelot.Configuration.Setter;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.UnitTests.Responder;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using static Ocelot.UnitTests.Wait;
|
using static Ocelot.Infrastructure.Wait;
|
||||||
|
|
||||||
namespace Ocelot.UnitTests.Configuration
|
namespace Ocelot.UnitTests.Configuration
|
||||||
{
|
{
|
||||||
@ -21,6 +22,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
private Mock<IFileConfigurationRepository> _repo;
|
private Mock<IFileConfigurationRepository> _repo;
|
||||||
private Mock<IFileConfigurationSetter> _setter;
|
private Mock<IFileConfigurationSetter> _setter;
|
||||||
private FileConfiguration _fileConfig;
|
private FileConfiguration _fileConfig;
|
||||||
|
private Mock<IConsulPollerConfiguration> _config;
|
||||||
|
|
||||||
public ConsulFileConfigurationPollerTests()
|
public ConsulFileConfigurationPollerTests()
|
||||||
{
|
{
|
||||||
@ -30,8 +32,10 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
_repo = new Mock<IFileConfigurationRepository>();
|
_repo = new Mock<IFileConfigurationRepository>();
|
||||||
_setter = new Mock<IFileConfigurationSetter>();
|
_setter = new Mock<IFileConfigurationSetter>();
|
||||||
_fileConfig = new FileConfiguration();
|
_fileConfig = new FileConfiguration();
|
||||||
|
_config = new Mock<IConsulPollerConfiguration>();
|
||||||
_repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse<FileConfiguration>(_fileConfig));
|
_repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse<FileConfiguration>(_fileConfig));
|
||||||
_poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object);
|
_config.Setup(x => x.Delay).Returns(100);
|
||||||
|
_poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object, _config.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@ -42,7 +46,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_start()
|
public void should_start()
|
||||||
{
|
{
|
||||||
this.Given(x => ThenTheSetterIsCalled(_fileConfig))
|
this.Given(x => ThenTheSetterIsCalled(_fileConfig, 1))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,22 +69,82 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => WhenTheConfigIsChangedInConsul(newConfig))
|
this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 0))
|
||||||
.Then(x => ThenTheSetterIsCalled(newConfig))
|
.Then(x => ThenTheSetterIsCalled(newConfig, 1))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig)
|
[Fact]
|
||||||
|
public void should_not_poll_if_already_polling()
|
||||||
{
|
{
|
||||||
_repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse<FileConfiguration>(newConfig));
|
var newConfig = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 10))
|
||||||
|
.Then(x => ThenTheSetterIsCalled(newConfig, 1))
|
||||||
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheSetterIsCalled(FileConfiguration fileConfig)
|
[Fact]
|
||||||
|
public void should_do_nothing_if_call_to_consul_fails()
|
||||||
|
{
|
||||||
|
var newConfig = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamHostAndPorts = new List<FileHostAndPort>
|
||||||
|
{
|
||||||
|
new FileHostAndPort
|
||||||
|
{
|
||||||
|
Host = "test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => WhenConsulErrors())
|
||||||
|
.Then(x => ThenTheSetterIsCalled(newConfig, 0))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenConsulErrors()
|
||||||
|
{
|
||||||
|
_repo
|
||||||
|
.Setup(x => x.Get())
|
||||||
|
.ReturnsAsync(new ErrorResponse<FileConfiguration>(new AnyError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig, int delay)
|
||||||
|
{
|
||||||
|
_repo
|
||||||
|
.Setup(x => x.Get())
|
||||||
|
.Callback(() => Thread.Sleep(delay))
|
||||||
|
.ReturnsAsync(new OkResponse<FileConfiguration>(newConfig));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheSetterIsCalled(FileConfiguration fileConfig, int times)
|
||||||
{
|
{
|
||||||
var result = WaitFor(2000).Until(() => {
|
var result = WaitFor(2000).Until(() => {
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_setter.Verify(x => x.Set(fileConfig), Times.Once);
|
_setter.Verify(x => x.Set(fileConfig), Times.Exactly(times));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch(Exception)
|
catch(Exception)
|
||||||
|
@ -827,6 +827,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
result.DownstreamReRoute[0].ClaimsToQueries.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToQueries.Count);
|
result.DownstreamReRoute[0].ClaimsToQueries.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToQueries.Count);
|
||||||
result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey);
|
result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey);
|
||||||
result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers);
|
result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers);
|
||||||
|
result.DownstreamReRoute[0].AddHeadersToDownstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToDownstream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -909,7 +910,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
|
|
||||||
private void GivenTheHeaderFindAndReplaceCreatorReturns()
|
private void GivenTheHeaderFindAndReplaceCreatorReturns()
|
||||||
{
|
{
|
||||||
_headerFindAndReplaceCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(new HeaderTransformations(new List<HeaderFindAndReplace>(), new List<HeaderFindAndReplace>()));
|
_headerFindAndReplaceCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(new HeaderTransformations(new List<HeaderFindAndReplace>(), new List<HeaderFindAndReplace>(), new List<AddHeader>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheFollowingIsReturned(ServiceProviderConfiguration serviceProviderConfiguration)
|
private void GivenTheFollowingIsReturned(ServiceProviderConfiguration serviceProviderConfiguration)
|
||||||
|
@ -5,7 +5,11 @@ using Ocelot.Configuration;
|
|||||||
using Ocelot.Configuration.Builder;
|
using Ocelot.Configuration.Builder;
|
||||||
using Ocelot.Configuration.Creator;
|
using Ocelot.Configuration.Creator;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
|
using Ocelot.Infrastructure;
|
||||||
|
using Ocelot.Logging;
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.UnitTests.Responder;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@ -17,12 +21,17 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
private HeaderFindAndReplaceCreator _creator;
|
private HeaderFindAndReplaceCreator _creator;
|
||||||
private FileReRoute _reRoute;
|
private FileReRoute _reRoute;
|
||||||
private HeaderTransformations _result;
|
private HeaderTransformations _result;
|
||||||
private Mock<IBaseUrlFinder> _finder;
|
private Mock<IPlaceholders> _placeholders;
|
||||||
|
private Mock<IOcelotLoggerFactory> _factory;
|
||||||
|
private Mock<IOcelotLogger> _logger;
|
||||||
|
|
||||||
public HeaderFindAndReplaceCreatorTests()
|
public HeaderFindAndReplaceCreatorTests()
|
||||||
{
|
{
|
||||||
_finder = new Mock<IBaseUrlFinder>();
|
_logger = new Mock<IOcelotLogger>();
|
||||||
_creator = new HeaderFindAndReplaceCreator(_finder.Object);
|
_factory = new Mock<IOcelotLoggerFactory>();
|
||||||
|
_factory.Setup(x => x.CreateLogger<HeaderFindAndReplaceCreator>()).Returns(_logger.Object);
|
||||||
|
_placeholders = new Mock<IPlaceholders>();
|
||||||
|
_creator = new HeaderFindAndReplaceCreator(_placeholders.Object, _factory.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -84,6 +93,40 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_log_errors_and_not_add_headers()
|
||||||
|
{
|
||||||
|
var reRoute = new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamHeaderTransform = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{"Location", "http://www.bbc.co.uk/, {BaseUrl}"},
|
||||||
|
},
|
||||||
|
UpstreamHeaderTransform = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{"Location", "http://www.bbc.co.uk/, {BaseUrl}"},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var expected = new List<HeaderFindAndReplace>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => GivenTheReRoute(reRoute))
|
||||||
|
.And(x => GivenTheBaseUrlErrors())
|
||||||
|
.When(x => WhenICreate())
|
||||||
|
.Then(x => ThenTheFollowingDownstreamIsReturned(expected))
|
||||||
|
.And(x => ThenTheFollowingUpstreamIsReturned(expected))
|
||||||
|
.And(x => ThenTheLoggerIsCalledCorrectly("Unable to add DownstreamHeaderTransform Location: http://www.bbc.co.uk/, {BaseUrl}"))
|
||||||
|
.And(x => ThenTheLoggerIsCalledCorrectly("Unable to add UpstreamHeaderTransform Location: http://www.bbc.co.uk/, {BaseUrl}"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheLoggerIsCalledCorrectly(string message)
|
||||||
|
{
|
||||||
|
_logger.Verify(x => x.LogError(message), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_use_base_url_partial_placeholder()
|
public void should_use_base_url_partial_placeholder()
|
||||||
{
|
{
|
||||||
@ -107,9 +150,41 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_add_trace_id_header()
|
||||||
|
{
|
||||||
|
var reRoute = new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamHeaderTransform = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{"Trace-Id", "{TraceId}"},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var expected = new AddHeader("Trace-Id", "{TraceId}");
|
||||||
|
|
||||||
|
this.Given(x => GivenTheReRoute(reRoute))
|
||||||
|
.And(x => GivenTheBaseUrlIs("http://ocelot.com/"))
|
||||||
|
.When(x => WhenICreate())
|
||||||
|
.Then(x => ThenTheFollowingAddHeaderIsReturned(expected))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
private void GivenTheBaseUrlIs(string baseUrl)
|
private void GivenTheBaseUrlIs(string baseUrl)
|
||||||
{
|
{
|
||||||
_finder.Setup(x => x.Find()).Returns(baseUrl);
|
_placeholders.Setup(x => x.Get(It.IsAny<string>())).Returns(new OkResponse<string>(baseUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheBaseUrlErrors()
|
||||||
|
{
|
||||||
|
_placeholders.Setup(x => x.Get(It.IsAny<string>())).Returns(new ErrorResponse<string>(new AnyError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheFollowingAddHeaderIsReturned(AddHeader addHeader)
|
||||||
|
{
|
||||||
|
_result.AddHeadersToDownstream[0].Key.ShouldBe(addHeader.Key);
|
||||||
|
_result.AddHeadersToDownstream[0].Value.ShouldBe(addHeader.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheFollowingDownstreamIsReturned(List<HeaderFindAndReplace> downstream)
|
private void ThenTheFollowingDownstreamIsReturned(List<HeaderFindAndReplace> downstream)
|
||||||
|
@ -19,10 +19,10 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_create_options_with_useCookie_and_allowAutoRedirect_true_as_default()
|
public void should_create_options_with_useCookie_false_and_allowAutoRedirect_true_as_default()
|
||||||
{
|
{
|
||||||
var fileReRoute = new FileReRoute();
|
var fileReRoute = new FileReRoute();
|
||||||
var expectedOptions = new HttpHandlerOptions(true, true, false);
|
var expectedOptions = new HttpHandlerOptions(false, false, false);
|
||||||
|
|
||||||
this.Given(x => GivenTheFollowing(fileReRoute))
|
this.Given(x => GivenTheFollowing(fileReRoute))
|
||||||
.When(x => WhenICreateHttpHandlerOptions())
|
.When(x => WhenICreateHttpHandlerOptions())
|
||||||
@ -61,12 +61,12 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
_httpHandlerOptions = _httpHandlerOptionsCreator.Create(_fileReRoute);
|
_httpHandlerOptions = _httpHandlerOptionsCreator.Create(_fileReRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheFollowingOptionsReturned(HttpHandlerOptions options)
|
private void ThenTheFollowingOptionsReturned(HttpHandlerOptions expected)
|
||||||
{
|
{
|
||||||
_httpHandlerOptions.ShouldNotBeNull();
|
_httpHandlerOptions.ShouldNotBeNull();
|
||||||
_httpHandlerOptions.AllowAutoRedirect.ShouldBe(options.AllowAutoRedirect);
|
_httpHandlerOptions.AllowAutoRedirect.ShouldBe(expected.AllowAutoRedirect);
|
||||||
_httpHandlerOptions.UseCookieContainer.ShouldBe(options.UseCookieContainer);
|
_httpHandlerOptions.UseCookieContainer.ShouldBe(expected.UseCookieContainer);
|
||||||
_httpHandlerOptions.UseTracing.ShouldBe(options.UseTracing);
|
_httpHandlerOptions.UseTracing.ShouldBe(expected.UseTracing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,9 +22,10 @@ namespace Ocelot.UnitTests.DependencyInjection
|
|||||||
|
|
||||||
private void GivenTheBaseUrl(string baseUrl)
|
private void GivenTheBaseUrl(string baseUrl)
|
||||||
{
|
{
|
||||||
|
#pragma warning disable CS0618
|
||||||
var builder = new ConfigurationBuilder()
|
var builder = new ConfigurationBuilder()
|
||||||
.AddOcelotBaseUrl(baseUrl);
|
.AddOcelotBaseUrl(baseUrl);
|
||||||
|
#pragma warning restore CS0618
|
||||||
_configuration = builder.Build();
|
_configuration = builder.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,12 +223,14 @@ namespace Ocelot.UnitTests.DependencyInjection
|
|||||||
_ocelotBuilder.AddAdministration("/administration", options);
|
_ocelotBuilder.AddAdministration("/administration", options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddTransientGlobalDelegatingHandler<T>() where T : DelegatingHandler
|
private void AddTransientGlobalDelegatingHandler<T>()
|
||||||
|
where T : DelegatingHandler
|
||||||
{
|
{
|
||||||
_ocelotBuilder.AddTransientDelegatingHandler<T>(true);
|
_ocelotBuilder.AddTransientDelegatingHandler<T>(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddSpecificTransientDelegatingHandler<T>() where T : DelegatingHandler
|
private void AddSpecificTransientDelegatingHandler<T>()
|
||||||
|
where T : DelegatingHandler
|
||||||
{
|
{
|
||||||
_ocelotBuilder.AddTransientDelegatingHandler<T>();
|
_ocelotBuilder.AddTransientDelegatingHandler<T>();
|
||||||
}
|
}
|
||||||
@ -298,12 +300,14 @@ namespace Ocelot.UnitTests.DependencyInjection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddGlobalDelegatingHandler<T>() where T : DelegatingHandler
|
private void AddGlobalDelegatingHandler<T>()
|
||||||
|
where T : DelegatingHandler
|
||||||
{
|
{
|
||||||
_ocelotBuilder.AddSingletonDelegatingHandler<T>(true);
|
_ocelotBuilder.AddSingletonDelegatingHandler<T>(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddSpecificDelegatingHandler<T>() where T : DelegatingHandler
|
private void AddSpecificDelegatingHandler<T>()
|
||||||
|
where T : DelegatingHandler
|
||||||
{
|
{
|
||||||
_ocelotBuilder.AddSingletonDelegatingHandler<T>();
|
_ocelotBuilder.AddSingletonDelegatingHandler<T>();
|
||||||
}
|
}
|
||||||
|
148
test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs
Normal file
148
test/Ocelot.UnitTests/Headers/AddHeadersToResponseTests.cs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
using Xunit;
|
||||||
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Ocelot.Headers;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Ocelot.Configuration.Creator;
|
||||||
|
using System.Linq;
|
||||||
|
using Moq;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Infrastructure;
|
||||||
|
using Ocelot.UnitTests.Responder;
|
||||||
|
using System;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
|
||||||
|
namespace Ocelot.UnitTests.Headers
|
||||||
|
{
|
||||||
|
public class AddHeadersToResponseTests
|
||||||
|
{
|
||||||
|
private IAddHeadersToResponse _adder;
|
||||||
|
private Mock<IPlaceholders> _placeholders;
|
||||||
|
private HttpResponseMessage _response;
|
||||||
|
private List<AddHeader> _addHeaders;
|
||||||
|
private Mock<IOcelotLoggerFactory> _factory;
|
||||||
|
private Mock<IOcelotLogger> _logger;
|
||||||
|
|
||||||
|
public AddHeadersToResponseTests()
|
||||||
|
{
|
||||||
|
_factory = new Mock<IOcelotLoggerFactory>();
|
||||||
|
_logger = new Mock<IOcelotLogger>();
|
||||||
|
_factory.Setup(x => x.CreateLogger<AddHeadersToResponse>()).Returns(_logger.Object);
|
||||||
|
_placeholders = new Mock<IPlaceholders>();
|
||||||
|
_adder = new AddHeadersToResponse(_placeholders.Object, _factory.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_add_header()
|
||||||
|
{
|
||||||
|
var addHeaders = new List<AddHeader>
|
||||||
|
{
|
||||||
|
new AddHeader("Laura", "Tom")
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(_ => GivenAResponseMessage())
|
||||||
|
.And(_ => GivenTheAddHeaders(addHeaders))
|
||||||
|
.When(_ => WhenIAdd())
|
||||||
|
.And(_ => ThenTheHeaderIsReturned("Laura", "Tom"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_add_trace_id_placeholder()
|
||||||
|
{
|
||||||
|
var addHeaders = new List<AddHeader>
|
||||||
|
{
|
||||||
|
new AddHeader("Trace-Id", "{TraceId}")
|
||||||
|
};
|
||||||
|
|
||||||
|
var traceId = "123";
|
||||||
|
|
||||||
|
this.Given(_ => GivenAResponseMessage())
|
||||||
|
.And(_ => GivenTheTraceIdIs(traceId))
|
||||||
|
.And(_ => GivenTheAddHeaders(addHeaders))
|
||||||
|
.When(_ => WhenIAdd())
|
||||||
|
.Then(_ => ThenTheHeaderIsReturned("Trace-Id", traceId))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_add_trace_id_placeholder_and_normal()
|
||||||
|
{
|
||||||
|
var addHeaders = new List<AddHeader>
|
||||||
|
{
|
||||||
|
new AddHeader("Trace-Id", "{TraceId}"),
|
||||||
|
new AddHeader("Tom", "Laura")
|
||||||
|
};
|
||||||
|
|
||||||
|
var traceId = "123";
|
||||||
|
|
||||||
|
this.Given(_ => GivenAResponseMessage())
|
||||||
|
.And(_ => GivenTheTraceIdIs(traceId))
|
||||||
|
.And(_ => GivenTheAddHeaders(addHeaders))
|
||||||
|
.When(_ => WhenIAdd())
|
||||||
|
.Then(_ => ThenTheHeaderIsReturned("Trace-Id", traceId))
|
||||||
|
.Then(_ => ThenTheHeaderIsReturned("Tom", "Laura"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_do_nothing_and_log_error()
|
||||||
|
{
|
||||||
|
var addHeaders = new List<AddHeader>
|
||||||
|
{
|
||||||
|
new AddHeader("Trace-Id", "{TraceId}")
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(_ => GivenAResponseMessage())
|
||||||
|
.And(_ => GivenTheTraceIdErrors())
|
||||||
|
.And(_ => GivenTheAddHeaders(addHeaders))
|
||||||
|
.When(_ => WhenIAdd())
|
||||||
|
.Then(_ => ThenTheHeaderIsNotAdded("Trace-Id"))
|
||||||
|
.And(_ => ThenTheErrorIsLogged())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheErrorIsLogged()
|
||||||
|
{
|
||||||
|
_logger.Verify(x => x.LogError("Unable to add header to response Trace-Id: {TraceId}"), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheHeaderIsNotAdded(string key)
|
||||||
|
{
|
||||||
|
_response.Headers.TryGetValues(key, out var values).ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheTraceIdIs(string traceId)
|
||||||
|
{
|
||||||
|
_placeholders.Setup(x => x.Get("{TraceId}")).Returns(new OkResponse<string>(traceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheTraceIdErrors()
|
||||||
|
{
|
||||||
|
_placeholders.Setup(x => x.Get("{TraceId}")).Returns(new ErrorResponse<string>(new AnyError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheHeaderIsReturned(string key, string value)
|
||||||
|
{
|
||||||
|
var values = _response.Headers.GetValues(key);
|
||||||
|
values.First().ShouldBe(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenIAdd()
|
||||||
|
{
|
||||||
|
_adder.Add(_addHeaders, _response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenAResponseMessage()
|
||||||
|
{
|
||||||
|
_response = new HttpResponseMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheAddHeaders(List<AddHeader> addHeaders)
|
||||||
|
{
|
||||||
|
_addHeaders = addHeaders;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,7 @@ namespace Ocelot.UnitTests.Headers
|
|||||||
private HttpHeadersTransformationMiddleware _middleware;
|
private HttpHeadersTransformationMiddleware _middleware;
|
||||||
private DownstreamContext _downstreamContext;
|
private DownstreamContext _downstreamContext;
|
||||||
private OcelotRequestDelegate _next;
|
private OcelotRequestDelegate _next;
|
||||||
|
private Mock<IAddHeadersToResponse> _addHeaders;
|
||||||
|
|
||||||
public HttpHeadersTransformationMiddlewareTests()
|
public HttpHeadersTransformationMiddlewareTests()
|
||||||
{
|
{
|
||||||
@ -36,7 +37,8 @@ namespace Ocelot.UnitTests.Headers
|
|||||||
_logger = new Mock<IOcelotLogger>();
|
_logger = new Mock<IOcelotLogger>();
|
||||||
_loggerFactory.Setup(x => x.CreateLogger<AuthorisationMiddleware>()).Returns(_logger.Object);
|
_loggerFactory.Setup(x => x.CreateLogger<AuthorisationMiddleware>()).Returns(_logger.Object);
|
||||||
_next = context => Task.CompletedTask;
|
_next = context => Task.CompletedTask;
|
||||||
_middleware = new HttpHeadersTransformationMiddleware(_next, _loggerFactory.Object, _preReplacer.Object, _postReplacer.Object);
|
_addHeaders = new Mock<IAddHeadersToResponse>();
|
||||||
|
_middleware = new HttpHeadersTransformationMiddleware(_next, _loggerFactory.Object, _preReplacer.Object, _postReplacer.Object, _addHeaders.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -49,9 +51,16 @@ namespace Ocelot.UnitTests.Headers
|
|||||||
.When(x => WhenICallTheMiddleware())
|
.When(x => WhenICallTheMiddleware())
|
||||||
.Then(x => ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly())
|
.Then(x => ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly())
|
||||||
.And(x => ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly())
|
.And(x => ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly())
|
||||||
|
.And(x => ThenAddHeadersIsCalledCorrectly())
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ThenAddHeadersIsCalledCorrectly()
|
||||||
|
{
|
||||||
|
_addHeaders
|
||||||
|
.Verify(x => x.Add(_downstreamContext.DownstreamReRoute.AddHeadersToDownstream, _downstreamContext.DownstreamResponse), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
private void WhenICallTheMiddleware()
|
private void WhenICallTheMiddleware()
|
||||||
{
|
{
|
||||||
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
|
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
|
||||||
|
@ -7,20 +7,30 @@ using Ocelot.Configuration;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Moq;
|
||||||
|
using Ocelot.Infrastructure;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
|
||||||
namespace Ocelot.UnitTests.Headers
|
namespace Ocelot.UnitTests.Headers
|
||||||
{
|
{
|
||||||
public class HttpResponseHeaderReplacerTests
|
public class HttpResponseHeaderReplacerTests
|
||||||
{
|
{
|
||||||
private HttpResponseMessage _response;
|
private HttpResponseMessage _response;
|
||||||
|
private Placeholders _placeholders;
|
||||||
private HttpResponseHeaderReplacer _replacer;
|
private HttpResponseHeaderReplacer _replacer;
|
||||||
private List<HeaderFindAndReplace> _headerFindAndReplaces;
|
private List<HeaderFindAndReplace> _headerFindAndReplaces;
|
||||||
private Response _result;
|
private Response _result;
|
||||||
private HttpRequestMessage _request;
|
private HttpRequestMessage _request;
|
||||||
|
private Mock<IBaseUrlFinder> _finder;
|
||||||
|
private Mock<IRequestScopedDataRepository> _repo;
|
||||||
|
|
||||||
public HttpResponseHeaderReplacerTests()
|
public HttpResponseHeaderReplacerTests()
|
||||||
{
|
{
|
||||||
_replacer = new HttpResponseHeaderReplacer();
|
_repo = new Mock<IRequestScopedDataRepository>();
|
||||||
|
_finder = new Mock<IBaseUrlFinder>();
|
||||||
|
_placeholders = new Placeholders(_finder.Object, _repo.Object);
|
||||||
|
_replacer = new HttpResponseHeaderReplacer(_placeholders);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
79
test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs
Normal file
79
test/Ocelot.UnitTests/Infrastructure/PlaceholdersTests.cs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Moq;
|
||||||
|
using Ocelot.Infrastructure;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Ocelot.UnitTests.Infrastructure
|
||||||
|
{
|
||||||
|
public class PlaceholdersTests
|
||||||
|
{
|
||||||
|
private IPlaceholders _placeholders;
|
||||||
|
private Mock<IBaseUrlFinder> _finder;
|
||||||
|
private Mock<IRequestScopedDataRepository> _repo;
|
||||||
|
|
||||||
|
public PlaceholdersTests()
|
||||||
|
{
|
||||||
|
_repo = new Mock<IRequestScopedDataRepository>();
|
||||||
|
_finder = new Mock<IBaseUrlFinder>();
|
||||||
|
_placeholders = new Placeholders(_finder.Object, _repo.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_base_url()
|
||||||
|
{
|
||||||
|
var baseUrl = "http://www.bbc.co.uk";
|
||||||
|
_finder.Setup(x => x.Find()).Returns(baseUrl);
|
||||||
|
var result = _placeholders.Get("{BaseUrl}");
|
||||||
|
result.Data.ShouldBe(baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_key_does_not_exist()
|
||||||
|
{
|
||||||
|
var result = _placeholders.Get("{Test}");
|
||||||
|
result.IsError.ShouldBeTrue();
|
||||||
|
result.Errors[0].Message.ShouldBe("Unable to find placeholder called {Test}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_downstream_base_url_when_port_is_not_80_or_443()
|
||||||
|
{
|
||||||
|
var request = new HttpRequestMessage();
|
||||||
|
request.RequestUri = new Uri("http://www.bbc.co.uk");
|
||||||
|
var result = _placeholders.Get("{DownstreamBaseUrl}", request);
|
||||||
|
result.Data.ShouldBe("http://www.bbc.co.uk/");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_downstream_base_url_when_port_is_80_or_443()
|
||||||
|
{
|
||||||
|
var request = new HttpRequestMessage();
|
||||||
|
request.RequestUri = new Uri("http://www.bbc.co.uk:123");
|
||||||
|
var result = _placeholders.Get("{DownstreamBaseUrl}", request);
|
||||||
|
result.Data.ShouldBe("http://www.bbc.co.uk:123/");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_key_does_not_exist_for_http_request_message()
|
||||||
|
{
|
||||||
|
var result = _placeholders.Get("{Test}", new System.Net.Http.HttpRequestMessage());
|
||||||
|
result.IsError.ShouldBeTrue();
|
||||||
|
result.Errors[0].Message.ShouldBe("Unable to find placeholder called {Test}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_return_trace_id()
|
||||||
|
{
|
||||||
|
var traceId = "123";
|
||||||
|
_repo.Setup(x => x.Get<string>("TraceId")).Returns(new OkResponse<string>(traceId));
|
||||||
|
var result = _placeholders.Get("{TraceId}");
|
||||||
|
result.Data.ShouldBe(traceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Moq;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Configuration.Builder;
|
||||||
|
using Ocelot.DependencyInjection;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Ocelot.Middleware.Multiplexer;
|
||||||
|
using Ocelot.Middleware.Pipeline;
|
||||||
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Ocelot.UnitTests.Middleware
|
||||||
|
{
|
||||||
|
public class OcelotPipelineExtensionsTests
|
||||||
|
{
|
||||||
|
private OcelotPipelineBuilder _builder;
|
||||||
|
private OcelotRequestDelegate _handlers;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_set_up_pipeline()
|
||||||
|
{
|
||||||
|
this.Given(_ => GivenTheDepedenciesAreSetUp())
|
||||||
|
.When(_ => WhenIBuild())
|
||||||
|
.Then(_ => ThenThePipelineIsBuilt())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenThePipelineIsBuilt()
|
||||||
|
{
|
||||||
|
_handlers.ShouldNotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenIBuild()
|
||||||
|
{
|
||||||
|
_handlers = _builder.BuildOcelotPipeline(new OcelotPipelineConfiguration());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheDepedenciesAreSetUp()
|
||||||
|
{
|
||||||
|
IConfigurationBuilder test = new ConfigurationBuilder();
|
||||||
|
var root = test.Build();
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddSingleton<IConfiguration>(root);
|
||||||
|
services.AddOcelot();
|
||||||
|
var provider = services.BuildServiceProvider();
|
||||||
|
_builder = new OcelotPipelineBuilder(provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -163,7 +163,6 @@
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_not_add_content_headers()
|
public void should_not_add_content_headers()
|
||||||
{
|
{
|
||||||
|
@ -1,9 +1,19 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Configuration.Builder;
|
using Ocelot.Configuration.Builder;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
using Ocelot.Requester;
|
using Ocelot.Requester;
|
||||||
using Ocelot.Responses;
|
using Ocelot.Responses;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
@ -12,25 +22,37 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Ocelot.UnitTests.Requester
|
namespace Ocelot.UnitTests.Requester
|
||||||
{
|
{
|
||||||
public class HttpClientBuilderTests
|
public class HttpClientBuilderTests : IDisposable
|
||||||
{
|
{
|
||||||
private readonly HttpClientBuilder _builder;
|
private readonly HttpClientBuilder _builder;
|
||||||
private readonly Mock<IDelegatingHandlerHandlerFactory> _factory;
|
private readonly Mock<IDelegatingHandlerHandlerFactory> _factory;
|
||||||
private IHttpClient _httpClient;
|
private IHttpClient _httpClient;
|
||||||
private HttpResponseMessage _response;
|
private HttpResponseMessage _response;
|
||||||
private DownstreamReRoute _request;
|
private DownstreamContext _context;
|
||||||
|
private readonly Mock<IHttpClientCache> _cacheHandlers;
|
||||||
|
private Mock<IOcelotLogger> _logger;
|
||||||
|
private int _count;
|
||||||
|
private IWebHost _host;
|
||||||
|
|
||||||
public HttpClientBuilderTests()
|
public HttpClientBuilderTests()
|
||||||
{
|
{
|
||||||
|
_cacheHandlers = new Mock<IHttpClientCache>();
|
||||||
|
_logger = new Mock<IOcelotLogger>();
|
||||||
_factory = new Mock<IDelegatingHandlerHandlerFactory>();
|
_factory = new Mock<IDelegatingHandlerHandlerFactory>();
|
||||||
_builder = new HttpClientBuilder(_factory.Object);
|
_builder = new HttpClientBuilder(_factory.Object, _cacheHandlers.Object, _logger.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_build_http_client()
|
public void should_build_http_client()
|
||||||
{
|
{
|
||||||
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
|
.WithIsQos(false)
|
||||||
|
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false))
|
||||||
|
.WithReRouteKey("")
|
||||||
|
.Build();
|
||||||
|
|
||||||
this.Given(x => GivenTheFactoryReturns())
|
this.Given(x => GivenTheFactoryReturns())
|
||||||
.And(x => GivenARequest())
|
.And(x => GivenARequest(reRoute))
|
||||||
.When(x => WhenIBuild())
|
.When(x => WhenIBuild())
|
||||||
.Then(x => ThenTheHttpClientShouldNotBeNull())
|
.Then(x => ThenTheHttpClientShouldNotBeNull())
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
@ -39,6 +61,12 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void should_call_delegating_handlers_in_order()
|
public void should_call_delegating_handlers_in_order()
|
||||||
{
|
{
|
||||||
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
|
.WithIsQos(false)
|
||||||
|
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false))
|
||||||
|
.WithReRouteKey("")
|
||||||
|
.Build();
|
||||||
|
|
||||||
var fakeOne = new FakeDelegatingHandler();
|
var fakeOne = new FakeDelegatingHandler();
|
||||||
var fakeTwo = new FakeDelegatingHandler();
|
var fakeTwo = new FakeDelegatingHandler();
|
||||||
|
|
||||||
@ -49,7 +77,7 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.Given(x => GivenTheFactoryReturns(handlers))
|
this.Given(x => GivenTheFactoryReturns(handlers))
|
||||||
.And(x => GivenARequest())
|
.And(x => GivenARequest(reRoute))
|
||||||
.And(x => WhenIBuild())
|
.And(x => WhenIBuild())
|
||||||
.When(x => WhenICallTheClient())
|
.When(x => WhenICallTheClient())
|
||||||
.Then(x => ThenTheFakeAreHandledInOrder(fakeOne, fakeTwo))
|
.Then(x => ThenTheFakeAreHandledInOrder(fakeOne, fakeTwo))
|
||||||
@ -57,12 +85,95 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenARequest()
|
[Fact]
|
||||||
|
public void should_re_use_cookies_from_container()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder().WithIsQos(false)
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)).WithReRouteKey("").Build();
|
.WithIsQos(false)
|
||||||
|
.WithHttpHandlerOptions(new HttpHandlerOptions(false, true, false))
|
||||||
|
.WithReRouteKey("")
|
||||||
|
.Build();
|
||||||
|
|
||||||
_request = reRoute;
|
this.Given(_ => GivenADownstreamService())
|
||||||
|
.And(_ => GivenARequest(reRoute))
|
||||||
|
.And(_ => GivenTheFactoryReturnsNothing())
|
||||||
|
.And(_ => WhenIBuild())
|
||||||
|
.And(_ => WhenICallTheClient("http://localhost:5003"))
|
||||||
|
.And(_ => ThenTheCookieIsSet())
|
||||||
|
.And(_ => GivenTheClientIsCached())
|
||||||
|
.And(_ => WhenIBuild())
|
||||||
|
.When(_ => WhenICallTheClient("http://localhost:5003"))
|
||||||
|
.Then(_ => ThenTheResponseIsOk())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheClientIsCached()
|
||||||
|
{
|
||||||
|
_cacheHandlers.Setup(x => x.Get(It.IsAny<string>())).Returns(_httpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheCookieIsSet()
|
||||||
|
{
|
||||||
|
_response.Headers.TryGetValues("Set-Cookie", out var test).ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenICallTheClient(string url)
|
||||||
|
{
|
||||||
|
_response = _httpClient
|
||||||
|
.SendAsync(new HttpRequestMessage(HttpMethod.Get, url))
|
||||||
|
.GetAwaiter()
|
||||||
|
.GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheResponseIsOk()
|
||||||
|
{
|
||||||
|
_response.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenADownstreamService()
|
||||||
|
{
|
||||||
|
_host = new WebHostBuilder()
|
||||||
|
.UseUrls("http://localhost:5003")
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
if (_count == 0)
|
||||||
|
{
|
||||||
|
context.Response.Cookies.Append("test", "0");
|
||||||
|
context.Response.StatusCode = 200;
|
||||||
|
_count++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_count == 1)
|
||||||
|
{
|
||||||
|
if (context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue))
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 200;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.StatusCode = 500;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_host.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenARequest(DownstreamReRoute downstream)
|
||||||
|
{
|
||||||
|
var context = new DownstreamContext(new DefaultHttpContext())
|
||||||
|
{
|
||||||
|
DownstreamReRoute = downstream,
|
||||||
|
DownstreamRequest = new HttpRequestMessage() { RequestUri = new Uri("http://localhost:5003") },
|
||||||
|
};
|
||||||
|
|
||||||
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenSomethingIsReturned()
|
private void ThenSomethingIsReturned()
|
||||||
@ -88,6 +199,14 @@ 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()
|
||||||
|
{
|
||||||
|
var handlers = new List<Func<DelegatingHandler>>();
|
||||||
|
|
||||||
|
_factory
|
||||||
|
.Setup(x => x.Get(It.IsAny<DownstreamReRoute>()))
|
||||||
|
.Returns(new OkResponse<List<Func<DelegatingHandler>>>(handlers));
|
||||||
|
}
|
||||||
|
|
||||||
private void GivenTheFactoryReturns(List<Func<DelegatingHandler>> handlers)
|
private void GivenTheFactoryReturns(List<Func<DelegatingHandler>> handlers)
|
||||||
{
|
{
|
||||||
@ -98,12 +217,18 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
|
|
||||||
private void WhenIBuild()
|
private void WhenIBuild()
|
||||||
{
|
{
|
||||||
_httpClient = _builder.Create(_request);
|
_httpClient = _builder.Create(_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheHttpClientShouldNotBeNull()
|
private void ThenTheHttpClientShouldNotBeNull()
|
||||||
{
|
{
|
||||||
_httpClient.ShouldNotBeNull();
|
_httpClient.ShouldNotBeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_response?.Dispose();
|
||||||
|
_host?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,10 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
.Setup(x => x.CreateLogger<HttpClientHttpRequester>())
|
.Setup(x => x.CreateLogger<HttpClientHttpRequester>())
|
||||||
.Returns(_logger.Object);
|
.Returns(_logger.Object);
|
||||||
_cacheHandlers = new Mock<IHttpClientCache>();
|
_cacheHandlers = new Mock<IHttpClientCache>();
|
||||||
_httpClientRequester = new HttpClientHttpRequester(_loggerFactory.Object, _cacheHandlers.Object, _house.Object);
|
_httpClientRequester = new HttpClientHttpRequester(
|
||||||
|
_loggerFactory.Object,
|
||||||
|
_cacheHandlers.Object,
|
||||||
|
_house.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using Butterfly.Client.Tracing;
|
using Butterfly.Client.Tracing;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
using Ocelot.Requester;
|
using Ocelot.Requester;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@ -10,11 +11,13 @@ namespace Ocelot.UnitTests.Requester
|
|||||||
{
|
{
|
||||||
private TracingHandlerFactory _factory;
|
private TracingHandlerFactory _factory;
|
||||||
private Mock<IServiceTracer> _tracer;
|
private Mock<IServiceTracer> _tracer;
|
||||||
|
private Mock<IRequestScopedDataRepository> _repo;
|
||||||
|
|
||||||
public TracingHandlerFactoryTests()
|
public TracingHandlerFactoryTests()
|
||||||
{
|
{
|
||||||
_tracer = new Mock<IServiceTracer>();
|
_tracer = new Mock<IServiceTracer>();
|
||||||
_factory = new TracingHandlerFactory(_tracer.Object);
|
_repo = new Mock<IRequestScopedDataRepository>();
|
||||||
|
_factory = new TracingHandlerFactory(_tracer.Object, _repo.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -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(33, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?");
|
Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(34, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode)
|
private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode)
|
||||||
|
@ -1,68 +0,0 @@
|
|||||||
namespace Ocelot.UnitTests
|
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net.Http;
|
|
||||||
using Microsoft.AspNetCore.TestHost;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Moq;
|
|
||||||
using Ocelot.Infrastructure.RequestData;
|
|
||||||
|
|
||||||
public abstract class ServerHostedMiddlewareTest : IDisposable
|
|
||||||
{
|
|
||||||
protected TestServer Server { get; private set; }
|
|
||||||
protected HttpClient Client { get; private set; }
|
|
||||||
protected string Url { get; private set; }
|
|
||||||
protected HttpResponseMessage ResponseMessage { get; private set; }
|
|
||||||
protected Mock<IRequestScopedDataRepository> ScopedRepository { get; private set; }
|
|
||||||
|
|
||||||
public ServerHostedMiddlewareTest()
|
|
||||||
{
|
|
||||||
Url = "http://localhost:51879";
|
|
||||||
ScopedRepository = new Mock<IRequestScopedDataRepository>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void GivenTheTestServerIsConfigured()
|
|
||||||
{
|
|
||||||
var builder = new WebHostBuilder()
|
|
||||||
.ConfigureServices(x => GivenTheTestServerServicesAreConfigured(x))
|
|
||||||
.UseUrls(Url)
|
|
||||||
.UseKestrel()
|
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
|
||||||
.UseIISIntegration()
|
|
||||||
.Configure(app => GivenTheTestServerPipelineIsConfigured(app));
|
|
||||||
|
|
||||||
Server = new TestServer(builder);
|
|
||||||
Client = Server.CreateClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void GivenTheTestServerServicesAreConfigured(IServiceCollection services)
|
|
||||||
{
|
|
||||||
// override this in your test fixture to set up service dependencies
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app)
|
|
||||||
{
|
|
||||||
// override this in your test fixture to set up the test server pipeline
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void WhenICallTheMiddleware()
|
|
||||||
{
|
|
||||||
ResponseMessage = Client.GetAsync(Url).Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void WhenICallTheMiddlewareWithTheRequestIdKey(string requestIdKey, string value)
|
|
||||||
{
|
|
||||||
Client.DefaultRequestHeaders.Add(requestIdKey, value);
|
|
||||||
ResponseMessage = Client.GetAsync(Url).Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Client.Dispose();
|
|
||||||
Server.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user