mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 06:42:50 +08:00
commit
112b4b69c6
3
.gitignore
vendored
3
.gitignore
vendored
@ -249,3 +249,6 @@ test/Ocelot.AcceptanceTests/configuration.json
|
|||||||
_build/
|
_build/
|
||||||
_static/
|
_static/
|
||||||
_templates/
|
_templates/
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
70
docs/features/headerstransformation.rst
Normal file
70
docs/features/headerstransformation.rst
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
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
|
||||||
|
^^^^^^
|
||||||
|
|
||||||
|
In order to transform a header first we specify the header key and then the type of transform we want e.g.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
"Test": "http://www.bbc.co.uk/, http://ocelot.com/"
|
||||||
|
|
||||||
|
The key is "Test" and the value is "http://www.bbc.co.uk/, http://ocelot.com/". The value is saying replace http://www.bbc.co.uk/ with http://ocelot.com/. The syntax is {find}, {replace}. Hopefully pretty simple. There are examples below that explain more.
|
||||||
|
|
||||||
|
Pre Downstream Request
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Add the following to a ReRoute in configuration.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This header will be changed before the request downstream and will be sent to the downstream server.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
"UpstreamHeaderTransform": {
|
||||||
|
"Test": "http://www.bbc.co.uk/, http://ocelot.com/"
|
||||||
|
},
|
||||||
|
|
||||||
|
Post Downstream Request
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Add the following to a ReRoute in configuration.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This transformation will take place after Ocelot has received the response from the downstream service.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
"DownstreamHeaderTransform": {
|
||||||
|
"Test": "http://www.bbc.co.uk/, http://ocelot.com/"
|
||||||
|
},
|
||||||
|
|
||||||
|
Placeholders
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Ocelot allows placeholders that can be used in header transformation. At the moment there is only one placeholder.
|
||||||
|
|
||||||
|
{BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value.
|
||||||
|
|
||||||
|
Handling 302 Redirects
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
Ocelot will by default automatically follow redirects however if you want to return the location header to the client you might want to change the location to be Ocelot not the downstream service. Ocelot allows this with the following configuration.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
"DownstreamHeaderTransform": {
|
||||||
|
"Location": "http://www.bbc.co.uk/, http://ocelot.com/"
|
||||||
|
},
|
||||||
|
"HttpHandlerOptions": {
|
||||||
|
"AllowAutoRedirect": false,
|
||||||
|
},
|
||||||
|
|
||||||
|
or you could use the BaseUrl placeholder.
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
"DownstreamHeaderTransform": {
|
||||||
|
"Location": "http://localhost:6773, {BaseUrl}"
|
||||||
|
},
|
||||||
|
"HttpHandlerOptions": {
|
||||||
|
"AllowAutoRedirect": false,
|
||||||
|
},
|
||||||
|
|
||||||
|
Ocelot will not try and replace the location header returned by the downstream service with its own URL.
|
@ -24,7 +24,7 @@ the following.
|
|||||||
"DownstreamPathTemplate": "/api/posts/{postId}",
|
"DownstreamPathTemplate": "/api/posts/{postId}",
|
||||||
"DownstreamScheme": "https",
|
"DownstreamScheme": "https",
|
||||||
"DownstreamPort": 80,
|
"DownstreamPort": 80,
|
||||||
"DownstreamHost" "localhost",
|
"DownstreamHost":"localhost",
|
||||||
"UpstreamPathTemplate": "/posts/{postId}",
|
"UpstreamPathTemplate": "/posts/{postId}",
|
||||||
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
"UpstreamHttpMethod": [ "Put", "Delete" ]
|
||||||
}
|
}
|
||||||
@ -46,7 +46,7 @@ You can also do a catch all type of ReRoute e.g.
|
|||||||
"DownstreamPathTemplate": "/api/{everything}",
|
"DownstreamPathTemplate": "/api/{everything}",
|
||||||
"DownstreamScheme": "https",
|
"DownstreamScheme": "https",
|
||||||
"DownstreamPort": 80,
|
"DownstreamPort": 80,
|
||||||
"DownstreamHost" "localhost",
|
"DownstreamHost":"localhost",
|
||||||
"UpstreamPathTemplate": "/{everything}",
|
"UpstreamPathTemplate": "/{everything}",
|
||||||
"UpstreamHttpMethod": [ "Get", "Post" ]
|
"UpstreamHttpMethod": [ "Get", "Post" ]
|
||||||
}
|
}
|
||||||
@ -75,7 +75,7 @@ Ocelot's routing also supports a catch all style routing where the user can spec
|
|||||||
"DownstreamPathTemplate": "/{url}",
|
"DownstreamPathTemplate": "/{url}",
|
||||||
"DownstreamScheme": "https",
|
"DownstreamScheme": "https",
|
||||||
"DownstreamPort": 80,
|
"DownstreamPort": 80,
|
||||||
"DownstreamHost" "localhost",
|
"DownstreamHost":"localhost",
|
||||||
"UpstreamPathTemplate": "/{url}",
|
"UpstreamPathTemplate": "/{url}",
|
||||||
"UpstreamHttpMethod": [ "Get" ]
|
"UpstreamHttpMethod": [ "Get" ]
|
||||||
}
|
}
|
||||||
@ -88,7 +88,7 @@ The catch all has a lower priority than any other ReRoute. If you also have the
|
|||||||
"DownstreamPathTemplate": "/",
|
"DownstreamPathTemplate": "/",
|
||||||
"DownstreamScheme": "https",
|
"DownstreamScheme": "https",
|
||||||
"DownstreamPort": 80,
|
"DownstreamPort": 80,
|
||||||
"DownstreamHost" "10.0.10.1",
|
"DownstreamHost":"10.0.10.1",
|
||||||
"UpstreamPathTemplate": "/",
|
"UpstreamPathTemplate": "/",
|
||||||
"UpstreamHttpMethod": [ "Get" ]
|
"UpstreamHttpMethod": [ "Get" ]
|
||||||
}
|
}
|
@ -27,6 +27,7 @@ Thanks for taking a look at the Ocelot documentation. Please use the left hand n
|
|||||||
features/raft
|
features/raft
|
||||||
features/caching
|
features/caching
|
||||||
features/qualityofservice
|
features/qualityofservice
|
||||||
|
features/headerstransformation
|
||||||
features/claimstransformation
|
features/claimstransformation
|
||||||
features/logging
|
features/logging
|
||||||
features/requestid
|
features/requestid
|
||||||
|
3
run-acceptance-tests.sh
Executable file
3
run-acceptance-tests.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
./build.sh --target RunAcceptanceTests
|
3
run-unit-tests.sh
Executable file
3
run-unit-tests.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
./build.sh --target RunUnitTests
|
@ -36,6 +36,9 @@ namespace Ocelot.Configuration.Builder
|
|||||||
private bool _useServiceDiscovery;
|
private bool _useServiceDiscovery;
|
||||||
private string _serviceName;
|
private string _serviceName;
|
||||||
|
|
||||||
|
private List<HeaderFindAndReplace> _upstreamHeaderFindAndReplace;
|
||||||
|
private List<HeaderFindAndReplace> _downstreamHeaderFindAndReplace;
|
||||||
|
|
||||||
public ReRouteBuilder WithLoadBalancer(string loadBalancer)
|
public ReRouteBuilder WithLoadBalancer(string loadBalancer)
|
||||||
{
|
{
|
||||||
_loadBalancer = loadBalancer;
|
_loadBalancer = loadBalancer;
|
||||||
@ -198,6 +201,18 @@ namespace Ocelot.Configuration.Builder
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithUpstreamHeaderFindAndReplace(List<HeaderFindAndReplace> upstreamHeaderFindAndReplace)
|
||||||
|
{
|
||||||
|
_upstreamHeaderFindAndReplace = upstreamHeaderFindAndReplace;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReRouteBuilder WithDownstreamHeaderFindAndReplace(List<HeaderFindAndReplace> downstreamHeaderFindAndReplace)
|
||||||
|
{
|
||||||
|
_downstreamHeaderFindAndReplace = downstreamHeaderFindAndReplace;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public ReRoute Build()
|
public ReRoute Build()
|
||||||
{
|
{
|
||||||
return new ReRoute(
|
return new ReRoute(
|
||||||
@ -226,7 +241,9 @@ namespace Ocelot.Configuration.Builder
|
|||||||
_rateLimitOptions,
|
_rateLimitOptions,
|
||||||
_httpHandlerOptions,
|
_httpHandlerOptions,
|
||||||
_useServiceDiscovery,
|
_useServiceDiscovery,
|
||||||
_serviceName);
|
_serviceName,
|
||||||
|
_upstreamHeaderFindAndReplace,
|
||||||
|
_downstreamHeaderFindAndReplace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ namespace Ocelot.Configuration.Creator
|
|||||||
private readonly IRegionCreator _regionCreator;
|
private readonly IRegionCreator _regionCreator;
|
||||||
private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
|
private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
|
||||||
private readonly IAdministrationPath _adminPath;
|
private readonly IAdministrationPath _adminPath;
|
||||||
|
private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator;
|
||||||
|
|
||||||
|
|
||||||
public FileOcelotConfigurationCreator(
|
public FileOcelotConfigurationCreator(
|
||||||
@ -53,9 +54,11 @@ namespace Ocelot.Configuration.Creator
|
|||||||
IRateLimitOptionsCreator rateLimitOptionsCreator,
|
IRateLimitOptionsCreator rateLimitOptionsCreator,
|
||||||
IRegionCreator regionCreator,
|
IRegionCreator regionCreator,
|
||||||
IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
|
IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
|
||||||
IAdministrationPath adminPath
|
IAdministrationPath adminPath,
|
||||||
|
IHeaderFindAndReplaceCreator headerFAndRCreator
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
_headerFAndRCreator = headerFAndRCreator;
|
||||||
_adminPath = adminPath;
|
_adminPath = adminPath;
|
||||||
_regionCreator = regionCreator;
|
_regionCreator = regionCreator;
|
||||||
_rateLimitOptionsCreator = rateLimitOptionsCreator;
|
_rateLimitOptionsCreator = rateLimitOptionsCreator;
|
||||||
@ -128,6 +131,8 @@ namespace Ocelot.Configuration.Creator
|
|||||||
|
|
||||||
var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileReRoute);
|
var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileReRoute);
|
||||||
|
|
||||||
|
var hAndRs = _headerFAndRCreator.Create(fileReRoute);
|
||||||
|
|
||||||
var reRoute = new ReRouteBuilder()
|
var reRoute = new ReRouteBuilder()
|
||||||
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
|
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
|
||||||
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
|
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
|
||||||
@ -155,6 +160,8 @@ namespace Ocelot.Configuration.Creator
|
|||||||
.WithHttpHandlerOptions(httpHandlerOptions)
|
.WithHttpHandlerOptions(httpHandlerOptions)
|
||||||
.WithServiceName(fileReRoute.ServiceName)
|
.WithServiceName(fileReRoute.ServiceName)
|
||||||
.WithUseServiceDiscovery(fileReRoute.UseServiceDiscovery)
|
.WithUseServiceDiscovery(fileReRoute.UseServiceDiscovery)
|
||||||
|
.WithUpstreamHeaderFindAndReplace(hAndRs.Upstream)
|
||||||
|
.WithDownstreamHeaderFindAndReplace(hAndRs.Downstream)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
return reRoute;
|
return reRoute;
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Creator
|
||||||
|
{
|
||||||
|
public class HeaderFindAndReplaceCreator : IHeaderFindAndReplaceCreator
|
||||||
|
{
|
||||||
|
private IBaseUrlFinder _finder;
|
||||||
|
private Dictionary<string, Func<string>> _placeholders;
|
||||||
|
|
||||||
|
public HeaderFindAndReplaceCreator(IBaseUrlFinder finder)
|
||||||
|
{
|
||||||
|
_finder = finder;
|
||||||
|
_placeholders = new Dictionary<string, Func<string>>();
|
||||||
|
_placeholders.Add("{BaseUrl}", () => {
|
||||||
|
return _finder.Find();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public HeaderTransformations Create(FileReRoute fileReRoute)
|
||||||
|
{
|
||||||
|
var upstream = new List<HeaderFindAndReplace>();
|
||||||
|
|
||||||
|
foreach(var input in fileReRoute.UpstreamHeaderTransform)
|
||||||
|
{
|
||||||
|
var hAndr = Map(input);
|
||||||
|
upstream.Add(hAndr);
|
||||||
|
}
|
||||||
|
|
||||||
|
var downstream = new List<HeaderFindAndReplace>();
|
||||||
|
|
||||||
|
foreach(var input in fileReRoute.DownstreamHeaderTransform)
|
||||||
|
{
|
||||||
|
var hAndr = Map(input);
|
||||||
|
downstream.Add(hAndr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HeaderTransformations(upstream, downstream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HeaderFindAndReplace Map(KeyValuePair<string,string> input)
|
||||||
|
{
|
||||||
|
var findAndReplace = input.Value.Split(",");
|
||||||
|
|
||||||
|
var replace = findAndReplace[1].TrimStart();
|
||||||
|
|
||||||
|
var startOfPlaceholder = replace.IndexOf("{");
|
||||||
|
if(startOfPlaceholder > -1)
|
||||||
|
{
|
||||||
|
var endOfPlaceholder = replace.IndexOf("}", startOfPlaceholder);
|
||||||
|
|
||||||
|
var placeholder = replace.Substring(startOfPlaceholder, startOfPlaceholder + (endOfPlaceholder + 1));
|
||||||
|
|
||||||
|
if(_placeholders.ContainsKey(placeholder))
|
||||||
|
{
|
||||||
|
var value = _placeholders[placeholder].Invoke();
|
||||||
|
replace = replace.Replace(placeholder, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hAndr = new HeaderFindAndReplace(input.Key, findAndReplace[0], replace, 0);
|
||||||
|
|
||||||
|
return hAndr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
src/Ocelot/Configuration/Creator/HeaderTransformations.cs
Normal file
17
src/Ocelot/Configuration/Creator/HeaderTransformations.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Creator
|
||||||
|
{
|
||||||
|
public class HeaderTransformations
|
||||||
|
{
|
||||||
|
public HeaderTransformations(List<HeaderFindAndReplace> upstream, List<HeaderFindAndReplace> downstream)
|
||||||
|
{
|
||||||
|
Upstream = upstream;
|
||||||
|
Downstream = downstream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<HeaderFindAndReplace> Upstream {get;private set;}
|
||||||
|
|
||||||
|
public List<HeaderFindAndReplace> Downstream {get;private set;}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
|
|
||||||
|
namespace Ocelot.Configuration.Creator
|
||||||
|
{
|
||||||
|
public interface IHeaderFindAndReplaceCreator
|
||||||
|
{
|
||||||
|
HeaderTransformations Create(FileReRoute fileReRoute);
|
||||||
|
}
|
||||||
|
}
|
@ -11,17 +11,21 @@ namespace Ocelot.Configuration.File
|
|||||||
AddClaimsToRequest = new Dictionary<string, string>();
|
AddClaimsToRequest = new Dictionary<string, string>();
|
||||||
RouteClaimsRequirement = new Dictionary<string, string>();
|
RouteClaimsRequirement = new Dictionary<string, string>();
|
||||||
AddQueriesToRequest = new Dictionary<string, string>();
|
AddQueriesToRequest = new Dictionary<string, string>();
|
||||||
|
DownstreamHeaderTransform = new Dictionary<string, string>();
|
||||||
FileCacheOptions = new FileCacheOptions();
|
FileCacheOptions = new FileCacheOptions();
|
||||||
QoSOptions = new FileQoSOptions();
|
QoSOptions = new FileQoSOptions();
|
||||||
RateLimitOptions = new FileRateLimitRule();
|
RateLimitOptions = new FileRateLimitRule();
|
||||||
AuthenticationOptions = new FileAuthenticationOptions();
|
AuthenticationOptions = new FileAuthenticationOptions();
|
||||||
HttpHandlerOptions = new FileHttpHandlerOptions();
|
HttpHandlerOptions = new FileHttpHandlerOptions();
|
||||||
|
UpstreamHeaderTransform = new Dictionary<string, string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string DownstreamPathTemplate { get; set; }
|
public string DownstreamPathTemplate { get; set; }
|
||||||
public string UpstreamPathTemplate { get; set; }
|
public string UpstreamPathTemplate { get; set; }
|
||||||
public List<string> UpstreamHttpMethod { get; set; }
|
public List<string> UpstreamHttpMethod { get; set; }
|
||||||
public Dictionary<string, string> AddHeadersToRequest { get; set; }
|
public Dictionary<string, string> AddHeadersToRequest { get; set; }
|
||||||
|
public Dictionary<string, string> UpstreamHeaderTransform { get; set; }
|
||||||
|
public Dictionary<string, string> DownstreamHeaderTransform { get; set; }
|
||||||
public Dictionary<string, string> AddClaimsToRequest { get; set; }
|
public Dictionary<string, string> AddClaimsToRequest { get; set; }
|
||||||
public Dictionary<string, string> RouteClaimsRequirement { get; set; }
|
public Dictionary<string, string> RouteClaimsRequirement { get; set; }
|
||||||
public Dictionary<string, string> AddQueriesToRequest { get; set; }
|
public Dictionary<string, string> AddQueriesToRequest { get; set; }
|
||||||
|
20
src/Ocelot/Configuration/HeaderFindAndReplace.cs
Normal file
20
src/Ocelot/Configuration/HeaderFindAndReplace.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
namespace Ocelot.Configuration
|
||||||
|
{
|
||||||
|
public class HeaderFindAndReplace
|
||||||
|
{
|
||||||
|
public HeaderFindAndReplace(string key, string find, string replace, int index)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
Find = find;
|
||||||
|
Replace = replace;
|
||||||
|
Index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Key {get;}
|
||||||
|
public string Find {get;}
|
||||||
|
public string Replace {get;}
|
||||||
|
|
||||||
|
// only index 0 for now..
|
||||||
|
public int Index {get;}
|
||||||
|
}
|
||||||
|
}
|
@ -32,8 +32,12 @@ namespace Ocelot.Configuration
|
|||||||
RateLimitOptions ratelimitOptions,
|
RateLimitOptions ratelimitOptions,
|
||||||
HttpHandlerOptions httpHandlerOptions,
|
HttpHandlerOptions httpHandlerOptions,
|
||||||
bool useServiceDiscovery,
|
bool useServiceDiscovery,
|
||||||
string serviceName)
|
string serviceName,
|
||||||
|
List<HeaderFindAndReplace> upstreamHeadersFindAndReplace,
|
||||||
|
List<HeaderFindAndReplace> downstreamHeadersFindAndReplace)
|
||||||
{
|
{
|
||||||
|
DownstreamHeadersFindAndReplace = downstreamHeadersFindAndReplace;
|
||||||
|
UpstreamHeadersFindAndReplace = upstreamHeadersFindAndReplace;
|
||||||
ServiceName = serviceName;
|
ServiceName = serviceName;
|
||||||
UseServiceDiscovery = useServiceDiscovery;
|
UseServiceDiscovery = useServiceDiscovery;
|
||||||
ReRouteKey = reRouteKey;
|
ReRouteKey = reRouteKey;
|
||||||
@ -91,5 +95,8 @@ namespace Ocelot.Configuration
|
|||||||
public HttpHandlerOptions HttpHandlerOptions { get; private set; }
|
public HttpHandlerOptions HttpHandlerOptions { get; private set; }
|
||||||
public bool UseServiceDiscovery {get;private set;}
|
public bool UseServiceDiscovery {get;private set;}
|
||||||
public string ServiceName {get;private set;}
|
public string ServiceName {get;private set;}
|
||||||
|
public List<HeaderFindAndReplace> UpstreamHeadersFindAndReplace {get;private set;}
|
||||||
|
public List<HeaderFindAndReplace> DownstreamHeadersFindAndReplace {get;private set;}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -19,6 +19,14 @@ namespace Ocelot.Configuration.Validator
|
|||||||
.Must(path => path.StartsWith("/"))
|
.Must(path => path.StartsWith("/"))
|
||||||
.WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash");
|
.WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash");
|
||||||
|
|
||||||
|
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
||||||
|
.Must(path => !path.Contains("//"))
|
||||||
|
.WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.");
|
||||||
|
|
||||||
|
RuleFor(reRoute => reRoute.DownstreamPathTemplate)
|
||||||
|
.Must(path => !path.Contains("//"))
|
||||||
|
.WithMessage("{PropertyName} {PropertyValue} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.");
|
||||||
|
|
||||||
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
RuleFor(reRoute => reRoute.UpstreamPathTemplate)
|
||||||
.Must(path => path.StartsWith("/"))
|
.Must(path => path.StartsWith("/"))
|
||||||
.WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash");
|
.WithMessage("{PropertyName} {PropertyValue} doesnt start with forward slash");
|
||||||
|
@ -75,6 +75,9 @@ namespace Ocelot.DependencyInjection
|
|||||||
|
|
||||||
//add ocelot services...
|
//add ocelot services...
|
||||||
_services.Configure<FileConfiguration>(configurationRoot);
|
_services.Configure<FileConfiguration>(configurationRoot);
|
||||||
|
_services.TryAddSingleton<IHttpResponseHeaderReplacer, HttpResponseHeaderReplacer>();
|
||||||
|
_services.TryAddSingleton<IHttpContextRequestHeaderReplacer, HttpContextRequestHeaderReplacer>();
|
||||||
|
_services.TryAddSingleton<IHeaderFindAndReplaceCreator, HeaderFindAndReplaceCreator>();
|
||||||
_services.TryAddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>();
|
_services.TryAddSingleton<IOcelotConfigurationCreator, FileOcelotConfigurationCreator>();
|
||||||
_services.TryAddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();
|
_services.TryAddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();
|
||||||
_services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>();
|
_services.TryAddSingleton<IConfigurationValidator, FileConfigurationFluentValidator>();
|
||||||
|
@ -13,5 +13,6 @@ namespace Ocelot.DownstreamRouteFinder
|
|||||||
}
|
}
|
||||||
public List<PlaceholderNameAndValue> TemplatePlaceholderNameAndValues { get; private set; }
|
public List<PlaceholderNameAndValue> TemplatePlaceholderNameAndValues { get; private set; }
|
||||||
public ReRoute ReRoute { get; private set; }
|
public ReRoute ReRoute { get; private set; }
|
||||||
|
public object UpstreamHeadersFindAndReplace {get;private set;}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -85,7 +85,10 @@ namespace Ocelot.Errors.Middleware
|
|||||||
|
|
||||||
private void SetInternalServerErrorOnResponse(HttpContext context)
|
private void SetInternalServerErrorOnResponse(HttpContext context)
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = 500;
|
if (!context.Response.HasStarted)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 500;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string CreateMessage(HttpContext context, Exception e)
|
private string CreateMessage(HttpContext context, Exception e)
|
||||||
|
25
src/Ocelot/Headers/HttpContextRequestHeaderReplacer.cs
Normal file
25
src/Ocelot/Headers/HttpContextRequestHeaderReplacer.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Headers
|
||||||
|
{
|
||||||
|
public class HttpContextRequestHeaderReplacer : IHttpContextRequestHeaderReplacer
|
||||||
|
{
|
||||||
|
public Response Replace(HttpContext context, List<HeaderFindAndReplace> fAndRs)
|
||||||
|
{
|
||||||
|
foreach (var f in fAndRs)
|
||||||
|
{
|
||||||
|
if(context.Request.Headers.TryGetValue(f.Key, out var values))
|
||||||
|
{
|
||||||
|
var replaced = values[f.Index].Replace(f.Find, f.Replace);
|
||||||
|
context.Request.Headers.Remove(f.Key);
|
||||||
|
context.Request.Headers.Add(f.Key, replaced);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OkResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
src/Ocelot/Headers/HttpResponseHeaderReplacer.cs
Normal file
26
src/Ocelot/Headers/HttpResponseHeaderReplacer.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Headers
|
||||||
|
{
|
||||||
|
public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer
|
||||||
|
{
|
||||||
|
public Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs)
|
||||||
|
{
|
||||||
|
foreach (var f in fAndRs)
|
||||||
|
{
|
||||||
|
if(response.Headers.TryGetValues(f.Key, out var values))
|
||||||
|
{
|
||||||
|
var replaced = values.ToList()[f.Index].Replace(f.Find, f.Replace);
|
||||||
|
response.Headers.Remove(f.Key);
|
||||||
|
response.Headers.Add(f.Key, replaced);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OkResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/Ocelot/Headers/IHttpContextRequestHeaderReplacer.cs
Normal file
12
src/Ocelot/Headers/IHttpContextRequestHeaderReplacer.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Headers
|
||||||
|
{
|
||||||
|
public interface IHttpContextRequestHeaderReplacer
|
||||||
|
{
|
||||||
|
Response Replace(HttpContext context, List<HeaderFindAndReplace> fAndRs);
|
||||||
|
}
|
||||||
|
}
|
12
src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs
Normal file
12
src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
|
||||||
|
namespace Ocelot.Headers
|
||||||
|
{
|
||||||
|
public interface IHttpResponseHeaderReplacer
|
||||||
|
{
|
||||||
|
Response Replace(HttpResponseMessage response, List<HeaderFindAndReplace> fAndRs);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
|
||||||
|
namespace Ocelot.Headers.Middleware
|
||||||
|
{
|
||||||
|
public class HttpHeadersTransformationMiddleware : OcelotMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly IOcelotLogger _logger;
|
||||||
|
private readonly IHttpContextRequestHeaderReplacer _preReplacer;
|
||||||
|
private readonly IHttpResponseHeaderReplacer _postReplacer;
|
||||||
|
|
||||||
|
public HttpHeadersTransformationMiddleware(RequestDelegate next,
|
||||||
|
IOcelotLoggerFactory loggerFactory,
|
||||||
|
IRequestScopedDataRepository requestScopedDataRepository,
|
||||||
|
IHttpContextRequestHeaderReplacer preReplacer,
|
||||||
|
IHttpResponseHeaderReplacer postReplacer)
|
||||||
|
: base(requestScopedDataRepository)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_postReplacer = postReplacer;
|
||||||
|
_preReplacer = preReplacer;
|
||||||
|
_logger = loggerFactory.CreateLogger<HttpHeadersTransformationMiddleware>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
var preFAndRs = this.DownstreamRoute.ReRoute.UpstreamHeadersFindAndReplace;
|
||||||
|
|
||||||
|
_preReplacer.Replace(context, preFAndRs);
|
||||||
|
|
||||||
|
await _next.Invoke(context);
|
||||||
|
|
||||||
|
var postFAndRs = this.DownstreamRoute.ReRoute.DownstreamHeadersFindAndReplace;
|
||||||
|
|
||||||
|
_postReplacer.Replace(HttpResponseMessage, postFAndRs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
||||||
|
namespace Ocelot.Headers.Middleware
|
||||||
|
{
|
||||||
|
public static class HttpHeadersTransformationMiddlewareExtensions
|
||||||
|
{
|
||||||
|
public static IApplicationBuilder UseHttpHeadersTransformationMiddleware(this IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
return builder.UseMiddleware<HttpHeadersTransformationMiddleware>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -123,7 +123,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers
|
|||||||
|
|
||||||
foreach (var service in services)
|
foreach (var service in services)
|
||||||
{
|
{
|
||||||
var exists = _leases.FirstOrDefault(l => l.HostAndPort.ToString() == service.HostAndPort.ToString());
|
var exists = _leases.FirstOrDefault(l => l.HostAndPort.DownstreamHost == service.HostAndPort.DownstreamHost && l.HostAndPort.DownstreamPort == service.HostAndPort.DownstreamPort);
|
||||||
|
|
||||||
if (exists == null)
|
if (exists == null)
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Internal;
|
||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
|
||||||
namespace Ocelot.Logging
|
namespace Ocelot.Logging
|
||||||
@ -22,21 +23,21 @@ namespace Ocelot.Logging
|
|||||||
{
|
{
|
||||||
var requestId = GetOcelotRequestId();
|
var requestId = GetOcelotRequestId();
|
||||||
var previousRequestId = GetOcelotPreviousRequestId();
|
var previousRequestId = GetOcelotPreviousRequestId();
|
||||||
_logger.LogTrace("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message},", requestId, previousRequestId, message, args);
|
_logger.LogTrace("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message},", requestId, previousRequestId, new FormattedLogValues(message, args).ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LogDebug(string message, params object[] args)
|
public void LogDebug(string message, params object[] args)
|
||||||
{
|
{
|
||||||
var requestId = GetOcelotRequestId();
|
var requestId = GetOcelotRequestId();
|
||||||
var previousRequestId = GetOcelotPreviousRequestId();
|
var previousRequestId = GetOcelotPreviousRequestId();
|
||||||
_logger.LogDebug("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message},", requestId, previousRequestId, message, args);
|
_logger.LogDebug("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message},", requestId, previousRequestId, new FormattedLogValues(message, args).ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LogInformation(string message, params object[] args)
|
public void LogInformation(string message, params object[] args)
|
||||||
{
|
{
|
||||||
var requestId = GetOcelotRequestId();
|
var requestId = GetOcelotRequestId();
|
||||||
var previousRequestId = GetOcelotPreviousRequestId();
|
var previousRequestId = GetOcelotPreviousRequestId();
|
||||||
_logger.LogInformation("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message},", requestId, previousRequestId, message, args);
|
_logger.LogInformation("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message},", requestId, previousRequestId, new FormattedLogValues(message, args).ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LogError(string message, Exception exception)
|
public void LogError(string message, Exception exception)
|
||||||
@ -50,7 +51,7 @@ namespace Ocelot.Logging
|
|||||||
{
|
{
|
||||||
var requestId = GetOcelotRequestId();
|
var requestId = GetOcelotRequestId();
|
||||||
var previousRequestId = GetOcelotPreviousRequestId();
|
var previousRequestId = GetOcelotPreviousRequestId();
|
||||||
_logger.LogError("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}", requestId, previousRequestId, message, args);
|
_logger.LogError("requestId: {requestId}, previousRequestId: {previousRequestId}, message: {message}", requestId, previousRequestId, new FormattedLogValues(message, args).ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LogCritical(string message, Exception exception)
|
public void LogCritical(string message, Exception exception)
|
||||||
@ -66,7 +67,7 @@ namespace Ocelot.Logging
|
|||||||
|
|
||||||
if (requestId == null || requestId.IsError)
|
if (requestId == null || requestId.IsError)
|
||||||
{
|
{
|
||||||
return $"no request id";
|
return "no request id";
|
||||||
}
|
}
|
||||||
|
|
||||||
return requestId.Data;
|
return requestId.Data;
|
||||||
@ -78,7 +79,7 @@ namespace Ocelot.Logging
|
|||||||
|
|
||||||
if (requestId == null || requestId.IsError)
|
if (requestId == null || requestId.IsError)
|
||||||
{
|
{
|
||||||
return $"no previous request id";
|
return "no previous request id";
|
||||||
}
|
}
|
||||||
|
|
||||||
return requestId.Data;
|
return requestId.Data;
|
||||||
|
@ -86,12 +86,15 @@ namespace Ocelot.Middleware
|
|||||||
// This is registered first so it can catch any errors and issue an appropriate response
|
// This is registered first so it can catch any errors and issue an appropriate response
|
||||||
builder.UseResponderMiddleware();
|
builder.UseResponderMiddleware();
|
||||||
|
|
||||||
// Initialises downstream request
|
|
||||||
builder.UseDownstreamRequestInitialiser();
|
|
||||||
|
|
||||||
// Then we get the downstream route information
|
// Then we get the downstream route information
|
||||||
builder.UseDownstreamRouteFinderMiddleware();
|
builder.UseDownstreamRouteFinderMiddleware();
|
||||||
|
|
||||||
|
// Now we have the ds route we can transform headers and stuff?
|
||||||
|
builder.UseHttpHeadersTransformationMiddleware();
|
||||||
|
|
||||||
|
// Initialises downstream request
|
||||||
|
builder.UseDownstreamRequestInitialiser();
|
||||||
|
|
||||||
// We check whether the request is ratelimit, and if there is no continue processing
|
// We check whether the request is ratelimit, and if there is no continue processing
|
||||||
builder.UseRateLimiting();
|
builder.UseRateLimiting();
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ namespace Ocelot.Request.Builder
|
|||||||
bool useCookieContainer,
|
bool useCookieContainer,
|
||||||
bool allowAutoRedirect)
|
bool allowAutoRedirect)
|
||||||
{
|
{
|
||||||
return new OkResponse<Request>(new Request(httpRequestMessage, isQos, qosProvider, useCookieContainer, allowAutoRedirect));
|
return new OkResponse<Request>(new Request(httpRequestMessage, isQos, qosProvider, allowAutoRedirect, useCookieContainer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
190
test/Ocelot.AcceptanceTests/HeaderTests.cs
Normal file
190
test/Ocelot.AcceptanceTests/HeaderTests.cs
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Ocelot.AcceptanceTests
|
||||||
|
{
|
||||||
|
public class HeaderTests : IDisposable
|
||||||
|
{
|
||||||
|
private IWebHost _builder;
|
||||||
|
private readonly Steps _steps;
|
||||||
|
private string _downstreamPath;
|
||||||
|
|
||||||
|
public HeaderTests()
|
||||||
|
{
|
||||||
|
_steps = new Steps();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_transform_upstream_header()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHost = "localhost",
|
||||||
|
DownstreamPort = 51879,
|
||||||
|
UpstreamPathTemplate = "/",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
UpstreamHeaderTransform = new Dictionary<string,string>
|
||||||
|
{
|
||||||
|
{"Laz", "D, GP"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Laz"))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.And(x => _steps.GivenIAddAHeader("Laz", "D"))
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseBodyShouldBe("GP"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_transform_downstream_header()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHost = "localhost",
|
||||||
|
DownstreamPort = 51879,
|
||||||
|
UpstreamPathTemplate = "/",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
DownstreamHeaderTransform = new Dictionary<string,string>
|
||||||
|
{
|
||||||
|
{"Location", "http://www.bbc.co.uk/, http://ocelot.com/"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Location", "http://www.bbc.co.uk/"))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseHeaderIs("Location", "http://ocelot.com/"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_fix_issue_190()
|
||||||
|
{
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHost = "localhost",
|
||||||
|
DownstreamPort = 6773,
|
||||||
|
UpstreamPathTemplate = "/",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
DownstreamHeaderTransform = new Dictionary<string,string>
|
||||||
|
{
|
||||||
|
{"Location", "http://localhost:6773, {BaseUrl}"}
|
||||||
|
},
|
||||||
|
HttpHandlerOptions = new FileHttpHandlerOptions
|
||||||
|
{
|
||||||
|
AllowAutoRedirect = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:6773", "/", 302, "Location", "http://localhost:6773/pay/Receive"))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Redirect))
|
||||||
|
.And(x => _steps.ThenTheResponseHeaderIs("Location", "http://localhost:5000/pay/Receive"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey)
|
||||||
|
{
|
||||||
|
_builder = new WebHostBuilder()
|
||||||
|
.UseUrls(baseUrl)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UsePathBase(basePath);
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
if(context.Request.Headers.TryGetValue(headerKey, out var values))
|
||||||
|
{
|
||||||
|
var result = values.First();
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
await context.Response.WriteAsync(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builder.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey, string headerValue)
|
||||||
|
{
|
||||||
|
_builder = new WebHostBuilder()
|
||||||
|
.UseUrls(baseUrl)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UsePathBase(basePath);
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
context.Response.OnStarting(() => {
|
||||||
|
context.Response.Headers.Add(headerKey, headerValue);
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builder.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath)
|
||||||
|
{
|
||||||
|
_downstreamPath.ShouldBe(expectedDownstreamPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_builder?.Dispose();
|
||||||
|
_steps.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,74 @@ namespace Ocelot.AcceptanceTests
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_use_service_discovery_and_load_balance_request()
|
public void should_use_service_discovery_and_load_balance_request()
|
||||||
|
{
|
||||||
|
var consulPort = 8502;
|
||||||
|
var serviceName = "product";
|
||||||
|
var downstreamServiceOneUrl = "http://localhost:50881";
|
||||||
|
var downstreamServiceTwoUrl = "http://localhost:50882";
|
||||||
|
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
||||||
|
var serviceEntryOne = new ServiceEntry()
|
||||||
|
{
|
||||||
|
Service = new AgentService()
|
||||||
|
{
|
||||||
|
Service = serviceName,
|
||||||
|
Address = "localhost",
|
||||||
|
Port = 50881,
|
||||||
|
ID = Guid.NewGuid().ToString(),
|
||||||
|
Tags = new string[0]
|
||||||
|
},
|
||||||
|
};
|
||||||
|
var serviceEntryTwo = new ServiceEntry()
|
||||||
|
{
|
||||||
|
Service = new AgentService()
|
||||||
|
{
|
||||||
|
Service = serviceName,
|
||||||
|
Address = "localhost",
|
||||||
|
Port = 50882,
|
||||||
|
ID = Guid.NewGuid().ToString(),
|
||||||
|
Tags = new string[0]
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
UpstreamPathTemplate = "/",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
ServiceName = serviceName,
|
||||||
|
LoadBalancer = "LeastConnection",
|
||||||
|
UseServiceDiscovery = true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
|
{
|
||||||
|
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = consulPort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
|
||||||
|
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200))
|
||||||
|
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
|
||||||
|
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50))
|
||||||
|
.Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50))
|
||||||
|
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(24, 26))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_send_request_to_service_after_it_becomes_available()
|
||||||
{
|
{
|
||||||
var consulPort = 8501;
|
var consulPort = 8501;
|
||||||
var serviceName = "product";
|
var serviceName = "product";
|
||||||
@ -92,16 +160,47 @@ namespace Ocelot.AcceptanceTests
|
|||||||
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
|
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50))
|
.And(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 10))
|
||||||
.Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50))
|
.And(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(10))
|
||||||
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes())
|
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(4, 6))
|
||||||
|
.And(x => WhenIRemoveAService(serviceEntryTwo))
|
||||||
|
.And(x => GivenIResetCounters())
|
||||||
|
.And(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 10))
|
||||||
|
.And(x => ThenOnlyOneServiceHasBeenCalled())
|
||||||
|
.And(x => WhenIAddAServiceBackIn(serviceEntryTwo))
|
||||||
|
.And(x => GivenIResetCounters())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 10))
|
||||||
|
.Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(10))
|
||||||
|
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(4, 6))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenBothServicesCalledRealisticAmountOfTimes()
|
private void WhenIAddAServiceBackIn(ServiceEntry serviceEntryTwo)
|
||||||
{
|
{
|
||||||
_counterOne.ShouldBeInRange(24,26);
|
_serviceEntries.Add(serviceEntryTwo);
|
||||||
_counterOne.ShouldBeInRange(24,26);
|
}
|
||||||
|
|
||||||
|
private void ThenOnlyOneServiceHasBeenCalled()
|
||||||
|
{
|
||||||
|
_counterOne.ShouldBe(10);
|
||||||
|
_counterTwo.ShouldBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenIRemoveAService(ServiceEntry serviceEntryTwo)
|
||||||
|
{
|
||||||
|
_serviceEntries.Remove(serviceEntryTwo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenIResetCounters()
|
||||||
|
{
|
||||||
|
_counterOne = 0;
|
||||||
|
_counterTwo = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenBothServicesCalledRealisticAmountOfTimes(int bottom, int top)
|
||||||
|
{
|
||||||
|
_counterOne.ShouldBeInRange(bottom, top);
|
||||||
|
_counterOne.ShouldBeInRange(bottom, top);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected)
|
private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected)
|
||||||
|
@ -108,6 +108,12 @@ namespace Ocelot.AcceptanceTests
|
|||||||
_ocelotClient = _ocelotServer.CreateClient();
|
_ocelotClient = _ocelotServer.CreateClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ThenTheResponseHeaderIs(string key, string value)
|
||||||
|
{
|
||||||
|
var header = _response.Headers.GetValues(key);
|
||||||
|
header.First().ShouldBe(value);
|
||||||
|
}
|
||||||
|
|
||||||
public void GivenOcelotIsRunningUsingJsonSerializedCache()
|
public void GivenOcelotIsRunningUsingJsonSerializedCache()
|
||||||
{
|
{
|
||||||
_webHostBuilder = new WebHostBuilder();
|
_webHostBuilder = new WebHostBuilder();
|
||||||
@ -326,6 +332,11 @@ namespace Ocelot.AcceptanceTests
|
|||||||
_response = _ocelotClient.GetAsync(url).Result;
|
_response = _ocelotClient.GetAsync(url).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void GivenIAddAHeader(string key, string value)
|
||||||
|
{
|
||||||
|
_ocelotClient.DefaultRequestHeaders.Add(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times)
|
public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times)
|
||||||
{
|
{
|
||||||
var tasks = new Task[times];
|
var tasks = new Task[times];
|
||||||
|
230
test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs
Normal file
230
test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using Consul;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Ocelot.AcceptanceTests
|
||||||
|
{
|
||||||
|
public class TwoDownstreamServicesTests : IDisposable
|
||||||
|
{
|
||||||
|
private IWebHost _builderOne;
|
||||||
|
private IWebHost _builderTwo;
|
||||||
|
private IWebHost _fakeConsulBuilder;
|
||||||
|
private readonly Steps _steps;
|
||||||
|
private readonly List<ServiceEntry> _serviceEntries;
|
||||||
|
private string _downstreamPathOne;
|
||||||
|
private string _downstreamPathTwo;
|
||||||
|
|
||||||
|
public TwoDownstreamServicesTests()
|
||||||
|
{
|
||||||
|
_steps = new Steps();
|
||||||
|
_serviceEntries = new List<ServiceEntry>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_fix_issue_194()
|
||||||
|
{
|
||||||
|
var consulPort = 8503;
|
||||||
|
var downstreamServiceOneUrl = "http://localhost:8362";
|
||||||
|
var downstreamServiceTwoUrl = "http://localhost:8330";
|
||||||
|
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
||||||
|
|
||||||
|
// http://localhost:8362/api/user/info?id=1 is ok
|
||||||
|
// http://localhost:3164/api/user/info?id=1 is ok
|
||||||
|
// http://localhost:8330/api/product/info?id=1 is ok
|
||||||
|
// http://localhost:3164/api/product/info?id=1 is 404
|
||||||
|
|
||||||
|
// is my configuration.json
|
||||||
|
// {
|
||||||
|
// "ReRoutes": [
|
||||||
|
// //{
|
||||||
|
// // "DownstreamPathTemplate": "/{product}",
|
||||||
|
// // "DownstreamScheme": "http",
|
||||||
|
// // "UpstreamPathTemplate": "/{product}",
|
||||||
|
// // "UpstreamHttpMethod": [ "Get", "Post" ],
|
||||||
|
// // "ServiceName": "api-product",
|
||||||
|
// // "LoadBalancer": "LeastConnection",
|
||||||
|
// // "UseServiceDiscovery": true
|
||||||
|
// //},
|
||||||
|
// //{
|
||||||
|
// // "DownstreamPathTemplate": "/{user}",
|
||||||
|
// // "DownstreamScheme": "http",
|
||||||
|
// // "UpstreamPathTemplate": "/{user}",
|
||||||
|
// // "UpstreamHttpMethod": [ "Get", "Post" ],
|
||||||
|
// // "ServiceName": "api-user",
|
||||||
|
// // "LoadBalancer": "LeastConnection",
|
||||||
|
// // "UseServiceDiscovery": true
|
||||||
|
// //},
|
||||||
|
// {
|
||||||
|
// "DownstreamPathTemplate": "/api/user/{user}",
|
||||||
|
// "DownstreamScheme": "http",
|
||||||
|
// "DownstreamHost": "localhost",
|
||||||
|
// "DownstreamPort": 8362,
|
||||||
|
// "UpstreamPathTemplate": "/api/user/{user}",
|
||||||
|
// "UpstreamHttpMethod": [ "Get" ]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "DownstreamPathTemplate": "/api/product/{product}",
|
||||||
|
// "DownstreamScheme": "http",
|
||||||
|
// "DownstreamHost": "localhost",
|
||||||
|
// "DownstreamPort": 8330,
|
||||||
|
// "UpstreamPathTemplate": "//api/product/{product}",
|
||||||
|
// "UpstreamHttpMethod": [ "Get" ]
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "GlobalConfiguration": {
|
||||||
|
// "ServiceDiscoveryProvider": {
|
||||||
|
// "Host": "localhost",
|
||||||
|
// "Port": 8500
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
var configuration = new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/api/user/{user}",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHost = "localhost",
|
||||||
|
DownstreamPort = 8362,
|
||||||
|
UpstreamPathTemplate = "/api/user/{user}",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
},
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/api/product/{product}",
|
||||||
|
DownstreamScheme = "http",
|
||||||
|
DownstreamHost = "localhost",
|
||||||
|
DownstreamPort = 8330,
|
||||||
|
UpstreamPathTemplate = "/api/product/{product}",
|
||||||
|
UpstreamHttpMethod = new List<string> { "Get" },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GlobalConfiguration = new FileGlobalConfiguration()
|
||||||
|
{
|
||||||
|
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
||||||
|
{
|
||||||
|
Host = "localhost",
|
||||||
|
Port = consulPort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, "/api/user/info", 200, "user"))
|
||||||
|
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, "/api/product/info", 200, "product"))
|
||||||
|
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
|
||||||
|
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
||||||
|
.And(x => _steps.GivenOcelotIsRunning())
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/user/info?id=1"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseBodyShouldBe("user"))
|
||||||
|
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/product/info?id=1"))
|
||||||
|
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
||||||
|
.And(x => _steps.ThenTheResponseBodyShouldBe("product"))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url)
|
||||||
|
{
|
||||||
|
_fakeConsulBuilder = new WebHostBuilder()
|
||||||
|
.UseUrls(url)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.UseUrls(url)
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
if(context.Request.Path.Value == "/v1/health/service/product")
|
||||||
|
{
|
||||||
|
await context.Response.WriteJsonAsync(_serviceEntries);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_fakeConsulBuilder.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenProductServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody)
|
||||||
|
{
|
||||||
|
_builderOne = new WebHostBuilder()
|
||||||
|
.UseUrls(baseUrl)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UsePathBase(basePath);
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
_downstreamPathOne = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
|
||||||
|
|
||||||
|
if(_downstreamPathOne != basePath)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
await context.Response.WriteAsync("downstream path didnt match base path");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
await context.Response.WriteAsync(responseBody);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builderOne.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenProductServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody)
|
||||||
|
{
|
||||||
|
_builderTwo = new WebHostBuilder()
|
||||||
|
.UseUrls(baseUrl)
|
||||||
|
.UseKestrel()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseIISIntegration()
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
app.UsePathBase(basePath);
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
_downstreamPathTwo = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
|
||||||
|
|
||||||
|
if(_downstreamPathTwo != basePath)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
await context.Response.WriteAsync("downstream path didnt match base path");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
await context.Response.WriteAsync(responseBody);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_builderTwo.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_builderOne?.Dispose();
|
||||||
|
_builderTwo?.Dispose();
|
||||||
|
_steps.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,9 +46,11 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
.Then(x => x.ThenTheResultIsNotValid())
|
.Then(x => x.ThenTheResultIsNotValid())
|
||||||
.Then(x => x.ThenTheErrorIs<FileValidationFailedError>())
|
.Then(x => x.ThenTheErrorIs<FileValidationFailedError>())
|
||||||
.And(x => x.ThenTheErrorMessageAtPositionIs(0, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} doesnt start with forward slash"))
|
.And(x => x.ThenTheErrorMessageAtPositionIs(0, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} doesnt start with forward slash"))
|
||||||
.And(x => x.ThenTheErrorMessageAtPositionIs(1, "Upstream Path Template http://asdf.com doesnt start with forward slash"))
|
.And(x => x.ThenTheErrorMessageAtPositionIs(1, "Upstream Path Template http://asdf.com contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature."))
|
||||||
.And(x => x.ThenTheErrorMessageAtPositionIs(2, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} contains scheme"))
|
.And(x => x.ThenTheErrorMessageAtPositionIs(2, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature."))
|
||||||
.And(x => x.ThenTheErrorMessageAtPositionIs(3, "Upstream Path Template http://asdf.com contains scheme"))
|
.And(x => x.ThenTheErrorMessageAtPositionIs(3, "Upstream Path Template http://asdf.com doesnt start with forward slash"))
|
||||||
|
.And(x => x.ThenTheErrorMessageAtPositionIs(4, "Downstream Path Template http://www.bbc.co.uk/api/products/{productId} contains scheme"))
|
||||||
|
.And(x => x.ThenTheErrorMessageAtPositionIs(5, "Upstream Path Template http://asdf.com contains scheme"))
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,6 +114,50 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void configuration_is_invalid_if_upstream_url_contains_forward_slash_then_another_forward_slash()
|
||||||
|
{
|
||||||
|
this.Given(x => x.GivenAConfiguration(new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "/api/products/",
|
||||||
|
UpstreamPathTemplate = "//api/prod/",
|
||||||
|
DownstreamHost = "bbc.co.uk",
|
||||||
|
DownstreamPort = 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.When(x => x.WhenIValidateTheConfiguration())
|
||||||
|
.Then(x => x.ThenTheResultIsNotValid())
|
||||||
|
.And(x => x.ThenTheErrorMessageAtPositionIs(0, "Upstream Path Template //api/prod/ contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature."))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void configuration_is_invalid_if_downstream_url_contains_forward_slash_then_another_forward_slash()
|
||||||
|
{
|
||||||
|
this.Given(x => x.GivenAConfiguration(new FileConfiguration
|
||||||
|
{
|
||||||
|
ReRoutes = new List<FileReRoute>
|
||||||
|
{
|
||||||
|
new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamPathTemplate = "//api/products/",
|
||||||
|
UpstreamPathTemplate = "/api/prod/",
|
||||||
|
DownstreamHost = "bbc.co.uk",
|
||||||
|
DownstreamPort = 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.When(x => x.WhenIValidateTheConfiguration())
|
||||||
|
.Then(x => x.ThenTheResultIsNotValid())
|
||||||
|
.And(x => x.ThenTheErrorMessageAtPositionIs(0, "Downstream Path Template //api/products/ contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature."))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void configuration_is_valid_with_valid_authentication_provider()
|
public void configuration_is_valid_with_valid_authentication_provider()
|
||||||
{
|
{
|
||||||
|
@ -39,6 +39,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
private Mock<IRegionCreator> _regionCreator;
|
private Mock<IRegionCreator> _regionCreator;
|
||||||
private Mock<IHttpHandlerOptionsCreator> _httpHandlerOptionsCreator;
|
private Mock<IHttpHandlerOptionsCreator> _httpHandlerOptionsCreator;
|
||||||
private Mock<IAdministrationPath> _adminPath;
|
private Mock<IAdministrationPath> _adminPath;
|
||||||
|
private readonly Mock<IHeaderFindAndReplaceCreator> _headerFindAndReplaceCreator;
|
||||||
|
|
||||||
public FileConfigurationCreatorTests()
|
public FileConfigurationCreatorTests()
|
||||||
{
|
{
|
||||||
@ -56,6 +57,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
_regionCreator = new Mock<IRegionCreator>();
|
_regionCreator = new Mock<IRegionCreator>();
|
||||||
_httpHandlerOptionsCreator = new Mock<IHttpHandlerOptionsCreator>();
|
_httpHandlerOptionsCreator = new Mock<IHttpHandlerOptionsCreator>();
|
||||||
_adminPath = new Mock<IAdministrationPath>();
|
_adminPath = new Mock<IAdministrationPath>();
|
||||||
|
_headerFindAndReplaceCreator = new Mock<IHeaderFindAndReplaceCreator>();
|
||||||
|
|
||||||
_ocelotConfigurationCreator = new FileOcelotConfigurationCreator(
|
_ocelotConfigurationCreator = new FileOcelotConfigurationCreator(
|
||||||
_fileConfig.Object,
|
_fileConfig.Object,
|
||||||
@ -71,7 +73,8 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
_rateLimitOptions.Object,
|
_rateLimitOptions.Object,
|
||||||
_regionCreator.Object,
|
_regionCreator.Object,
|
||||||
_httpHandlerOptionsCreator.Object,
|
_httpHandlerOptionsCreator.Object,
|
||||||
_adminPath.Object);
|
_adminPath.Object,
|
||||||
|
_headerFindAndReplaceCreator.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -91,6 +94,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.And(x => x.GivenTheFollowingIsReturned(serviceProviderConfig))
|
.And(x => x.GivenTheFollowingIsReturned(serviceProviderConfig))
|
||||||
|
.And(x => GivenTheHeaderFindAndReplaceCreatorReturns())
|
||||||
.And(x => x.GivenTheConfigIsValid())
|
.And(x => x.GivenTheConfigIsValid())
|
||||||
.When(x => x.WhenICreateTheConfig())
|
.When(x => x.WhenICreateTheConfig())
|
||||||
.Then(x => x.ThenTheServiceProviderCreatorIsCalledCorrectly())
|
.Then(x => x.ThenTheServiceProviderCreatorIsCalledCorrectly())
|
||||||
@ -121,10 +125,12 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
||||||
|
.And(x => GivenTheHeaderFindAndReplaceCreatorReturns())
|
||||||
.And(x => x.GivenTheConfigIsValid())
|
.And(x => x.GivenTheConfigIsValid())
|
||||||
.And(x => x.GivenTheFollowingRegionIsReturned("region"))
|
.And(x => x.GivenTheFollowingRegionIsReturned("region"))
|
||||||
.When(x => x.WhenICreateTheConfig())
|
.When(x => x.WhenICreateTheConfig())
|
||||||
.Then(x => x.ThenTheRegionCreatorIsCalledCorrectly("region"))
|
.Then(x => x.ThenTheRegionCreatorIsCalledCorrectly("region"))
|
||||||
|
.And(x => x.ThenTheHeaderFindAndReplaceCreatorIsCalledCorrectly())
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,6 +154,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
.And(x => x.GivenTheConfigIsValid())
|
.And(x => x.GivenTheConfigIsValid())
|
||||||
|
.And(x => GivenTheHeaderFindAndReplaceCreatorReturns())
|
||||||
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
||||||
.When(x => x.WhenICreateTheConfig())
|
.When(x => x.WhenICreateTheConfig())
|
||||||
.Then(x => x.ThenTheRateLimitOptionsCreatorIsCalledCorrectly())
|
.Then(x => x.ThenTheRateLimitOptionsCreatorIsCalledCorrectly())
|
||||||
@ -187,6 +194,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
.And(x => x.GivenTheConfigIsValid())
|
.And(x => x.GivenTheConfigIsValid())
|
||||||
|
.And(x => GivenTheHeaderFindAndReplaceCreatorReturns())
|
||||||
.And(x => x.GivenTheFollowingOptionsAreReturned(serviceOptions))
|
.And(x => x.GivenTheFollowingOptionsAreReturned(serviceOptions))
|
||||||
.And(x => x.GivenTheQosOptionsCreatorReturns(expected))
|
.And(x => x.GivenTheQosOptionsCreatorReturns(expected))
|
||||||
.When(x => x.WhenICreateTheConfig())
|
.When(x => x.WhenICreateTheConfig())
|
||||||
@ -214,6 +222,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
.And(x => x.GivenTheConfigIsValid())
|
.And(x => x.GivenTheConfigIsValid())
|
||||||
|
.And(x => GivenTheHeaderFindAndReplaceCreatorReturns())
|
||||||
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
||||||
.When(x => x.WhenICreateTheConfig())
|
.When(x => x.WhenICreateTheConfig())
|
||||||
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
|
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
|
||||||
@ -248,6 +257,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
.And(x => x.GivenTheConfigIsValid())
|
.And(x => x.GivenTheConfigIsValid())
|
||||||
|
.And(x => GivenTheHeaderFindAndReplaceCreatorReturns())
|
||||||
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
||||||
.When(x => x.WhenICreateTheConfig())
|
.When(x => x.WhenICreateTheConfig())
|
||||||
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
|
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
|
||||||
@ -290,6 +300,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.And(x => x.GivenTheConfigIsValid())
|
.And(x => x.GivenTheConfigIsValid())
|
||||||
|
.And(x => GivenTheHeaderFindAndReplaceCreatorReturns())
|
||||||
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
||||||
.When(x => x.WhenICreateTheConfig())
|
.When(x => x.WhenICreateTheConfig())
|
||||||
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
|
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
|
||||||
@ -325,6 +336,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.And(x => x.GivenTheConfigIsValid())
|
.And(x => x.GivenTheConfigIsValid())
|
||||||
|
.And(x => GivenTheHeaderFindAndReplaceCreatorReturns())
|
||||||
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
||||||
.When(x => x.WhenICreateTheConfig())
|
.When(x => x.WhenICreateTheConfig())
|
||||||
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
|
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
|
||||||
@ -359,6 +371,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.And(x => x.GivenTheConfigIsValid())
|
.And(x => x.GivenTheConfigIsValid())
|
||||||
|
.And(x => GivenTheHeaderFindAndReplaceCreatorReturns())
|
||||||
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
||||||
.And(x => x.GivenTheUpstreamTemplatePatternCreatorReturns("(?i)/api/products/.*/$"))
|
.And(x => x.GivenTheUpstreamTemplatePatternCreatorReturns("(?i)/api/products/.*/$"))
|
||||||
.When(x => x.WhenICreateTheConfig())
|
.When(x => x.WhenICreateTheConfig())
|
||||||
@ -398,6 +411,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.And(x => x.GivenTheConfigIsValid())
|
.And(x => x.GivenTheConfigIsValid())
|
||||||
|
.And(x => GivenTheHeaderFindAndReplaceCreatorReturns())
|
||||||
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
||||||
.And(x => x.GivenTheRequestIdCreatorReturns("blahhhh"))
|
.And(x => x.GivenTheRequestIdCreatorReturns("blahhhh"))
|
||||||
.When(x => x.WhenICreateTheConfig())
|
.When(x => x.WhenICreateTheConfig())
|
||||||
@ -435,6 +449,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
||||||
|
.And(x => GivenTheHeaderFindAndReplaceCreatorReturns())
|
||||||
.And(x => x.GivenTheConfigIsValid())
|
.And(x => x.GivenTheConfigIsValid())
|
||||||
.And(x => x.GivenTheFollowingHttpHandlerOptionsAreReturned(httpHandlerOptions))
|
.And(x => x.GivenTheFollowingHttpHandlerOptionsAreReturned(httpHandlerOptions))
|
||||||
.When(x => x.WhenICreateTheConfig())
|
.When(x => x.WhenICreateTheConfig())
|
||||||
@ -470,6 +485,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
|
|
||||||
this.Given(x => x.GivenTheConfigIs(fileConfig))
|
this.Given(x => x.GivenTheConfigIs(fileConfig))
|
||||||
.And(x => x.GivenTheConfigIsValid())
|
.And(x => x.GivenTheConfigIsValid())
|
||||||
|
.And(x => GivenTheHeaderFindAndReplaceCreatorReturns())
|
||||||
.And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions))
|
.And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions))
|
||||||
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
||||||
.And(x => x.GivenTheClaimsToThingCreatorReturns(new List<ClaimToThing> { new ClaimToThing("CustomerId", "CustomerId", "", 0) }))
|
.And(x => x.GivenTheClaimsToThingCreatorReturns(new List<ClaimToThing> { new ClaimToThing("CustomerId", "CustomerId", "", 0) }))
|
||||||
@ -504,6 +520,7 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
|
|
||||||
this.Given(x => x.GivenTheConfigIs(fileConfig))
|
this.Given(x => x.GivenTheConfigIs(fileConfig))
|
||||||
.And(x => x.GivenTheConfigIsValid())
|
.And(x => x.GivenTheConfigIsValid())
|
||||||
|
.And(x => GivenTheHeaderFindAndReplaceCreatorReturns())
|
||||||
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
|
||||||
.And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions))
|
.And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions))
|
||||||
.When(x => x.WhenICreateTheConfig())
|
.When(x => x.WhenICreateTheConfig())
|
||||||
@ -661,6 +678,17 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
.Verify(x => x.Create(_fileConfiguration.GlobalConfiguration), Times.Once);
|
.Verify(x => x.Create(_fileConfiguration.GlobalConfiguration), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ThenTheHeaderFindAndReplaceCreatorIsCalledCorrectly()
|
||||||
|
{
|
||||||
|
_headerFindAndReplaceCreator
|
||||||
|
.Verify(x => x.Create(It.IsAny<FileReRoute>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheHeaderFindAndReplaceCreatorReturns()
|
||||||
|
{
|
||||||
|
_headerFindAndReplaceCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(new HeaderTransformations(new List<HeaderFindAndReplace>(), new List<HeaderFindAndReplace>()));
|
||||||
|
}
|
||||||
|
|
||||||
private void GivenTheFollowingIsReturned(ServiceProviderConfiguration serviceProviderConfiguration)
|
private void GivenTheFollowingIsReturned(ServiceProviderConfiguration serviceProviderConfiguration)
|
||||||
{
|
{
|
||||||
_serviceProviderConfigCreator
|
_serviceProviderConfigCreator
|
||||||
|
@ -0,0 +1,158 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Moq;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Configuration.Builder;
|
||||||
|
using Ocelot.Configuration.Creator;
|
||||||
|
using Ocelot.Configuration.File;
|
||||||
|
using Ocelot.Middleware;
|
||||||
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Ocelot.UnitTests.Configuration
|
||||||
|
{
|
||||||
|
public class HeaderFindAndReplaceCreatorTests
|
||||||
|
{
|
||||||
|
private HeaderFindAndReplaceCreator _creator;
|
||||||
|
private FileReRoute _reRoute;
|
||||||
|
private HeaderTransformations _result;
|
||||||
|
private Mock<IBaseUrlFinder> _finder;
|
||||||
|
|
||||||
|
public HeaderFindAndReplaceCreatorTests()
|
||||||
|
{
|
||||||
|
_finder = new Mock<IBaseUrlFinder>();
|
||||||
|
_creator = new HeaderFindAndReplaceCreator(_finder.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_create()
|
||||||
|
{
|
||||||
|
var reRoute = new FileReRoute
|
||||||
|
{
|
||||||
|
UpstreamHeaderTransform = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{"Test", "Test, Chicken"},
|
||||||
|
|
||||||
|
{"Moop", "o, a"}
|
||||||
|
},
|
||||||
|
DownstreamHeaderTransform = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{"Pop", "West, East"},
|
||||||
|
|
||||||
|
{"Bop", "e, r"}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var upstream = new List<HeaderFindAndReplace>
|
||||||
|
{
|
||||||
|
new HeaderFindAndReplace("Test", "Test", "Chicken", 0),
|
||||||
|
new HeaderFindAndReplace("Moop", "o", "a", 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
var downstream = new List<HeaderFindAndReplace>
|
||||||
|
{
|
||||||
|
new HeaderFindAndReplace("Pop", "West", "East", 0),
|
||||||
|
new HeaderFindAndReplace("Bop", "e", "r", 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => GivenTheReRoute(reRoute))
|
||||||
|
.When(x => WhenICreate())
|
||||||
|
.Then(x => ThenTheFollowingUpstreamIsReturned(upstream))
|
||||||
|
.Then(x => ThenTheFollowingDownstreamIsReturned(downstream))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_use_base_url_placeholder()
|
||||||
|
{
|
||||||
|
var reRoute = new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamHeaderTransform = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{"Location", "http://www.bbc.co.uk/, {BaseUrl}"},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var downstream = new List<HeaderFindAndReplace>
|
||||||
|
{
|
||||||
|
new HeaderFindAndReplace("Location", "http://www.bbc.co.uk/", "http://ocelot.com/", 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => GivenTheReRoute(reRoute))
|
||||||
|
.And(x => GivenTheBaseUrlIs("http://ocelot.com/"))
|
||||||
|
.When(x => WhenICreate())
|
||||||
|
.Then(x => ThenTheFollowingDownstreamIsReturned(downstream))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_use_base_url_partial_placeholder()
|
||||||
|
{
|
||||||
|
var reRoute = new FileReRoute
|
||||||
|
{
|
||||||
|
DownstreamHeaderTransform = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{"Location", "http://www.bbc.co.uk/pay, {BaseUrl}pay"},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var downstream = new List<HeaderFindAndReplace>
|
||||||
|
{
|
||||||
|
new HeaderFindAndReplace("Location", "http://www.bbc.co.uk/pay", "http://ocelot.com/pay", 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.Given(x => GivenTheReRoute(reRoute))
|
||||||
|
.And(x => GivenTheBaseUrlIs("http://ocelot.com/"))
|
||||||
|
.When(x => WhenICreate())
|
||||||
|
.Then(x => ThenTheFollowingDownstreamIsReturned(downstream))
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheBaseUrlIs(string baseUrl)
|
||||||
|
{
|
||||||
|
_finder.Setup(x => x.Find()).Returns(baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheFollowingDownstreamIsReturned(List<HeaderFindAndReplace> downstream)
|
||||||
|
{
|
||||||
|
_result.Downstream.Count.ShouldBe(downstream.Count);
|
||||||
|
|
||||||
|
for (int i = 0; i < _result.Downstream.Count; i++)
|
||||||
|
{
|
||||||
|
var result = _result.Downstream[i];
|
||||||
|
var expected = downstream[i];
|
||||||
|
result.Find.ShouldBe(expected.Find);
|
||||||
|
result.Index.ShouldBe(expected.Index);
|
||||||
|
result.Key.ShouldBe(expected.Key);
|
||||||
|
result.Replace.ShouldBe(expected.Replace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheReRoute(FileReRoute reRoute)
|
||||||
|
{
|
||||||
|
_reRoute = reRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenICreate()
|
||||||
|
{
|
||||||
|
_result = _creator.Create(_reRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheFollowingUpstreamIsReturned(List<HeaderFindAndReplace> expecteds)
|
||||||
|
{
|
||||||
|
_result.Upstream.Count.ShouldBe(expecteds.Count);
|
||||||
|
|
||||||
|
for (int i = 0; i < _result.Upstream.Count; i++)
|
||||||
|
{
|
||||||
|
var result = _result.Upstream[i];
|
||||||
|
var expected = expecteds[i];
|
||||||
|
result.Find.ShouldBe(expected.Find);
|
||||||
|
result.Index.ShouldBe(expected.Index);
|
||||||
|
result.Key.ShouldBe(expected.Key);
|
||||||
|
result.Replace.ShouldBe(expected.Replace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
using Xunit;
|
||||||
|
using Shouldly;
|
||||||
|
using Ocelot.Headers.Middleware;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Headers;
|
||||||
|
|
||||||
|
namespace Ocelot.UnitTests.Headers
|
||||||
|
{
|
||||||
|
public class HttpContextRequestHeaderReplacerTests
|
||||||
|
{
|
||||||
|
private HttpContext _context;
|
||||||
|
private List<HeaderFindAndReplace> _fAndRs;
|
||||||
|
private HttpContextRequestHeaderReplacer _replacer;
|
||||||
|
private Response _result;
|
||||||
|
|
||||||
|
public HttpContextRequestHeaderReplacerTests()
|
||||||
|
{
|
||||||
|
_replacer = new HttpContextRequestHeaderReplacer();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_replace_headers()
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext();
|
||||||
|
context.Request.Headers.Add("test", "test");
|
||||||
|
|
||||||
|
var fAndRs = new List<HeaderFindAndReplace>();
|
||||||
|
fAndRs.Add(new HeaderFindAndReplace("test", "test", "chiken", 0));
|
||||||
|
|
||||||
|
this.Given(x => GivenTheFollowingHttpRequest(context))
|
||||||
|
.And(x => GivenTheFollowingHeaderReplacements(fAndRs))
|
||||||
|
.When(x => WhenICallTheReplacer())
|
||||||
|
.Then(x => ThenTheHeadersAreReplaced())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_not_replace_headers()
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext();
|
||||||
|
context.Request.Headers.Add("test", "test");
|
||||||
|
|
||||||
|
var fAndRs = new List<HeaderFindAndReplace>();
|
||||||
|
|
||||||
|
this.Given(x => GivenTheFollowingHttpRequest(context))
|
||||||
|
.And(x => GivenTheFollowingHeaderReplacements(fAndRs))
|
||||||
|
.When(x => WhenICallTheReplacer())
|
||||||
|
.Then(x => ThenTheHeadersAreNotReplaced())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheHeadersAreNotReplaced()
|
||||||
|
{
|
||||||
|
_result.ShouldBeOfType<OkResponse>();
|
||||||
|
foreach (var f in _fAndRs)
|
||||||
|
{
|
||||||
|
_context.Request.Headers.TryGetValue(f.Key, out var values);
|
||||||
|
values[f.Index].ShouldBe("test");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheFollowingHttpRequest(HttpContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheFollowingHeaderReplacements(List<HeaderFindAndReplace> fAndRs)
|
||||||
|
{
|
||||||
|
_fAndRs = fAndRs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenICallTheReplacer()
|
||||||
|
{
|
||||||
|
_result = _replacer.Replace(_context, _fAndRs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheHeadersAreReplaced()
|
||||||
|
{
|
||||||
|
_result.ShouldBeOfType<OkResponse>();
|
||||||
|
foreach (var f in _fAndRs)
|
||||||
|
{
|
||||||
|
_context.Request.Headers.TryGetValue(f.Key, out var values);
|
||||||
|
values[f.Index].ShouldBe(f.Replace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
using Xunit;
|
||||||
|
using Shouldly;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Ocelot.Headers.Middleware;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Moq;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.DownstreamRouteFinder;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using Ocelot.Configuration.Builder;
|
||||||
|
using Ocelot.Headers;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Ocelot.UnitTests.Headers
|
||||||
|
{
|
||||||
|
public class HttpHeadersTransformationMiddlewareTests : ServerHostedMiddlewareTest
|
||||||
|
{
|
||||||
|
private Mock<IHttpContextRequestHeaderReplacer> _preReplacer;
|
||||||
|
private Mock<IHttpResponseHeaderReplacer> _postReplacer;
|
||||||
|
|
||||||
|
public HttpHeadersTransformationMiddlewareTests()
|
||||||
|
{
|
||||||
|
_preReplacer = new Mock<IHttpContextRequestHeaderReplacer>();
|
||||||
|
_postReplacer = new Mock<IHttpResponseHeaderReplacer>();
|
||||||
|
|
||||||
|
GivenTheTestServerIsConfigured();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_call_pre_and_post_header_transforms()
|
||||||
|
{
|
||||||
|
this.Given(x => GivenTheFollowingRequest())
|
||||||
|
.And(x => GivenTheReRouteHasPreFindAndReplaceSetUp())
|
||||||
|
.And(x => GivenTheHttpResponseMessageIs())
|
||||||
|
.When(x => WhenICallTheMiddleware())
|
||||||
|
.Then(x => ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly())
|
||||||
|
.And(x => ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheHttpResponseMessageIs()
|
||||||
|
{
|
||||||
|
var httpResponseMessage = new HttpResponseMessage();
|
||||||
|
var response = new OkResponse<HttpResponseMessage>(httpResponseMessage);
|
||||||
|
ScopedRepository.Setup(x => x.Get<HttpResponseMessage>("HttpResponseMessage")).Returns(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheReRouteHasPreFindAndReplaceSetUp()
|
||||||
|
{
|
||||||
|
var fAndRs = new List<HeaderFindAndReplace>();
|
||||||
|
var reRoute = new ReRouteBuilder().WithUpstreamHeaderFindAndReplace(fAndRs).WithDownstreamHeaderFindAndReplace(fAndRs).Build();
|
||||||
|
var dR = new DownstreamRoute(null, reRoute);
|
||||||
|
var response = new OkResponse<DownstreamRoute>(dR);
|
||||||
|
ScopedRepository.Setup(x => x.Get<DownstreamRoute>("DownstreamRoute")).Returns(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly()
|
||||||
|
{
|
||||||
|
_preReplacer.Verify(x => x.Replace(It.IsAny<HttpContext>(), It.IsAny<List<HeaderFindAndReplace>>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly()
|
||||||
|
{
|
||||||
|
_postReplacer.Verify(x => x.Replace(It.IsAny<HttpResponseMessage>(), It.IsAny<List<HeaderFindAndReplace>>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheFollowingRequest()
|
||||||
|
{
|
||||||
|
Client.DefaultRequestHeaders.Add("test", "test");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void GivenTheTestServerServicesAreConfigured(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
|
||||||
|
services.AddLogging();
|
||||||
|
services.AddSingleton(ScopedRepository.Object);
|
||||||
|
services.AddSingleton(_preReplacer.Object);
|
||||||
|
services.AddSingleton(_postReplacer.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
app.UseHttpHeadersTransformationMiddleware();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
using Xunit;
|
||||||
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Ocelot.Headers;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Ocelot.Responses;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Ocelot.UnitTests.Headers
|
||||||
|
{
|
||||||
|
public class HttpResponseHeaderReplacerTests
|
||||||
|
{
|
||||||
|
private HttpResponseMessage _response;
|
||||||
|
private HttpResponseHeaderReplacer _replacer;
|
||||||
|
private List<HeaderFindAndReplace> _headerFindAndReplaces;
|
||||||
|
private Response _result;
|
||||||
|
|
||||||
|
public HttpResponseHeaderReplacerTests()
|
||||||
|
{
|
||||||
|
_replacer = new HttpResponseHeaderReplacer();
|
||||||
|
}
|
||||||
|
[Fact]
|
||||||
|
public void should_replace_headers()
|
||||||
|
{
|
||||||
|
var response = new HttpResponseMessage();
|
||||||
|
response.Headers.Add("test", "test");
|
||||||
|
|
||||||
|
var fAndRs = new List<HeaderFindAndReplace>();
|
||||||
|
fAndRs.Add(new HeaderFindAndReplace("test", "test", "chiken", 0));
|
||||||
|
|
||||||
|
this.Given(x => GivenTheHttpResponse(response))
|
||||||
|
.And(x => GivenTheFollowingHeaderReplacements(fAndRs))
|
||||||
|
.When(x => WhenICallTheReplacer())
|
||||||
|
.Then(x => ThenTheHeadersAreReplaced())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_not_replace_headers()
|
||||||
|
{
|
||||||
|
var response = new HttpResponseMessage();
|
||||||
|
response.Headers.Add("test", "test");
|
||||||
|
|
||||||
|
var fAndRs = new List<HeaderFindAndReplace>();
|
||||||
|
|
||||||
|
this.Given(x => GivenTheHttpResponse(response))
|
||||||
|
.And(x => GivenTheFollowingHeaderReplacements(fAndRs))
|
||||||
|
.When(x => WhenICallTheReplacer())
|
||||||
|
.Then(x => ThenTheHeadersAreNotReplaced())
|
||||||
|
.BDDfy();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void ThenTheHeadersAreNotReplaced()
|
||||||
|
{
|
||||||
|
_result.ShouldBeOfType<OkResponse>();
|
||||||
|
foreach (var f in _headerFindAndReplaces)
|
||||||
|
{
|
||||||
|
_response.Headers.TryGetValues(f.Key, out var values);
|
||||||
|
values.ToList()[f.Index].ShouldBe("test");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheFollowingHeaderReplacements(List<HeaderFindAndReplace> fAndRs)
|
||||||
|
{
|
||||||
|
_headerFindAndReplaces = fAndRs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenTheHttpResponse(HttpResponseMessage response)
|
||||||
|
{
|
||||||
|
_response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WhenICallTheReplacer()
|
||||||
|
{
|
||||||
|
_result = _replacer.Replace(_response, _headerFindAndReplaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheHeadersAreReplaced()
|
||||||
|
{
|
||||||
|
_result.ShouldBeOfType<OkResponse>();
|
||||||
|
foreach (var f in _headerFindAndReplaces)
|
||||||
|
{
|
||||||
|
_response.Headers.TryGetValues(f.Key, out var values);
|
||||||
|
values.ToList()[f.Index].ShouldBe(f.Replace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -47,6 +47,53 @@ namespace Ocelot.UnitTests.LoadBalancer
|
|||||||
Task.WaitAll(tasks);
|
Task.WaitAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void should_handle_service_returning_to_available()
|
||||||
|
{
|
||||||
|
var serviceName = "products";
|
||||||
|
|
||||||
|
var availableServices = new List<Service>
|
||||||
|
{
|
||||||
|
new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||||
|
new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||||
|
};
|
||||||
|
|
||||||
|
_leastConnection = new LeastConnection(() => Task.FromResult(availableServices), serviceName);
|
||||||
|
|
||||||
|
var hostAndPortOne = _leastConnection.Lease().Result;
|
||||||
|
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||||
|
var hostAndPortTwo = _leastConnection.Lease().Result;
|
||||||
|
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2");
|
||||||
|
_leastConnection.Release(hostAndPortOne.Data);
|
||||||
|
_leastConnection.Release(hostAndPortTwo.Data);
|
||||||
|
|
||||||
|
availableServices = new List<Service>
|
||||||
|
{
|
||||||
|
new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||||
|
};
|
||||||
|
|
||||||
|
hostAndPortOne = _leastConnection.Lease().Result;
|
||||||
|
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||||
|
hostAndPortTwo = _leastConnection.Lease().Result;
|
||||||
|
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||||
|
_leastConnection.Release(hostAndPortOne.Data);
|
||||||
|
_leastConnection.Release(hostAndPortTwo.Data);
|
||||||
|
|
||||||
|
availableServices = new List<Service>
|
||||||
|
{
|
||||||
|
new Service(serviceName, new HostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]),
|
||||||
|
new Service(serviceName, new HostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]),
|
||||||
|
};
|
||||||
|
|
||||||
|
hostAndPortOne = _leastConnection.Lease().Result;
|
||||||
|
hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1");
|
||||||
|
hostAndPortTwo = _leastConnection.Lease().Result;
|
||||||
|
hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2");
|
||||||
|
_leastConnection.Release(hostAndPortOne.Data);
|
||||||
|
_leastConnection.Release(hostAndPortTwo.Data);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private async Task LeaseDelayAndRelease()
|
private async Task LeaseDelayAndRelease()
|
||||||
{
|
{
|
||||||
var hostAndPort = await _leastConnection.Lease();
|
var hostAndPort = await _leastConnection.Lease();
|
||||||
|
@ -4,6 +4,7 @@ using Shouldly;
|
|||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
// nothing in use
|
||||||
namespace Ocelot.UnitTests.ServiceDiscovery
|
namespace Ocelot.UnitTests.ServiceDiscovery
|
||||||
{
|
{
|
||||||
public class ServiceRegistryTests
|
public class ServiceRegistryTests
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="Cake" version="0.23.0" />
|
<package id="Cake" version="0.25.0" />
|
||||||
</packages>
|
</packages>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user