mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-23 00:32: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)
|
||||
|
||||
[](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)
|
||||
|
||||
|
38
build.cake
38
build.cake
@ -17,7 +17,7 @@ var artifactsDir = Directory("artifacts");
|
||||
// unit testing
|
||||
var artifactsForUnitTestsDir = artifactsDir + Directory("UnitTests");
|
||||
var unitTestAssemblies = @"./test/Ocelot.UnitTests/Ocelot.UnitTests.csproj";
|
||||
var minCodeCoverage = 76.4d;
|
||||
var minCodeCoverage = 82d;
|
||||
var coverallsRepoToken = "coveralls-repo-token-ocelot";
|
||||
var coverallsRepo = "https://coveralls.io/github/TomPallister/Ocelot";
|
||||
|
||||
@ -189,6 +189,24 @@ Task("RunAcceptanceTests")
|
||||
.IsDependentOn("Compile")
|
||||
.Does(() =>
|
||||
{
|
||||
if(TravisCI.IsRunningOnTravisCI)
|
||||
{
|
||||
Information(
|
||||
@"Job:
|
||||
JobId: {0}
|
||||
JobNumber: {1}
|
||||
OSName: {2}",
|
||||
BuildSystem.TravisCI.Environment.Job.JobId,
|
||||
BuildSystem.TravisCI.Environment.Job.JobNumber,
|
||||
BuildSystem.TravisCI.Environment.Job.OSName
|
||||
);
|
||||
|
||||
if(TravisCI.Environment.Job.OSName.ToLower() == "osx")
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var settings = new DotNetCoreTestSettings
|
||||
{
|
||||
Configuration = compileConfig,
|
||||
@ -205,6 +223,24 @@ Task("RunIntegrationTests")
|
||||
.IsDependentOn("Compile")
|
||||
.Does(() =>
|
||||
{
|
||||
if(TravisCI.IsRunningOnTravisCI)
|
||||
{
|
||||
Information(
|
||||
@"Job:
|
||||
JobId: {0}
|
||||
JobNumber: {1}
|
||||
OSName: {2}",
|
||||
BuildSystem.TravisCI.Environment.Job.JobId,
|
||||
BuildSystem.TravisCI.Environment.Job.JobNumber,
|
||||
BuildSystem.TravisCI.Environment.Job.OSName
|
||||
);
|
||||
|
||||
if(TravisCI.Environment.Job.OSName.ToLower() == "osx")
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var settings = new DotNetCoreTestSettings
|
||||
{
|
||||
Configuration = compileConfig,
|
||||
|
@ -73,10 +73,17 @@ Follow Redirects / Use CookieContainer
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior:
|
||||
- _AllowAutoRedirect_ is a value that indicates whether the request should follow redirection responses.
|
||||
Set it true if the request should automatically follow redirection responses from the Downstream resource; otherwise false. The default value is true.
|
||||
- _UseCookieContainer_ is a value that indicates whether the handler uses the CookieContainer property to store server cookies and uses these cookies when sending requests.
|
||||
The default value is true.
|
||||
|
||||
1. AllowAutoRedirect is a value that indicates whether the request should follow redirection responses. Set it true if the request should automatically
|
||||
follow redirection responses from the Downstream resource; otherwise false. The default value is false.
|
||||
2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer
|
||||
property to store server cookies and uses these cookies when sending requests. The default value is false. Please note
|
||||
that if you are using the CookieContainer Ocelot caches the HttpClient for each downstream service. This means that all requests
|
||||
to that DownstreamService will share the same cookies. `Issue 274 <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
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -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.
|
||||
|
||||
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.
|
||||
|
||||
@ -43,6 +67,7 @@ Ocelot allows placeholders that can be used in header transformation.
|
||||
|
||||
{BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value.
|
||||
{DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment.
|
||||
{TraceId} - This will use the Butterfly APM Trace Id. This only works for DownstreamHeaderTransform at the moment.
|
||||
|
||||
Handling 302 Redirects
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using Ocelot.Values;
|
||||
using System.Linq;
|
||||
using Ocelot.Configuration.Creator;
|
||||
|
||||
namespace Ocelot.Configuration.Builder
|
||||
{
|
||||
@ -37,11 +38,13 @@ namespace Ocelot.Configuration.Builder
|
||||
private string _upstreamHost;
|
||||
private string _key;
|
||||
private List<string> _delegatingHandlers;
|
||||
private List<AddHeader> _addHeadersToDownstream;
|
||||
|
||||
public DownstreamReRouteBuilder()
|
||||
{
|
||||
_downstreamAddresses = new List<DownstreamHostAndPort>();
|
||||
_delegatingHandlers = new List<string>();
|
||||
_addHeadersToDownstream = new List<AddHeader>();
|
||||
}
|
||||
|
||||
public DownstreamReRouteBuilder WithDownstreamAddresses(List<DownstreamHostAndPort> downstreamAddresses)
|
||||
@ -224,6 +227,12 @@ namespace Ocelot.Configuration.Builder
|
||||
return this;
|
||||
}
|
||||
|
||||
public DownstreamReRouteBuilder WithAddHeadersToDownstream(List<AddHeader> addHeadersToDownstream)
|
||||
{
|
||||
_addHeadersToDownstream = addHeadersToDownstream;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DownstreamReRoute Build()
|
||||
{
|
||||
return new DownstreamReRoute(
|
||||
@ -253,7 +262,8 @@ namespace Ocelot.Configuration.Builder
|
||||
_authenticationOptions,
|
||||
new PathTemplate(_downstreamPathTemplate),
|
||||
_reRouteKey,
|
||||
_delegatingHandlers);
|
||||
_delegatingHandlers,
|
||||
_addHeadersToDownstream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -213,6 +213,7 @@ namespace Ocelot.Configuration.Creator
|
||||
.WithDownstreamHeaderFindAndReplace(hAndRs.Downstream)
|
||||
.WithUpstreamHost(fileReRoute.UpstreamHost)
|
||||
.WithDelegatingHandlers(fileReRoute.DelegatingHandlers)
|
||||
.WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream)
|
||||
.Build();
|
||||
|
||||
return reRoute;
|
||||
|
@ -1,20 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Infrastructure;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
using Ocelot.Responses;
|
||||
|
||||
namespace Ocelot.Configuration.Creator
|
||||
{
|
||||
public class HeaderFindAndReplaceCreator : IHeaderFindAndReplaceCreator
|
||||
{
|
||||
private IBaseUrlFinder _finder;
|
||||
private readonly Dictionary<string, Func<string>> _placeholders;
|
||||
private IPlaceholders _placeholders;
|
||||
private IOcelotLogger _logger;
|
||||
|
||||
public HeaderFindAndReplaceCreator(IBaseUrlFinder finder)
|
||||
public HeaderFindAndReplaceCreator(IPlaceholders placeholders, IOcelotLoggerFactory factory)
|
||||
{
|
||||
_finder = finder;
|
||||
_placeholders = new Dictionary<string, Func<string>>();
|
||||
_placeholders.Add("{BaseUrl}", () => _finder.Find());
|
||||
_logger = factory.CreateLogger<HeaderFindAndReplaceCreator>();;
|
||||
_placeholders = placeholders;
|
||||
}
|
||||
|
||||
public HeaderTransformations Create(FileReRoute fileReRoute)
|
||||
@ -24,21 +26,43 @@ namespace Ocelot.Configuration.Creator
|
||||
foreach(var input in fileReRoute.UpstreamHeaderTransform)
|
||||
{
|
||||
var hAndr = Map(input);
|
||||
upstream.Add(hAndr);
|
||||
if(!hAndr.IsError)
|
||||
{
|
||||
upstream.Add(hAndr.Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError($"Unable to add UpstreamHeaderTransform {input.Key}: {input.Value}");
|
||||
}
|
||||
}
|
||||
|
||||
var downstream = new List<HeaderFindAndReplace>();
|
||||
var addHeadersToDownstream = new List<AddHeader>();
|
||||
|
||||
foreach(var input in fileReRoute.DownstreamHeaderTransform)
|
||||
{
|
||||
if(input.Value.Contains(","))
|
||||
{
|
||||
var hAndr = Map(input);
|
||||
downstream.Add(hAndr);
|
||||
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(",");
|
||||
|
||||
@ -51,16 +75,19 @@ namespace Ocelot.Configuration.Creator
|
||||
|
||||
var placeholder = replace.Substring(startOfPlaceholder, startOfPlaceholder + (endOfPlaceholder + 1));
|
||||
|
||||
if(_placeholders.ContainsKey(placeholder))
|
||||
var value = _placeholders.Get(placeholder);
|
||||
|
||||
if(value.IsError)
|
||||
{
|
||||
var value = _placeholders[placeholder].Invoke();
|
||||
replace = replace.Replace(placeholder, value);
|
||||
return new ErrorResponse<HeaderFindAndReplace>(value.Errors);
|
||||
}
|
||||
|
||||
replace = replace.Replace(placeholder, value.Data);
|
||||
}
|
||||
|
||||
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 HeaderTransformations(List<HeaderFindAndReplace> upstream, List<HeaderFindAndReplace> downstream)
|
||||
public HeaderTransformations(
|
||||
List<HeaderFindAndReplace> upstream,
|
||||
List<HeaderFindAndReplace> downstream,
|
||||
List<AddHeader> addHeader)
|
||||
{
|
||||
AddHeadersToDownstream = addHeader;
|
||||
Upstream = upstream;
|
||||
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 Ocelot.Configuration.Creator;
|
||||
using Ocelot.Values;
|
||||
|
||||
namespace Ocelot.Configuration
|
||||
@ -32,8 +33,10 @@ namespace Ocelot.Configuration
|
||||
AuthenticationOptions authenticationOptions,
|
||||
PathTemplate downstreamPathTemplate,
|
||||
string reRouteKey,
|
||||
List<string> delegatingHandlers)
|
||||
List<string> delegatingHandlers,
|
||||
List<AddHeader> addHeadersToDownstream)
|
||||
{
|
||||
AddHeadersToDownstream = addHeadersToDownstream;
|
||||
DelegatingHandlers = delegatingHandlers;
|
||||
Key = key;
|
||||
UpstreamPathTemplate = upstreamPathTemplate;
|
||||
@ -90,5 +93,6 @@ namespace Ocelot.Configuration
|
||||
public PathTemplate DownstreamPathTemplate { get; private set; }
|
||||
public string ReRouteKey { get; private set; }
|
||||
public List<string> DelegatingHandlers {get;private set;}
|
||||
public List<AddHeader> AddHeadersToDownstream {get;private set;}
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,8 @@
|
||||
{
|
||||
public FileHttpHandlerOptions()
|
||||
{
|
||||
AllowAutoRedirect = true;
|
||||
UseCookieContainer = true;
|
||||
AllowAutoRedirect = false;
|
||||
UseCookieContainer = false;
|
||||
}
|
||||
|
||||
public bool AllowAutoRedirect { get; set; }
|
||||
|
@ -17,10 +17,16 @@ namespace Ocelot.Configuration.Repository
|
||||
private string _previousAsJson;
|
||||
private readonly Timer _timer;
|
||||
private bool _polling;
|
||||
private readonly IConsulPollerConfiguration _config;
|
||||
|
||||
public ConsulFileConfigurationPoller(IOcelotLoggerFactory factory, IFileConfigurationRepository repo, IFileConfigurationSetter setter)
|
||||
public ConsulFileConfigurationPoller(
|
||||
IOcelotLoggerFactory factory,
|
||||
IFileConfigurationRepository repo,
|
||||
IFileConfigurationSetter setter,
|
||||
IConsulPollerConfiguration config)
|
||||
{
|
||||
_setter = setter;
|
||||
_config = config;
|
||||
_logger = factory.CreateLogger<ConsulFileConfigurationPoller>();
|
||||
_repo = repo;
|
||||
_previousAsJson = "";
|
||||
@ -34,7 +40,7 @@ namespace Ocelot.Configuration.Repository
|
||||
_polling = true;
|
||||
await Poll();
|
||||
_polling = false;
|
||||
}, null, 0, 1000);
|
||||
}, null, 0, _config.Delay);
|
||||
}
|
||||
|
||||
private async Task Poll()
|
||||
@ -63,8 +69,7 @@ namespace Ocelot.Configuration.Repository
|
||||
/// <summary>
|
||||
/// We could do object comparison here but performance isnt really a problem. This might be an issue one day!
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
/// <returns></returns>
|
||||
/// <returns>hash of the config</returns>
|
||||
private string ToJson(FileConfiguration 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.Net.Http;
|
||||
using Butterfly.Client.AspNetCore;
|
||||
using Ocelot.Infrastructure;
|
||||
|
||||
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..
|
||||
_services.AddSingleton<IServiceTracer, FakeServiceTracer>();
|
||||
_services.TryAddSingleton<IConsulPollerConfiguration, InMemoryConsulPollerConfiguration>();
|
||||
_services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();
|
||||
_services.TryAddSingleton<IPlaceholders, Placeholders>();
|
||||
}
|
||||
|
||||
public IOcelotAdministrationBuilder AddAdministration(string path, string secret)
|
||||
|
@ -34,6 +34,7 @@
|
||||
RateLimitOptionsError,
|
||||
PathTemplateDoesntStartWithForwardSlash,
|
||||
FileValidationFailedError,
|
||||
UnableToFindDelegatingHandlerProviderError
|
||||
UnableToFindDelegatingHandlerProviderError,
|
||||
CouldNotFindPlaceholderError
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using Ocelot.Configuration;
|
||||
using Ocelot.Infrastructure.Claims.Parser;
|
||||
using Ocelot.Responses;
|
||||
using System.Net.Http;
|
||||
using Ocelot.Configuration.Creator;
|
||||
|
||||
namespace Ocelot.Headers
|
||||
{
|
||||
|
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.Net.Http;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Infrastructure;
|
||||
using Ocelot.Infrastructure.Extensions;
|
||||
using Ocelot.Responses;
|
||||
|
||||
@ -10,24 +11,14 @@ namespace Ocelot.Headers
|
||||
{
|
||||
public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer
|
||||
{
|
||||
private Dictionary<string, Func<HttpRequestMessage, string>> _placeholders;
|
||||
private IPlaceholders _placeholders;
|
||||
|
||||
public HttpResponseHeaderReplacer()
|
||||
public HttpResponseHeaderReplacer(IPlaceholders placeholders)
|
||||
{
|
||||
_placeholders = new Dictionary<string, Func<HttpRequestMessage, string>>();
|
||||
_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}";
|
||||
_placeholders = placeholders;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@ -35,11 +26,13 @@ namespace Ocelot.Headers
|
||||
if(response.Headers.TryGetValues(f.Key, out var values))
|
||||
{
|
||||
//check to see if it is a placeholder in the find...
|
||||
if(_placeholders.TryGetValue(f.Find, out var replacePlaceholder))
|
||||
var placeholderValue = _placeholders.Get(f.Find, request);
|
||||
|
||||
if(!placeholderValue.IsError)
|
||||
{
|
||||
//if it is we need to get the value of the placeholder
|
||||
var find = replacePlaceholder(httpRequestMessage);
|
||||
var replaced = values.ToList()[f.Index].Replace(find, f.Replace.LastCharAsForwardSlash());
|
||||
//var find = replacePlaceholder(httpRequestMessage);
|
||||
var replaced = values.ToList()[f.Index].Replace(placeholderValue.Data, f.Replace.LastCharAsForwardSlash());
|
||||
response.Headers.Remove(f.Key);
|
||||
response.Headers.Add(f.Key, replaced);
|
||||
}
|
||||
|
@ -4,6 +4,8 @@
|
||||
using System.Net.Http;
|
||||
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Creator;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
using Ocelot.Responses;
|
||||
|
||||
public interface IAddHeadersToRequest
|
||||
|
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 IHttpContextRequestHeaderReplacer _preReplacer;
|
||||
private readonly IHttpResponseHeaderReplacer _postReplacer;
|
||||
private readonly IAddHeadersToResponse _addHeaders;
|
||||
|
||||
public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next,
|
||||
IOcelotLoggerFactory loggerFactory,
|
||||
IHttpContextRequestHeaderReplacer preReplacer,
|
||||
IHttpResponseHeaderReplacer postReplacer)
|
||||
IHttpResponseHeaderReplacer postReplacer,
|
||||
IAddHeadersToResponse addHeaders)
|
||||
{
|
||||
_addHeaders = addHeaders;
|
||||
_next = next;
|
||||
_postReplacer = postReplacer;
|
||||
_preReplacer = preReplacer;
|
||||
@ -37,6 +40,8 @@ namespace Ocelot.Headers.Middleware
|
||||
var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace;
|
||||
|
||||
_postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest);
|
||||
|
||||
_addHeaders.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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
|
||||
{
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ocelot.UnitTests
|
||||
namespace Ocelot.Infrastructure
|
||||
{
|
||||
public class Waiter
|
||||
{
|
@ -3,6 +3,7 @@
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||
<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>
|
||||
<AssemblyTitle>Ocelot</AssemblyTitle>
|
||||
<VersionPrefix>0.0.0-dev</VersionPrefix>
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
public class UnmappableRequestError : Error
|
||||
{
|
||||
public UnmappableRequestError(Exception ex) : base($"Error when parsing incoming request, exception: {ex.Message}", OcelotErrorCode.UnmappableRequestError)
|
||||
public UnmappableRequestError(Exception exception) : base($"Error when parsing incoming request, exception: {exception}", OcelotErrorCode.UnmappableRequestError)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
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 Ocelot.Configuration;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
|
||||
namespace Ocelot.Requester
|
||||
{
|
||||
public class HttpClientBuilder : IHttpClientBuilder
|
||||
{
|
||||
private readonly IDelegatingHandlerHandlerFactory _factory;
|
||||
private readonly IHttpClientCache _cacheHandlers;
|
||||
private readonly IOcelotLogger _logger;
|
||||
private string _cacheKey;
|
||||
private HttpClient _httpClient;
|
||||
private IHttpClient _client;
|
||||
private HttpClientHandler _httpclientHandler;
|
||||
|
||||
public HttpClientBuilder(IDelegatingHandlerHandlerFactory house)
|
||||
public HttpClientBuilder(
|
||||
IDelegatingHandlerHandlerFactory factory,
|
||||
IHttpClientCache cacheHandlers,
|
||||
IOcelotLogger logger)
|
||||
{
|
||||
_factory = house;
|
||||
_factory = factory;
|
||||
_cacheHandlers = cacheHandlers;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IHttpClient Create(DownstreamReRoute request)
|
||||
public IHttpClient Create(DownstreamContext request)
|
||||
{
|
||||
var httpclientHandler = new HttpClientHandler { AllowAutoRedirect = request.HttpHandlerOptions.AllowAutoRedirect, UseCookies = request.HttpHandlerOptions.UseCookieContainer};
|
||||
_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)
|
||||
@ -39,5 +75,12 @@ namespace Ocelot.Requester
|
||||
});
|
||||
return httpMessageHandler;
|
||||
}
|
||||
|
||||
private string GetCacheKey(DownstreamContext request)
|
||||
{
|
||||
var baseUrl = $"{request.DownstreamRequest.RequestUri.Scheme}://{request.DownstreamRequest.RequestUri.Authority}{request.DownstreamRequest.RequestUri.AbsolutePath}";
|
||||
|
||||
return baseUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,28 +24,24 @@ namespace Ocelot.Requester
|
||||
_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 = GetHttpClient(cacheKey, builder, request);
|
||||
var httpClient = builder.Create(context);
|
||||
|
||||
try
|
||||
{
|
||||
var response = await httpClient.SendAsync(request.DownstreamRequest);
|
||||
var response = await httpClient.SendAsync(context.DownstreamRequest);
|
||||
return new OkResponse<HttpResponseMessage>(response);
|
||||
}
|
||||
catch (TimeoutRejectedException exception)
|
||||
{
|
||||
return
|
||||
new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
|
||||
return new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
|
||||
}
|
||||
catch (BrokenCircuitException exception)
|
||||
{
|
||||
return
|
||||
new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
|
||||
return new ErrorResponse<HttpResponseMessage>(new RequestTimedOutError(exception));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@ -53,42 +49,8 @@ namespace Ocelot.Requester
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cacheHandlers.Set(cacheKey, httpClient, TimeSpan.FromHours(24));
|
||||
builder.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private IHttpClient GetHttpClient(string cacheKey, IHttpClientBuilder builder, DownstreamContext request)
|
||||
{
|
||||
var httpClient = _cacheHandlers.Get(cacheKey);
|
||||
|
||||
if (httpClient == null)
|
||||
{
|
||||
httpClient = builder.Create(request.DownstreamReRoute);
|
||||
}
|
||||
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
private string GetCacheKey(DownstreamContext request)
|
||||
{
|
||||
var baseUrl = $"{request.DownstreamRequest.RequestUri.Scheme}://{request.DownstreamRequest.RequestUri.Authority}";
|
||||
|
||||
return baseUrl;
|
||||
}
|
||||
}
|
||||
|
||||
public class ReRouteDelegatingHandler<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>
|
||||
/// This class was made to make unit testing easier when HttpClient is used.
|
||||
/// </summary>
|
||||
internal class HttpClientWrapper : IHttpClient
|
||||
public class HttpClientWrapper : IHttpClient
|
||||
{
|
||||
public HttpClient Client { get; }
|
||||
|
||||
|
@ -1,14 +1,10 @@
|
||||
using System.Net.Http;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Middleware;
|
||||
|
||||
namespace Ocelot.Requester
|
||||
{
|
||||
public interface IHttpClientBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the <see cref="HttpClient"/>
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
IHttpClient Create(DownstreamReRoute request);
|
||||
IHttpClient Create(DownstreamContext request);
|
||||
void Save();
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,6 @@ namespace Ocelot.Requester
|
||||
{
|
||||
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 Butterfly.Client.Tracing;
|
||||
using Butterfly.OpenTracing;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
|
||||
namespace Ocelot.Requester
|
||||
{
|
||||
public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler
|
||||
{
|
||||
private readonly IServiceTracer _tracer;
|
||||
private readonly IRequestScopedDataRepository _repo;
|
||||
private const string prefix_spanId = "ot-spanId";
|
||||
|
||||
public OcelotHttpTracingHandler(IServiceTracer tracer, HttpMessageHandler httpMessageHandler = null)
|
||||
public OcelotHttpTracingHandler(
|
||||
IServiceTracer tracer,
|
||||
IRequestScopedDataRepository repo,
|
||||
HttpMessageHandler httpMessageHandler = null)
|
||||
{
|
||||
_repo = repo;
|
||||
_tracer = tracer ?? throw new ArgumentNullException(nameof(tracer));
|
||||
InnerHandler = httpMessageHandler ?? new HttpClientHandler();
|
||||
}
|
||||
|
||||
protected override Task<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));
|
||||
}
|
||||
|
||||
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;
|
||||
if (request.Headers.TryGetValues(prefix_spanId, out traceIdVals))
|
||||
@ -33,6 +44,8 @@ namespace Ocelot.Requester
|
||||
request.Headers.TryAddWithoutValidation(prefix_spanId, span.SpanContext.SpanId);
|
||||
}
|
||||
|
||||
_repo.Add("TraceId", span.SpanContext.TraceId);
|
||||
|
||||
span.Tags.Client().Component("HttpClient")
|
||||
.HttpMethod(request.Method.Method)
|
||||
.HttpUrl(request.RequestUri.OriginalString)
|
||||
|
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 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.OpenTracing;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
|
||||
namespace Ocelot.Requester
|
||||
{
|
||||
public class TracingHandlerFactory : ITracingHandlerFactory
|
||||
{
|
||||
private readonly IServiceTracer _tracer;
|
||||
private readonly IRequestScopedDataRepository _repo;
|
||||
|
||||
public TracingHandlerFactory(IServiceTracer tracer)
|
||||
public TracingHandlerFactory(
|
||||
IServiceTracer tracer,
|
||||
IRequestScopedDataRepository repo)
|
||||
{
|
||||
_repo = repo;
|
||||
_tracer = tracer;
|
||||
}
|
||||
|
||||
public ITracingHandler Get()
|
||||
{
|
||||
return new OcelotHttpTracingHandler(_tracer);
|
||||
return new OcelotHttpTracingHandler(_tracer, _repo);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ namespace Ocelot.Requester
|
||||
public class UnableToCompleteRequestError : Error
|
||||
{
|
||||
public UnableToCompleteRequestError(Exception exception)
|
||||
: base($"Error making http request, exception: {exception.Message}", OcelotErrorCode.UnableToCompleteRequestError)
|
||||
: base($"Error making http request, exception: {exception}", OcelotErrorCode.UnableToCompleteRequestError)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,12 @@ namespace Ocelot.Responder.Middleware
|
||||
{
|
||||
var errors = context.Errors;
|
||||
_logger.LogError($"{errors.Count} pipeline errors found in {MiddlewareName}. Setting error response status code");
|
||||
|
||||
foreach(var error in errors)
|
||||
{
|
||||
_logger.LogError(error.Message);
|
||||
}
|
||||
|
||||
SetErrorResponse(context.HttpContext, errors);
|
||||
}
|
||||
else
|
||||
|
@ -70,7 +70,7 @@ namespace Ocelot.AcceptanceTests
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 51888,
|
||||
Port = 51388,
|
||||
}
|
||||
},
|
||||
UpstreamPathTemplate = "/api002/values",
|
||||
@ -92,7 +92,7 @@ namespace Ocelot.AcceptanceTests
|
||||
var butterflyUrl = "http://localhost:9618";
|
||||
|
||||
this.Given(x => GivenServiceOneIsRunning("http://localhost:51887", "/api/values", 200, "Hello from Laura", butterflyUrl))
|
||||
.And(x => GivenServiceTwoIsRunning("http://localhost:51888", "/api/values", 200, "Hello from Tom", butterflyUrl))
|
||||
.And(x => GivenServiceTwoIsRunning("http://localhost:51388", "/api/values", 200, "Hello from Tom", butterflyUrl))
|
||||
.And(x => GivenFakeButterfly(butterflyUrl))
|
||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||
.And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl))
|
||||
@ -109,6 +109,60 @@ namespace Ocelot.AcceptanceTests
|
||||
commandOnAllStateMachines.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_return_tracing_header()
|
||||
{
|
||||
var configuration = new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<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)
|
||||
{
|
||||
_serviceOneBuilder = new WebHostBuilder()
|
||||
|
@ -9,8 +9,10 @@ using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Ocelot.Configuration.File;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
using static Ocelot.Infrastructure.Wait;
|
||||
|
||||
namespace Ocelot.AcceptanceTests
|
||||
{
|
||||
@ -261,21 +263,27 @@ namespace Ocelot.AcceptanceTests
|
||||
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status"))
|
||||
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||
.And(x => GivenTheConsulConfigurationIs(secondConsulConfig))
|
||||
.And(x => GivenIWaitForTheConfigToReplicateToOcelot())
|
||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome"))
|
||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
||||
.When(x => GivenTheConsulConfigurationIs(secondConsulConfig))
|
||||
.Then(x => ThenTheConfigIsUpdatedInOcelot())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void GivenIWaitForTheConfigToReplicateToOcelot()
|
||||
private void ThenTheConfigIsUpdatedInOcelot()
|
||||
{
|
||||
var stopWatch = Stopwatch.StartNew();
|
||||
while (stopWatch.ElapsedMilliseconds < 10000)
|
||||
var result = WaitFor(20000).Until(() => {
|
||||
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)
|
||||
|
@ -16,6 +16,8 @@ namespace Ocelot.AcceptanceTests
|
||||
public class HeaderTests : IDisposable
|
||||
{
|
||||
private IWebHost _builder;
|
||||
private string _cookieValue;
|
||||
private int _count;
|
||||
private readonly Steps _steps;
|
||||
|
||||
public HeaderTests()
|
||||
@ -184,6 +186,125 @@ namespace Ocelot.AcceptanceTests
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void request_should_reuse_cookies_with_cookie_container()
|
||||
{
|
||||
var configuration = new FileConfiguration
|
||||
{
|
||||
ReRoutes = new List<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)
|
||||
{
|
||||
_builder = new WebHostBuilder()
|
||||
|
@ -31,7 +31,7 @@ namespace Ocelot.AcceptanceTests
|
||||
public void should_use_service_discovery_and_load_balance_request()
|
||||
{
|
||||
var downstreamServiceOneUrl = "http://localhost:50881";
|
||||
var downstreamServiceTwoUrl = "http://localhost:50882";
|
||||
var downstreamServiceTwoUrl = "http://localhost:50892";
|
||||
|
||||
var configuration = new FileConfiguration
|
||||
{
|
||||
@ -54,7 +54,7 @@ namespace Ocelot.AcceptanceTests
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 50882
|
||||
Port = 50892
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,9 +50,4 @@
|
||||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
|
||||
</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>
|
||||
|
@ -41,7 +41,7 @@ namespace Ocelot.AcceptanceTests
|
||||
new FileHostAndPort
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 51872,
|
||||
Port = 51892,
|
||||
}
|
||||
},
|
||||
UpstreamPathTemplate = "/",
|
||||
@ -56,7 +56,7 @@ namespace Ocelot.AcceptanceTests
|
||||
}
|
||||
};
|
||||
|
||||
this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51872", "Hello from Laura"))
|
||||
this.Given(x => x.GivenThereIsAPossiblyBrokenServiceRunningOn("http://localhost:51892", "Hello from Laura"))
|
||||
.Given(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||
.Given(x => _steps.GivenOcelotIsRunning())
|
||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||
|
@ -922,6 +922,55 @@ namespace Ocelot.AcceptanceTests
|
||||
.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)
|
||||
{
|
||||
_builder = new WebHostBuilder()
|
||||
|
@ -274,6 +274,11 @@ namespace Ocelot.AcceptanceTests
|
||||
_ocelotClient = _ocelotServer.CreateClient();
|
||||
}
|
||||
|
||||
internal void GivenIAddCookieToMyRequest(string cookie)
|
||||
{
|
||||
_ocelotClient.DefaultRequestHeaders.Add("Set-Cookie", cookie);
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
@ -313,6 +318,12 @@ namespace Ocelot.AcceptanceTests
|
||||
header.First().ShouldBe(value);
|
||||
}
|
||||
|
||||
public void ThenTheTraceHeaderIsSet(string key)
|
||||
{
|
||||
var header = _response.Headers.GetValues(key);
|
||||
header.First().ShouldNotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
public void GivenOcelotIsRunningUsingJsonSerializedCache()
|
||||
{
|
||||
_webHostBuilder = new WebHostBuilder();
|
||||
|
@ -342,7 +342,9 @@ namespace Ocelot.IntegrationTests
|
||||
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
|
||||
config.AddJsonFile("configuration.json");
|
||||
config.AddJsonFile("peers.json", optional: true, reloadOnChange: true);
|
||||
#pragma warning disable CS0618
|
||||
config.AddOcelotBaseUrl(url);
|
||||
#pragma warning restore CS0618
|
||||
config.AddEnvironmentVariables();
|
||||
})
|
||||
.ConfigureServices(x =>
|
||||
|
@ -1,16 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using Moq;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Configuration.Repository;
|
||||
using Ocelot.Configuration.Setter;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.UnitTests.Responder;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
using Shouldly;
|
||||
using static Ocelot.UnitTests.Wait;
|
||||
using static Ocelot.Infrastructure.Wait;
|
||||
|
||||
namespace Ocelot.UnitTests.Configuration
|
||||
{
|
||||
@ -21,6 +22,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
private Mock<IFileConfigurationRepository> _repo;
|
||||
private Mock<IFileConfigurationSetter> _setter;
|
||||
private FileConfiguration _fileConfig;
|
||||
private Mock<IConsulPollerConfiguration> _config;
|
||||
|
||||
public ConsulFileConfigurationPollerTests()
|
||||
{
|
||||
@ -30,8 +32,10 @@ namespace Ocelot.UnitTests.Configuration
|
||||
_repo = new Mock<IFileConfigurationRepository>();
|
||||
_setter = new Mock<IFileConfigurationSetter>();
|
||||
_fileConfig = new FileConfiguration();
|
||||
_config = new Mock<IConsulPollerConfiguration>();
|
||||
_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()
|
||||
@ -42,7 +46,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
[Fact]
|
||||
public void should_start()
|
||||
{
|
||||
this.Given(x => ThenTheSetterIsCalled(_fileConfig))
|
||||
this.Given(x => ThenTheSetterIsCalled(_fileConfig, 1))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
@ -65,22 +69,82 @@ namespace Ocelot.UnitTests.Configuration
|
||||
}
|
||||
};
|
||||
|
||||
this.Given(x => WhenTheConfigIsChangedInConsul(newConfig))
|
||||
.Then(x => ThenTheSetterIsCalled(newConfig))
|
||||
this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 0))
|
||||
.Then(x => ThenTheSetterIsCalled(newConfig, 1))
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig)
|
||||
[Fact]
|
||||
public void should_not_poll_if_already_polling()
|
||||
{
|
||||
_repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse<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(() => {
|
||||
try
|
||||
{
|
||||
_setter.Verify(x => x.Set(fileConfig), Times.Once);
|
||||
_setter.Verify(x => x.Set(fileConfig), Times.Exactly(times));
|
||||
return true;
|
||||
}
|
||||
catch(Exception)
|
||||
|
@ -827,6 +827,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
result.DownstreamReRoute[0].ClaimsToQueries.Count.ShouldBe(expected.DownstreamReRoute[0].ClaimsToQueries.Count);
|
||||
result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey);
|
||||
result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers);
|
||||
result.DownstreamReRoute[0].AddHeadersToDownstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToDownstream);
|
||||
}
|
||||
}
|
||||
|
||||
@ -909,7 +910,7 @@ namespace Ocelot.UnitTests.Configuration
|
||||
|
||||
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)
|
||||
|
@ -5,7 +5,11 @@ using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.Configuration.Creator;
|
||||
using Ocelot.Configuration.File;
|
||||
using Ocelot.Infrastructure;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
using Ocelot.Responses;
|
||||
using Ocelot.UnitTests.Responder;
|
||||
using Shouldly;
|
||||
using TestStack.BDDfy;
|
||||
using Xunit;
|
||||
@ -17,12 +21,17 @@ namespace Ocelot.UnitTests.Configuration
|
||||
private HeaderFindAndReplaceCreator _creator;
|
||||
private FileReRoute _reRoute;
|
||||
private HeaderTransformations _result;
|
||||
private Mock<IBaseUrlFinder> _finder;
|
||||
private Mock<IPlaceholders> _placeholders;
|
||||
private Mock<IOcelotLoggerFactory> _factory;
|
||||
private Mock<IOcelotLogger> _logger;
|
||||
|
||||
public HeaderFindAndReplaceCreatorTests()
|
||||
{
|
||||
_finder = new Mock<IBaseUrlFinder>();
|
||||
_creator = new HeaderFindAndReplaceCreator(_finder.Object);
|
||||
_logger = new Mock<IOcelotLogger>();
|
||||
_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]
|
||||
@ -84,6 +93,40 @@ namespace Ocelot.UnitTests.Configuration
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_log_errors_and_not_add_headers()
|
||||
{
|
||||
var reRoute = new FileReRoute
|
||||
{
|
||||
DownstreamHeaderTransform = new Dictionary<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]
|
||||
public void should_use_base_url_partial_placeholder()
|
||||
{
|
||||
@ -107,9 +150,41 @@ namespace Ocelot.UnitTests.Configuration
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void should_add_trace_id_header()
|
||||
{
|
||||
var reRoute = new FileReRoute
|
||||
{
|
||||
DownstreamHeaderTransform = new Dictionary<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)
|
||||
{
|
||||
_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)
|
||||
|
@ -19,10 +19,10 @@ namespace Ocelot.UnitTests.Configuration
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_create_options_with_useCookie_and_allowAutoRedirect_true_as_default()
|
||||
public void should_create_options_with_useCookie_false_and_allowAutoRedirect_true_as_default()
|
||||
{
|
||||
var fileReRoute = new FileReRoute();
|
||||
var expectedOptions = new HttpHandlerOptions(true, true, false);
|
||||
var expectedOptions = new HttpHandlerOptions(false, false, false);
|
||||
|
||||
this.Given(x => GivenTheFollowing(fileReRoute))
|
||||
.When(x => WhenICreateHttpHandlerOptions())
|
||||
@ -61,12 +61,12 @@ namespace Ocelot.UnitTests.Configuration
|
||||
_httpHandlerOptions = _httpHandlerOptionsCreator.Create(_fileReRoute);
|
||||
}
|
||||
|
||||
private void ThenTheFollowingOptionsReturned(HttpHandlerOptions options)
|
||||
private void ThenTheFollowingOptionsReturned(HttpHandlerOptions expected)
|
||||
{
|
||||
_httpHandlerOptions.ShouldNotBeNull();
|
||||
_httpHandlerOptions.AllowAutoRedirect.ShouldBe(options.AllowAutoRedirect);
|
||||
_httpHandlerOptions.UseCookieContainer.ShouldBe(options.UseCookieContainer);
|
||||
_httpHandlerOptions.UseTracing.ShouldBe(options.UseTracing);
|
||||
_httpHandlerOptions.AllowAutoRedirect.ShouldBe(expected.AllowAutoRedirect);
|
||||
_httpHandlerOptions.UseCookieContainer.ShouldBe(expected.UseCookieContainer);
|
||||
_httpHandlerOptions.UseTracing.ShouldBe(expected.UseTracing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,9 +22,10 @@ namespace Ocelot.UnitTests.DependencyInjection
|
||||
|
||||
private void GivenTheBaseUrl(string baseUrl)
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
var builder = new ConfigurationBuilder()
|
||||
.AddOcelotBaseUrl(baseUrl);
|
||||
|
||||
#pragma warning restore CS0618
|
||||
_configuration = builder.Build();
|
||||
}
|
||||
|
||||
|
@ -223,12 +223,14 @@ namespace Ocelot.UnitTests.DependencyInjection
|
||||
_ocelotBuilder.AddAdministration("/administration", options);
|
||||
}
|
||||
|
||||
private void AddTransientGlobalDelegatingHandler<T>() where T : DelegatingHandler
|
||||
private void AddTransientGlobalDelegatingHandler<T>()
|
||||
where T : DelegatingHandler
|
||||
{
|
||||
_ocelotBuilder.AddTransientDelegatingHandler<T>(true);
|
||||
}
|
||||
|
||||
private void AddSpecificTransientDelegatingHandler<T>() where T : DelegatingHandler
|
||||
private void AddSpecificTransientDelegatingHandler<T>()
|
||||
where T : DelegatingHandler
|
||||
{
|
||||
_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);
|
||||
}
|
||||
|
||||
private void AddSpecificDelegatingHandler<T>() where T : DelegatingHandler
|
||||
private void AddSpecificDelegatingHandler<T>()
|
||||
where T : DelegatingHandler
|
||||
{
|
||||
_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 DownstreamContext _downstreamContext;
|
||||
private OcelotRequestDelegate _next;
|
||||
private Mock<IAddHeadersToResponse> _addHeaders;
|
||||
|
||||
public HttpHeadersTransformationMiddlewareTests()
|
||||
{
|
||||
@ -36,7 +37,8 @@ namespace Ocelot.UnitTests.Headers
|
||||
_logger = new Mock<IOcelotLogger>();
|
||||
_loggerFactory.Setup(x => x.CreateLogger<AuthorisationMiddleware>()).Returns(_logger.Object);
|
||||
_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]
|
||||
@ -49,9 +51,16 @@ namespace Ocelot.UnitTests.Headers
|
||||
.When(x => WhenICallTheMiddleware())
|
||||
.Then(x => ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly())
|
||||
.And(x => ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly())
|
||||
.And(x => ThenAddHeadersIsCalledCorrectly())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void ThenAddHeadersIsCalledCorrectly()
|
||||
{
|
||||
_addHeaders
|
||||
.Verify(x => x.Add(_downstreamContext.DownstreamReRoute.AddHeadersToDownstream, _downstreamContext.DownstreamResponse), Times.Once);
|
||||
}
|
||||
|
||||
private void WhenICallTheMiddleware()
|
||||
{
|
||||
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();
|
||||
|
@ -7,20 +7,30 @@ using Ocelot.Configuration;
|
||||
using System.Collections.Generic;
|
||||
using Ocelot.Responses;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using Ocelot.Infrastructure;
|
||||
using Ocelot.Middleware;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
|
||||
namespace Ocelot.UnitTests.Headers
|
||||
{
|
||||
public class HttpResponseHeaderReplacerTests
|
||||
{
|
||||
private HttpResponseMessage _response;
|
||||
private Placeholders _placeholders;
|
||||
private HttpResponseHeaderReplacer _replacer;
|
||||
private List<HeaderFindAndReplace> _headerFindAndReplaces;
|
||||
private Response _result;
|
||||
private HttpRequestMessage _request;
|
||||
private Mock<IBaseUrlFinder> _finder;
|
||||
private Mock<IRequestScopedDataRepository> _repo;
|
||||
|
||||
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]
|
||||
|
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();
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void should_not_add_content_headers()
|
||||
{
|
||||
|
@ -1,9 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Moq;
|
||||
using Ocelot.Configuration;
|
||||
using Ocelot.Configuration.Builder;
|
||||
using Ocelot.Logging;
|
||||
using Ocelot.Middleware;
|
||||
using Ocelot.Requester;
|
||||
using Ocelot.Responses;
|
||||
using Shouldly;
|
||||
@ -12,25 +22,37 @@ using Xunit;
|
||||
|
||||
namespace Ocelot.UnitTests.Requester
|
||||
{
|
||||
public class HttpClientBuilderTests
|
||||
public class HttpClientBuilderTests : IDisposable
|
||||
{
|
||||
private readonly HttpClientBuilder _builder;
|
||||
private readonly Mock<IDelegatingHandlerHandlerFactory> _factory;
|
||||
private IHttpClient _httpClient;
|
||||
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()
|
||||
{
|
||||
_cacheHandlers = new Mock<IHttpClientCache>();
|
||||
_logger = new Mock<IOcelotLogger>();
|
||||
_factory = new Mock<IDelegatingHandlerHandlerFactory>();
|
||||
_builder = new HttpClientBuilder(_factory.Object);
|
||||
_builder = new HttpClientBuilder(_factory.Object, _cacheHandlers.Object, _logger.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void should_build_http_client()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder()
|
||||
.WithIsQos(false)
|
||||
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false))
|
||||
.WithReRouteKey("")
|
||||
.Build();
|
||||
|
||||
this.Given(x => GivenTheFactoryReturns())
|
||||
.And(x => GivenARequest())
|
||||
.And(x => GivenARequest(reRoute))
|
||||
.When(x => WhenIBuild())
|
||||
.Then(x => ThenTheHttpClientShouldNotBeNull())
|
||||
.BDDfy();
|
||||
@ -39,6 +61,12 @@ namespace Ocelot.UnitTests.Requester
|
||||
[Fact]
|
||||
public void should_call_delegating_handlers_in_order()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder()
|
||||
.WithIsQos(false)
|
||||
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false))
|
||||
.WithReRouteKey("")
|
||||
.Build();
|
||||
|
||||
var fakeOne = new FakeDelegatingHandler();
|
||||
var fakeTwo = new FakeDelegatingHandler();
|
||||
|
||||
@ -49,7 +77,7 @@ namespace Ocelot.UnitTests.Requester
|
||||
};
|
||||
|
||||
this.Given(x => GivenTheFactoryReturns(handlers))
|
||||
.And(x => GivenARequest())
|
||||
.And(x => GivenARequest(reRoute))
|
||||
.And(x => WhenIBuild())
|
||||
.When(x => WhenICallTheClient())
|
||||
.Then(x => ThenTheFakeAreHandledInOrder(fakeOne, fakeTwo))
|
||||
@ -57,12 +85,95 @@ namespace Ocelot.UnitTests.Requester
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void GivenARequest()
|
||||
[Fact]
|
||||
public void should_re_use_cookies_from_container()
|
||||
{
|
||||
var reRoute = new DownstreamReRouteBuilder().WithIsQos(false)
|
||||
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false)).WithReRouteKey("").Build();
|
||||
var reRoute = new DownstreamReRouteBuilder()
|
||||
.WithIsQos(false)
|
||||
.WithHttpHandlerOptions(new HttpHandlerOptions(false, true, false))
|
||||
.WithReRouteKey("")
|
||||
.Build();
|
||||
|
||||
_request = reRoute;
|
||||
this.Given(_ => GivenADownstreamService())
|
||||
.And(_ => GivenARequest(reRoute))
|
||||
.And(_ => GivenTheFactoryReturnsNothing())
|
||||
.And(_ => WhenIBuild())
|
||||
.And(_ => WhenICallTheClient("http://localhost:5003"))
|
||||
.And(_ => ThenTheCookieIsSet())
|
||||
.And(_ => GivenTheClientIsCached())
|
||||
.And(_ => WhenIBuild())
|
||||
.When(_ => WhenICallTheClient("http://localhost:5003"))
|
||||
.Then(_ => ThenTheResponseIsOk())
|
||||
.BDDfy();
|
||||
}
|
||||
|
||||
private void GivenTheClientIsCached()
|
||||
{
|
||||
_cacheHandlers.Setup(x => x.Get(It.IsAny<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()
|
||||
@ -88,6 +199,14 @@ namespace Ocelot.UnitTests.Requester
|
||||
.Setup(x => x.Get(It.IsAny<DownstreamReRoute>()))
|
||||
.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)
|
||||
{
|
||||
@ -98,12 +217,18 @@ namespace Ocelot.UnitTests.Requester
|
||||
|
||||
private void WhenIBuild()
|
||||
{
|
||||
_httpClient = _builder.Create(_request);
|
||||
_httpClient = _builder.Create(_context);
|
||||
}
|
||||
|
||||
private void ThenTheHttpClientShouldNotBeNull()
|
||||
{
|
||||
_httpClient.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_response?.Dispose();
|
||||
_host?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,10 @@ namespace Ocelot.UnitTests.Requester
|
||||
.Setup(x => x.CreateLogger<HttpClientHttpRequester>())
|
||||
.Returns(_logger.Object);
|
||||
_cacheHandlers = new Mock<IHttpClientCache>();
|
||||
_httpClientRequester = new HttpClientHttpRequester(_loggerFactory.Object, _cacheHandlers.Object, _house.Object);
|
||||
_httpClientRequester = new HttpClientHttpRequester(
|
||||
_loggerFactory.Object,
|
||||
_cacheHandlers.Object,
|
||||
_house.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Butterfly.Client.Tracing;
|
||||
using Moq;
|
||||
using Ocelot.Infrastructure.RequestData;
|
||||
using Ocelot.Requester;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
@ -10,11 +11,13 @@ namespace Ocelot.UnitTests.Requester
|
||||
{
|
||||
private TracingHandlerFactory _factory;
|
||||
private Mock<IServiceTracer> _tracer;
|
||||
private Mock<IRequestScopedDataRepository> _repo;
|
||||
|
||||
public TracingHandlerFactoryTests()
|
||||
{
|
||||
_tracer = new Mock<IServiceTracer>();
|
||||
_factory = new TracingHandlerFactory(_tracer.Object);
|
||||
_repo = new Mock<IRequestScopedDataRepository>();
|
||||
_factory = new TracingHandlerFactory(_tracer.Object, _repo.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -120,7 +120,7 @@ namespace Ocelot.UnitTests.Responder
|
||||
// If this test fails then it's because the number of error codes has changed.
|
||||
// You should make the appropriate changes to the test cases here to ensure
|
||||
// they cover all the error codes, and then modify this assertion.
|
||||
Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(33, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?");
|
||||
Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(34, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?");
|
||||
}
|
||||
|
||||
private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode)
|
||||
|
@ -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