mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-20 08:02:50 +08:00
Feature/transform headers (#204)
* New feature that lets a user do find and replace on an upstream header * can transform downstream and upstream headers, not sure if interface is good * can replace location header with placeholder * added some syntax
This commit is contained in:
parent
9c048ba615
commit
d0eee70c46
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.
|
@ -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
|
||||||
|
@ -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;}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -37,7 +37,7 @@ namespace Ocelot.Errors.Middleware
|
|||||||
public async Task Invoke(HttpContext context)
|
public async Task Invoke(HttpContext context)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await TrySetGlobalRequestId(context);
|
await TrySetGlobalRequestId(context);
|
||||||
|
|
||||||
_logger.LogDebug("ocelot pipeline started");
|
_logger.LogDebug("ocelot pipeline started");
|
||||||
|
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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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];
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user