#296 merged develop into this branch

This commit is contained in:
Tom Gardham-Pallister 2018-04-17 19:45:27 +01:00
commit b7ff73729f
16 changed files with 624 additions and 428 deletions

View File

@ -1,13 +1,29 @@
Headers Transformation Headers Transformation
===================== ======================
Ocelot allows the user to transform headers pre and post downstream request. At the moment Ocelot only supports find and replace. This feature was requested `GitHub #190 <https://github.com/TomPallister/Ocelot/issues/190>`_ and I decided that it was going to be useful in various ways. Ocelot allows the user to transform headers pre and post downstream request. At the moment Ocelot only supports find and replace. This feature was requested `GitHub #190 <https://github.com/TomPallister/Ocelot/issues/190>`_ and I decided that it was going to be useful in various ways.
Add to Request
^^^^^^^^^^^^^^
This feature was requestes in `GitHub #313 <https://github.com/ThreeMammals/Ocelot/issues/313>`_.
If you want to add a header to your upstream request please add the following to a ReRoute in your ocelot.json:
.. code-block:: json
"UpstreamHeaderTransform": {
"Uncle": "Bob"
}
In the example above a header with the key Uncle and value Bob would be send to to the upstream service.
Placeholders are supported too (see below).
Add to Response Add to Response
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
This feature was requested in `GitHub #280 <https://github.com/TomPallister/Ocelot/issues/280>`_. I have only implemented This feature was requested in `GitHub #280 <https://github.com/TomPallister/Ocelot/issues/280>`_.
for responses but could add for requests in the future.
If you want to add a header to your downstream response please add the following to a ReRoute in ocelot.json.. If you want to add a header to your downstream response please add the following to a ReRoute in ocelot.json..
@ -50,7 +66,7 @@ Add the following to a ReRoute in ocelot.json in order to replace http://www.bbc
}, },
Post Downstream Request Post Downstream Request
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
Add the following to a ReRoute in ocelot.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. Add the following to a ReRoute in ocelot.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.

View File

@ -39,12 +39,14 @@ namespace Ocelot.Configuration.Builder
private string _key; private string _key;
private List<string> _delegatingHandlers; private List<string> _delegatingHandlers;
private List<AddHeader> _addHeadersToDownstream; private List<AddHeader> _addHeadersToDownstream;
private List<AddHeader> _addHeadersToUpstream;
public DownstreamReRouteBuilder() public DownstreamReRouteBuilder()
{ {
_downstreamAddresses = new List<DownstreamHostAndPort>(); _downstreamAddresses = new List<DownstreamHostAndPort>();
_delegatingHandlers = new List<string>(); _delegatingHandlers = new List<string>();
_addHeadersToDownstream = new List<AddHeader>(); _addHeadersToDownstream = new List<AddHeader>();
_addHeadersToUpstream = new List<AddHeader>();
} }
public DownstreamReRouteBuilder WithDownstreamAddresses(List<DownstreamHostAndPort> downstreamAddresses) public DownstreamReRouteBuilder WithDownstreamAddresses(List<DownstreamHostAndPort> downstreamAddresses)
@ -233,6 +235,12 @@ namespace Ocelot.Configuration.Builder
return this; return this;
} }
public DownstreamReRouteBuilder WithAddHeadersToUpstream(List<AddHeader> addHeadersToUpstream)
{
_addHeadersToUpstream = addHeadersToUpstream;
return this;
}
public DownstreamReRoute Build() public DownstreamReRoute Build()
{ {
return new DownstreamReRoute( return new DownstreamReRoute(
@ -263,7 +271,8 @@ namespace Ocelot.Configuration.Builder
new PathTemplate(_downstreamPathTemplate), new PathTemplate(_downstreamPathTemplate),
_reRouteKey, _reRouteKey,
_delegatingHandlers, _delegatingHandlers,
_addHeadersToDownstream); _addHeadersToDownstream,
_addHeadersToUpstream);
} }
} }
} }

View File

@ -1,227 +1,228 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Ocelot.Cache; using Ocelot.Cache;
using Ocelot.Configuration.Builder; using Ocelot.Configuration.Builder;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Configuration.Validator; using Ocelot.Configuration.Validator;
using Ocelot.DependencyInjection; using Ocelot.DependencyInjection;
using Ocelot.Logging; using Ocelot.Logging;
using Ocelot.Responses; using Ocelot.Responses;
namespace Ocelot.Configuration.Creator namespace Ocelot.Configuration.Creator
{ {
/// <summary> /// <summary>
/// Register as singleton /// Register as singleton
/// </summary> /// </summary>
public class FileInternalConfigurationCreator : IInternalConfigurationCreator public class FileInternalConfigurationCreator : IInternalConfigurationCreator
{ {
private readonly IConfigurationValidator _configurationValidator; private readonly IConfigurationValidator _configurationValidator;
private readonly IOcelotLogger _logger; private readonly IOcelotLogger _logger;
private readonly IClaimsToThingCreator _claimsToThingCreator; private readonly IClaimsToThingCreator _claimsToThingCreator;
private readonly IAuthenticationOptionsCreator _authOptionsCreator; private readonly IAuthenticationOptionsCreator _authOptionsCreator;
private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator;
private readonly IRequestIdKeyCreator _requestIdKeyCreator; private readonly IRequestIdKeyCreator _requestIdKeyCreator;
private readonly IServiceProviderConfigurationCreator _serviceProviderConfigCreator; private readonly IServiceProviderConfigurationCreator _serviceProviderConfigCreator;
private readonly IQoSOptionsCreator _qosOptionsCreator; private readonly IQoSOptionsCreator _qosOptionsCreator;
private readonly IReRouteOptionsCreator _fileReRouteOptionsCreator; private readonly IReRouteOptionsCreator _fileReRouteOptionsCreator;
private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator; private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator;
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; private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator;
private readonly IDownstreamAddressesCreator _downstreamAddressesCreator; private readonly IDownstreamAddressesCreator _downstreamAddressesCreator;
public FileInternalConfigurationCreator( public FileInternalConfigurationCreator(
IConfigurationValidator configurationValidator, IConfigurationValidator configurationValidator,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IClaimsToThingCreator claimsToThingCreator, IClaimsToThingCreator claimsToThingCreator,
IAuthenticationOptionsCreator authOptionsCreator, IAuthenticationOptionsCreator authOptionsCreator,
IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator, IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator,
IRequestIdKeyCreator requestIdKeyCreator, IRequestIdKeyCreator requestIdKeyCreator,
IServiceProviderConfigurationCreator serviceProviderConfigCreator, IServiceProviderConfigurationCreator serviceProviderConfigCreator,
IQoSOptionsCreator qosOptionsCreator, IQoSOptionsCreator qosOptionsCreator,
IReRouteOptionsCreator fileReRouteOptionsCreator, IReRouteOptionsCreator fileReRouteOptionsCreator,
IRateLimitOptionsCreator rateLimitOptionsCreator, IRateLimitOptionsCreator rateLimitOptionsCreator,
IRegionCreator regionCreator, IRegionCreator regionCreator,
IHttpHandlerOptionsCreator httpHandlerOptionsCreator, IHttpHandlerOptionsCreator httpHandlerOptionsCreator,
IAdministrationPath adminPath, IAdministrationPath adminPath,
IHeaderFindAndReplaceCreator headerFAndRCreator, IHeaderFindAndReplaceCreator headerFAndRCreator,
IDownstreamAddressesCreator downstreamAddressesCreator IDownstreamAddressesCreator downstreamAddressesCreator
) )
{ {
_downstreamAddressesCreator = downstreamAddressesCreator; _downstreamAddressesCreator = downstreamAddressesCreator;
_headerFAndRCreator = headerFAndRCreator; _headerFAndRCreator = headerFAndRCreator;
_adminPath = adminPath; _adminPath = adminPath;
_regionCreator = regionCreator; _regionCreator = regionCreator;
_rateLimitOptionsCreator = rateLimitOptionsCreator; _rateLimitOptionsCreator = rateLimitOptionsCreator;
_requestIdKeyCreator = requestIdKeyCreator; _requestIdKeyCreator = requestIdKeyCreator;
_upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator;
_authOptionsCreator = authOptionsCreator; _authOptionsCreator = authOptionsCreator;
_configurationValidator = configurationValidator; _configurationValidator = configurationValidator;
_logger = loggerFactory.CreateLogger<FileInternalConfigurationCreator>(); _logger = loggerFactory.CreateLogger<FileInternalConfigurationCreator>();
_claimsToThingCreator = claimsToThingCreator; _claimsToThingCreator = claimsToThingCreator;
_serviceProviderConfigCreator = serviceProviderConfigCreator; _serviceProviderConfigCreator = serviceProviderConfigCreator;
_qosOptionsCreator = qosOptionsCreator; _qosOptionsCreator = qosOptionsCreator;
_fileReRouteOptionsCreator = fileReRouteOptionsCreator; _fileReRouteOptionsCreator = fileReRouteOptionsCreator;
_httpHandlerOptionsCreator = httpHandlerOptionsCreator; _httpHandlerOptionsCreator = httpHandlerOptionsCreator;
} }
public async Task<Response<IInternalConfiguration>> Create(FileConfiguration fileConfiguration) public async Task<Response<IInternalConfiguration>> Create(FileConfiguration fileConfiguration)
{ {
var config = await SetUpConfiguration(fileConfiguration); var config = await SetUpConfiguration(fileConfiguration);
return config; return config;
} }
private async Task<Response<IInternalConfiguration>> SetUpConfiguration(FileConfiguration fileConfiguration) private async Task<Response<IInternalConfiguration>> SetUpConfiguration(FileConfiguration fileConfiguration)
{ {
var response = await _configurationValidator.IsValid(fileConfiguration); var response = await _configurationValidator.IsValid(fileConfiguration);
if (response.Data.IsError) if (response.Data.IsError)
{ {
return new ErrorResponse<IInternalConfiguration>(response.Data.Errors); return new ErrorResponse<IInternalConfiguration>(response.Data.Errors);
} }
var reRoutes = new List<ReRoute>(); var reRoutes = new List<ReRoute>();
foreach (var reRoute in fileConfiguration.ReRoutes) foreach (var reRoute in fileConfiguration.ReRoutes)
{ {
var downstreamReRoute = SetUpDownstreamReRoute(reRoute, fileConfiguration.GlobalConfiguration); var downstreamReRoute = SetUpDownstreamReRoute(reRoute, fileConfiguration.GlobalConfiguration);
var ocelotReRoute = SetUpReRoute(reRoute, downstreamReRoute); var ocelotReRoute = SetUpReRoute(reRoute, downstreamReRoute);
reRoutes.Add(ocelotReRoute); reRoutes.Add(ocelotReRoute);
} }
foreach (var aggregate in fileConfiguration.Aggregates) foreach (var aggregate in fileConfiguration.Aggregates)
{ {
var ocelotReRoute = SetUpAggregateReRoute(reRoutes, aggregate, fileConfiguration.GlobalConfiguration); var ocelotReRoute = SetUpAggregateReRoute(reRoutes, aggregate, fileConfiguration.GlobalConfiguration);
reRoutes.Add(ocelotReRoute); reRoutes.Add(ocelotReRoute);
} }
var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration); var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration);
var config = new InternalConfiguration(reRoutes, _adminPath.Path, serviceProviderConfiguration, fileConfiguration.GlobalConfiguration.RequestIdKey); var config = new InternalConfiguration(reRoutes, _adminPath.Path, serviceProviderConfiguration, fileConfiguration.GlobalConfiguration.RequestIdKey);
return new OkResponse<IInternalConfiguration>(config); return new OkResponse<IInternalConfiguration>(config);
} }
public ReRoute SetUpAggregateReRoute(List<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) public ReRoute SetUpAggregateReRoute(List<ReRoute> reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration)
{ {
var applicableReRoutes = reRoutes var applicableReRoutes = reRoutes
.SelectMany(x => x.DownstreamReRoute) .SelectMany(x => x.DownstreamReRoute)
.Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key)) .Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key))
.ToList(); .ToList();
if(applicableReRoutes.Count != aggregateReRoute.ReRouteKeys.Count) if(applicableReRoutes.Count != aggregateReRoute.ReRouteKeys.Count)
{ {
//todo - log or throw or return error whatever? //todo - log or throw or return error whatever?
} }
//make another re route out of these //make another re route out of these
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(aggregateReRoute); var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(aggregateReRoute);
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.WithUpstreamPathTemplate(aggregateReRoute.UpstreamPathTemplate) .WithUpstreamPathTemplate(aggregateReRoute.UpstreamPathTemplate)
.WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod) .WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod)
.WithUpstreamTemplatePattern(upstreamTemplatePattern) .WithUpstreamTemplatePattern(upstreamTemplatePattern)
.WithDownstreamReRoutes(applicableReRoutes) .WithDownstreamReRoutes(applicableReRoutes)
.WithUpstreamHost(aggregateReRoute.UpstreamHost) .WithUpstreamHost(aggregateReRoute.UpstreamHost)
.WithAggregator(aggregateReRoute.Aggregator) .WithAggregator(aggregateReRoute.Aggregator)
.Build(); .Build();
return reRoute; return reRoute;
} }
private ReRoute SetUpReRoute(FileReRoute fileReRoute, DownstreamReRoute downstreamReRoutes) private ReRoute SetUpReRoute(FileReRoute fileReRoute, DownstreamReRoute downstreamReRoutes)
{ {
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
.WithUpstreamTemplatePattern(upstreamTemplatePattern) .WithUpstreamTemplatePattern(upstreamTemplatePattern)
.WithDownstreamReRoute(downstreamReRoutes) .WithDownstreamReRoute(downstreamReRoutes)
.WithUpstreamHost(fileReRoute.UpstreamHost) .WithUpstreamHost(fileReRoute.UpstreamHost)
.Build(); .Build();
return reRoute; return reRoute;
} }
private DownstreamReRoute SetUpDownstreamReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) private DownstreamReRoute SetUpDownstreamReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration)
{ {
var fileReRouteOptions = _fileReRouteOptionsCreator.Create(fileReRoute); var fileReRouteOptions = _fileReRouteOptionsCreator.Create(fileReRoute);
var requestIdKey = _requestIdKeyCreator.Create(fileReRoute, globalConfiguration); var requestIdKey = _requestIdKeyCreator.Create(fileReRoute, globalConfiguration);
var reRouteKey = CreateReRouteKey(fileReRoute); var reRouteKey = CreateReRouteKey(fileReRoute);
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);
var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute); var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute);
var claimsToHeaders = _claimsToThingCreator.Create(fileReRoute.AddHeadersToRequest); var claimsToHeaders = _claimsToThingCreator.Create(fileReRoute.AddHeadersToRequest);
var claimsToClaims = _claimsToThingCreator.Create(fileReRoute.AddClaimsToRequest); var claimsToClaims = _claimsToThingCreator.Create(fileReRoute.AddClaimsToRequest);
var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest); var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest);
var qosOptions = _qosOptionsCreator.Create(fileReRoute); var qosOptions = _qosOptionsCreator.Create(fileReRoute);
var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting); var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting);
var region = _regionCreator.Create(fileReRoute); var region = _regionCreator.Create(fileReRoute);
var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileReRoute); var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileReRoute);
var hAndRs = _headerFAndRCreator.Create(fileReRoute); var hAndRs = _headerFAndRCreator.Create(fileReRoute);
var downstreamAddresses = _downstreamAddressesCreator.Create(fileReRoute); var downstreamAddresses = _downstreamAddressesCreator.Create(fileReRoute);
var reRoute = new DownstreamReRouteBuilder() var reRoute = new DownstreamReRouteBuilder()
.WithKey(fileReRoute.Key) .WithKey(fileReRoute.Key)
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
.WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod)
.WithUpstreamTemplatePattern(upstreamTemplatePattern) .WithUpstreamTemplatePattern(upstreamTemplatePattern)
.WithIsAuthenticated(fileReRouteOptions.IsAuthenticated) .WithIsAuthenticated(fileReRouteOptions.IsAuthenticated)
.WithAuthenticationOptions(authOptionsForRoute) .WithAuthenticationOptions(authOptionsForRoute)
.WithClaimsToHeaders(claimsToHeaders) .WithClaimsToHeaders(claimsToHeaders)
.WithClaimsToClaims(claimsToClaims) .WithClaimsToClaims(claimsToClaims)
.WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement)
.WithIsAuthorised(fileReRouteOptions.IsAuthorised) .WithIsAuthorised(fileReRouteOptions.IsAuthorised)
.WithClaimsToQueries(claimsToQueries) .WithClaimsToQueries(claimsToQueries)
.WithRequestIdKey(requestIdKey) .WithRequestIdKey(requestIdKey)
.WithIsCached(fileReRouteOptions.IsCached) .WithIsCached(fileReRouteOptions.IsCached)
.WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region)) .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region))
.WithDownstreamScheme(fileReRoute.DownstreamScheme) .WithDownstreamScheme(fileReRoute.DownstreamScheme)
.WithLoadBalancer(fileReRoute.LoadBalancer) .WithLoadBalancer(fileReRoute.LoadBalancer)
.WithDownstreamAddresses(downstreamAddresses) .WithDownstreamAddresses(downstreamAddresses)
.WithReRouteKey(reRouteKey) .WithReRouteKey(reRouteKey)
.WithIsQos(fileReRouteOptions.IsQos) .WithIsQos(fileReRouteOptions.IsQos)
.WithQosOptions(qosOptions) .WithQosOptions(qosOptions)
.WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting) .WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting)
.WithRateLimitOptions(rateLimitOption) .WithRateLimitOptions(rateLimitOption)
.WithHttpHandlerOptions(httpHandlerOptions) .WithHttpHandlerOptions(httpHandlerOptions)
.WithServiceName(fileReRoute.ServiceName) .WithServiceName(fileReRoute.ServiceName)
.WithUseServiceDiscovery(fileReRoute.UseServiceDiscovery) .WithUseServiceDiscovery(fileReRoute.UseServiceDiscovery)
.WithUpstreamHeaderFindAndReplace(hAndRs.Upstream) .WithUpstreamHeaderFindAndReplace(hAndRs.Upstream)
.WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream)
.WithUpstreamHost(fileReRoute.UpstreamHost) .WithUpstreamHost(fileReRoute.UpstreamHost)
.WithDelegatingHandlers(fileReRoute.DelegatingHandlers) .WithDelegatingHandlers(fileReRoute.DelegatingHandlers)
.WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream)
.Build(); .WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream)
.Build();
return reRoute;
} return reRoute;
}
private string CreateReRouteKey(FileReRoute fileReRoute)
{ private string CreateReRouteKey(FileReRoute fileReRoute)
//note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain {
var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}|{string.Join(",", fileReRoute.UpstreamHttpMethod)}"; //note - not sure if this is the correct key, but this is probably the only unique key i can think of given my poor brain
return loadBalancerKey; var loadBalancerKey = $"{fileReRoute.UpstreamPathTemplate}|{string.Join(",", fileReRoute.UpstreamHttpMethod)}";
} return loadBalancerKey;
} }
} }
}

View File

@ -22,23 +22,31 @@ namespace Ocelot.Configuration.Creator
public HeaderTransformations Create(FileReRoute fileReRoute) public HeaderTransformations Create(FileReRoute fileReRoute)
{ {
var upstream = new List<HeaderFindAndReplace>(); var upstream = new List<HeaderFindAndReplace>();
var addHeadersToUpstream = new List<AddHeader>();
foreach(var input in fileReRoute.UpstreamHeaderTransform) foreach(var input in fileReRoute.UpstreamHeaderTransform)
{ {
var hAndr = Map(input); if (input.Value.Contains(","))
if(!hAndr.IsError)
{ {
upstream.Add(hAndr.Data); var hAndr = Map(input);
if (!hAndr.IsError)
{
upstream.Add(hAndr.Data);
}
else
{
_logger.LogWarning($"Unable to add UpstreamHeaderTransform {input.Key}: {input.Value}");
}
} }
else else
{ {
_logger.LogWarning($"Unable to add UpstreamHeaderTransform {input.Key}: {input.Value}"); addHeadersToUpstream.Add(new AddHeader(input.Key, input.Value));
} }
} }
var downstream = new List<HeaderFindAndReplace>(); var downstream = new List<HeaderFindAndReplace>();
var addHeadersToDownstream = new List<AddHeader>(); var addHeadersToDownstream = new List<AddHeader>();
foreach(var input in fileReRoute.DownstreamHeaderTransform) foreach(var input in fileReRoute.DownstreamHeaderTransform)
{ {
if(input.Value.Contains(",")) if(input.Value.Contains(","))
@ -59,7 +67,7 @@ namespace Ocelot.Configuration.Creator
} }
} }
return new HeaderTransformations(upstream, downstream, addHeadersToDownstream); return new HeaderTransformations(upstream, downstream, addHeadersToDownstream, addHeadersToUpstream);
} }
private Response<HeaderFindAndReplace> Map(KeyValuePair<string,string> input) private Response<HeaderFindAndReplace> Map(KeyValuePair<string,string> input)

View File

@ -7,9 +7,11 @@ namespace Ocelot.Configuration.Creator
public HeaderTransformations( public HeaderTransformations(
List<HeaderFindAndReplace> upstream, List<HeaderFindAndReplace> upstream,
List<HeaderFindAndReplace> downstream, List<HeaderFindAndReplace> downstream,
List<AddHeader> addHeader) List<AddHeader> addHeaderToDownstream,
List<AddHeader> addHeaderToUpstream)
{ {
AddHeadersToDownstream = addHeader; AddHeadersToDownstream = addHeaderToDownstream;
AddHeadersToUpstream = addHeaderToUpstream;
Upstream = upstream; Upstream = upstream;
Downstream = downstream; Downstream = downstream;
} }
@ -19,5 +21,6 @@ namespace Ocelot.Configuration.Creator
public List<HeaderFindAndReplace> Downstream { get; } public List<HeaderFindAndReplace> Downstream { get; }
public List<AddHeader> AddHeadersToDownstream { get; } public List<AddHeader> AddHeadersToDownstream { get; }
public List<AddHeader> AddHeadersToUpstream { get; }
} }
} }

View File

@ -34,7 +34,8 @@ namespace Ocelot.Configuration
PathTemplate downstreamPathTemplate, PathTemplate downstreamPathTemplate,
string reRouteKey, string reRouteKey,
List<string> delegatingHandlers, List<string> delegatingHandlers,
List<AddHeader> addHeadersToDownstream) List<AddHeader> addHeadersToDownstream,
List<AddHeader> addHeadersToUpstream)
{ {
AddHeadersToDownstream = addHeadersToDownstream; AddHeadersToDownstream = addHeadersToDownstream;
DelegatingHandlers = delegatingHandlers; DelegatingHandlers = delegatingHandlers;
@ -64,6 +65,7 @@ namespace Ocelot.Configuration
AuthenticationOptions = authenticationOptions; AuthenticationOptions = authenticationOptions;
DownstreamPathTemplate = downstreamPathTemplate; DownstreamPathTemplate = downstreamPathTemplate;
ReRouteKey = reRouteKey; ReRouteKey = reRouteKey;
AddHeadersToUpstream = addHeadersToUpstream;
} }
public string Key { get; private set; } public string Key { get; private set; }
@ -94,5 +96,6 @@ namespace Ocelot.Configuration
public string ReRouteKey { get; private set; } public string ReRouteKey { get; private set; }
public List<string> DelegatingHandlers {get;private set;} public List<string> DelegatingHandlers {get;private set;}
public List<AddHeader> AddHeadersToDownstream {get;private set;} public List<AddHeader> AddHeadersToDownstream {get;private set;}
public List<AddHeader> AddHeadersToUpstream { get; private set; }
} }
} }

View File

@ -4,6 +4,7 @@ using Ocelot.Configuration;
using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Infrastructure.Claims.Parser;
using Ocelot.Responses; using Ocelot.Responses;
using System.Net.Http; using System.Net.Http;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
using Ocelot.Request.Middleware; using Ocelot.Request.Middleware;
@ -41,5 +42,19 @@ namespace Ocelot.Headers
return new OkResponse(); return new OkResponse();
} }
public void SetHeadersOnDownstreamRequest(IEnumerable<AddHeader> headers, HttpContext context)
{
var requestHeader = context.Request.Headers;
foreach (var header in headers)
{
if (requestHeader.ContainsKey(header.Key))
{
requestHeader.Remove(header.Key);
}
requestHeader.Add(header.Key, header.Value);
}
}
} }
} }

View File

@ -1,4 +1,6 @@
namespace Ocelot.Headers using Microsoft.AspNetCore.Http;
namespace Ocelot.Headers
{ {
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
@ -12,5 +14,6 @@
public interface IAddHeadersToRequest public interface IAddHeadersToRequest
{ {
Response SetHeadersOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<System.Security.Claims.Claim> claims, DownstreamRequest downstreamRequest); Response SetHeadersOnDownstreamRequest(List<ClaimToThing> claimsToThings, IEnumerable<System.Security.Claims.Claim> claims, DownstreamRequest downstreamRequest);
void SetHeadersOnDownstreamRequest(IEnumerable<AddHeader> headers, HttpContext context);
} }
} }

View File

@ -9,16 +9,19 @@ namespace Ocelot.Headers.Middleware
private readonly OcelotRequestDelegate _next; private readonly OcelotRequestDelegate _next;
private readonly IHttpContextRequestHeaderReplacer _preReplacer; private readonly IHttpContextRequestHeaderReplacer _preReplacer;
private readonly IHttpResponseHeaderReplacer _postReplacer; private readonly IHttpResponseHeaderReplacer _postReplacer;
private readonly IAddHeadersToResponse _addHeaders; private readonly IAddHeadersToResponse _addHeadersToResponse;
private readonly IAddHeadersToRequest _addHeadersToRequest;
public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next, public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IHttpContextRequestHeaderReplacer preReplacer, IHttpContextRequestHeaderReplacer preReplacer,
IHttpResponseHeaderReplacer postReplacer, IHttpResponseHeaderReplacer postReplacer,
IAddHeadersToResponse addHeaders) IAddHeadersToResponse addHeadersToResponse,
IAddHeadersToRequest addHeadersToRequest)
:base(loggerFactory.CreateLogger<HttpHeadersTransformationMiddleware>()) :base(loggerFactory.CreateLogger<HttpHeadersTransformationMiddleware>())
{ {
_addHeaders = addHeaders; _addHeadersToResponse = addHeadersToResponse;
_addHeadersToRequest = addHeadersToRequest;
_next = next; _next = next;
_postReplacer = postReplacer; _postReplacer = postReplacer;
_preReplacer = preReplacer; _preReplacer = preReplacer;
@ -31,13 +34,15 @@ namespace Ocelot.Headers.Middleware
//todo - this should be on httprequestmessage not httpcontext? //todo - this should be on httprequestmessage not httpcontext?
_preReplacer.Replace(context.HttpContext, preFAndRs); _preReplacer.Replace(context.HttpContext, preFAndRs);
_addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.AddHeadersToUpstream, context.HttpContext);
await _next.Invoke(context); await _next.Invoke(context);
var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace; var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace;
_postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest); _postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest);
_addHeaders.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse); _addHeadersToResponse.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse);
} }
} }
} }

View File

@ -819,6 +819,7 @@
result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey); result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey);
result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers); result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers);
result.DownstreamReRoute[0].AddHeadersToDownstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToDownstream); result.DownstreamReRoute[0].AddHeadersToDownstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToDownstream);
result.DownstreamReRoute[0].AddHeadersToUpstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToUpstream, "AddHeadersToUpstream should be set");
} }
} }
@ -901,7 +902,7 @@
private void GivenTheHeaderFindAndReplaceCreatorReturns() private void GivenTheHeaderFindAndReplaceCreatorReturns()
{ {
_headerFindAndReplaceCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(new HeaderTransformations(new List<HeaderFindAndReplace>(), new List<HeaderFindAndReplace>(), new List<AddHeader>())); _headerFindAndReplaceCreator.Setup(x => x.Create(It.IsAny<FileReRoute>())).Returns(new HeaderTransformations(new List<HeaderFindAndReplace>(), new List<HeaderFindAndReplace>(), new List<AddHeader>(), new List<AddHeader>()));
} }
private void GivenTheFollowingIsReturned(ServiceProviderConfiguration serviceProviderConfiguration) private void GivenTheFollowingIsReturned(ServiceProviderConfiguration serviceProviderConfiguration)

View File

@ -12,13 +12,17 @@ using Ocelot.Configuration.Repository;
namespace Ocelot.UnitTests.Configuration namespace Ocelot.UnitTests.Configuration
{ {
public class FileConfigurationRepositoryTests public class FileConfigurationRepositoryTests : IDisposable
{ {
private readonly Mock<IHostingEnvironment> _hostingEnvironment = new Mock<IHostingEnvironment>(); private readonly Mock<IHostingEnvironment> _hostingEnvironment = new Mock<IHostingEnvironment>();
private IFileConfigurationRepository _repo; private IFileConfigurationRepository _repo;
private FileConfiguration _result; private FileConfiguration _result;
private FileConfiguration _fileConfiguration; private FileConfiguration _fileConfiguration;
private string _environmentName = "DEV";
// This is a bit dirty and it is dev.dev so that the configuration tests
// cant pick it up if they run in parralel..sigh these are not really unit
// tests but whatever...
private string _environmentName = "DEV.DEV";
public FileConfigurationRepositoryTests() public FileConfigurationRepositoryTests()
{ {
@ -221,5 +225,10 @@ namespace Ocelot.UnitTests.Configuration
ReRoutes = reRoutes ReRoutes = reRoutes
}; };
} }
public void Dispose()
{
File.Delete($"./ocelot.{_environmentName}.json");
}
} }
} }

View File

@ -149,7 +149,6 @@ namespace Ocelot.UnitTests.Configuration
.Then(x => ThenTheFollowingDownstreamIsReturned(downstream)) .Then(x => ThenTheFollowingDownstreamIsReturned(downstream))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_add_trace_id_header() public void should_add_trace_id_header()
{ {
@ -166,7 +165,45 @@ namespace Ocelot.UnitTests.Configuration
this.Given(x => GivenTheReRoute(reRoute)) this.Given(x => GivenTheReRoute(reRoute))
.And(x => GivenTheBaseUrlIs("http://ocelot.com/")) .And(x => GivenTheBaseUrlIs("http://ocelot.com/"))
.When(x => WhenICreate()) .When(x => WhenICreate())
.Then(x => ThenTheFollowingAddHeaderIsReturned(expected)) .Then(x => ThenTheFollowingAddHeaderToDownstreamIsReturned(expected))
.BDDfy();
}
[Fact]
public void should_add_downstream_header_as_is_when_no_replacement_is_given()
{
var reRoute = new FileReRoute
{
DownstreamHeaderTransform = new Dictionary<string, string>
{
{"X-Custom-Header", "Value"},
}
};
var expected = new AddHeader("X-Custom-Header", "Value");
this.Given(x => GivenTheReRoute(reRoute))
.And(x => WhenICreate())
.Then(x => x.ThenTheFollowingAddHeaderToDownstreamIsReturned(expected))
.BDDfy();
}
[Fact]
public void should_add_upstream_header_as_is_when_no_replacement_is_given()
{
var reRoute = new FileReRoute
{
UpstreamHeaderTransform = new Dictionary<string, string>
{
{"X-Custom-Header", "Value"},
}
};
var expected = new AddHeader("X-Custom-Header", "Value");
this.Given(x => GivenTheReRoute(reRoute))
.And(x => WhenICreate())
.Then(x => x.ThenTheFollowingAddHeaderToUpstreamIsReturned(expected))
.BDDfy(); .BDDfy();
} }
@ -180,11 +217,17 @@ namespace Ocelot.UnitTests.Configuration
_placeholders.Setup(x => x.Get(It.IsAny<string>())).Returns(new ErrorResponse<string>(new AnyError())); _placeholders.Setup(x => x.Get(It.IsAny<string>())).Returns(new ErrorResponse<string>(new AnyError()));
} }
private void ThenTheFollowingAddHeaderIsReturned(AddHeader addHeader) private void ThenTheFollowingAddHeaderToDownstreamIsReturned(AddHeader addHeader)
{ {
_result.AddHeadersToDownstream[0].Key.ShouldBe(addHeader.Key); _result.AddHeadersToDownstream[0].Key.ShouldBe(addHeader.Key);
_result.AddHeadersToDownstream[0].Value.ShouldBe(addHeader.Value); _result.AddHeadersToDownstream[0].Value.ShouldBe(addHeader.Value);
} }
private void ThenTheFollowingAddHeaderToUpstreamIsReturned(AddHeader addHeader)
{
_result.AddHeadersToUpstream[0].Key.ShouldBe(addHeader.Key);
_result.AddHeadersToUpstream[0].Value.ShouldBe(addHeader.Value);
}
private void ThenTheFollowingDownstreamIsReturned(List<HeaderFindAndReplace> downstream) private void ThenTheFollowingDownstreamIsReturned(List<HeaderFindAndReplace> downstream)
{ {

View File

@ -6,6 +6,7 @@ using Xunit;
namespace Ocelot.UnitTests.DependencyInjection namespace Ocelot.UnitTests.DependencyInjection
{ {
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -17,7 +18,7 @@ namespace Ocelot.UnitTests.DependencyInjection
private string _result; private string _result;
private IConfigurationRoot _configRoot; private IConfigurationRoot _configRoot;
private FileConfiguration _globalConfig; private FileConfiguration _globalConfig;
private FileConfiguration _reRoute; private FileConfiguration _reRouteA;
private FileConfiguration _reRouteB; private FileConfiguration _reRouteB;
private FileConfiguration _aggregate; private FileConfiguration _aggregate;
@ -64,7 +65,7 @@ namespace Ocelot.UnitTests.DependencyInjection
} }
}; };
_reRoute = new FileConfiguration _reRouteA = new FileConfiguration
{ {
ReRoutes = new List<FileReRoute> ReRoutes = new List<FileReRoute>
{ {
@ -160,17 +161,10 @@ namespace Ocelot.UnitTests.DependencyInjection
} }
}; };
var globalJson = JsonConvert.SerializeObject(_globalConfig); File.WriteAllText("ocelot.global.json", JsonConvert.SerializeObject(_globalConfig));
File.WriteAllText("ocelot.global.json", globalJson); File.WriteAllText("ocelot.reRoutesA.json", JsonConvert.SerializeObject(_reRouteA));
File.WriteAllText("ocelot.reRoutesB.json", JsonConvert.SerializeObject(_reRouteB));
var reRouteJson = JsonConvert.SerializeObject(_reRoute); File.WriteAllText("ocelot.aggregates.json", JsonConvert.SerializeObject(_aggregate));
File.WriteAllText("ocelot.reRoutes.json", reRouteJson);
var reRouteJsonB = JsonConvert.SerializeObject(_reRouteB);
File.WriteAllText("ocelot.reRoutesB.json", reRouteJsonB);
var aggregates = JsonConvert.SerializeObject(_aggregate);
File.WriteAllText("ocelot.aggregates.json", aggregates);
} }
private void WhenIAddOcelotConfiguration() private void WhenIAddOcelotConfiguration()
@ -195,21 +189,21 @@ namespace Ocelot.UnitTests.DependencyInjection
fc.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(_globalConfig.GlobalConfiguration.ServiceDiscoveryProvider.Port); fc.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(_globalConfig.GlobalConfiguration.ServiceDiscoveryProvider.Port);
fc.GlobalConfiguration.ServiceDiscoveryProvider.Type.ShouldBe(_globalConfig.GlobalConfiguration.ServiceDiscoveryProvider.Type); fc.GlobalConfiguration.ServiceDiscoveryProvider.Type.ShouldBe(_globalConfig.GlobalConfiguration.ServiceDiscoveryProvider.Type);
fc.ReRoutes.Count.ShouldBe(_reRoute.ReRoutes.Count + _reRouteB.ReRoutes.Count); fc.ReRoutes.Count.ShouldBe(_reRouteA.ReRoutes.Count + _reRouteB.ReRoutes.Count);
fc.ReRoutes.ShouldContain(x => x.DownstreamPathTemplate == _reRoute.ReRoutes[0].DownstreamPathTemplate); fc.ReRoutes.ShouldContain(x => x.DownstreamPathTemplate == _reRouteA.ReRoutes[0].DownstreamPathTemplate);
fc.ReRoutes.ShouldContain(x => x.DownstreamPathTemplate == _reRouteB.ReRoutes[0].DownstreamPathTemplate); fc.ReRoutes.ShouldContain(x => x.DownstreamPathTemplate == _reRouteB.ReRoutes[0].DownstreamPathTemplate);
fc.ReRoutes.ShouldContain(x => x.DownstreamPathTemplate == _reRouteB.ReRoutes[1].DownstreamPathTemplate); fc.ReRoutes.ShouldContain(x => x.DownstreamPathTemplate == _reRouteB.ReRoutes[1].DownstreamPathTemplate);
fc.ReRoutes.ShouldContain(x => x.DownstreamScheme == _reRoute.ReRoutes[0].DownstreamScheme); fc.ReRoutes.ShouldContain(x => x.DownstreamScheme == _reRouteA.ReRoutes[0].DownstreamScheme);
fc.ReRoutes.ShouldContain(x => x.DownstreamScheme == _reRouteB.ReRoutes[0].DownstreamScheme); fc.ReRoutes.ShouldContain(x => x.DownstreamScheme == _reRouteB.ReRoutes[0].DownstreamScheme);
fc.ReRoutes.ShouldContain(x => x.DownstreamScheme == _reRouteB.ReRoutes[1].DownstreamScheme); fc.ReRoutes.ShouldContain(x => x.DownstreamScheme == _reRouteB.ReRoutes[1].DownstreamScheme);
fc.ReRoutes.ShouldContain(x => x.Key == _reRoute.ReRoutes[0].Key); fc.ReRoutes.ShouldContain(x => x.Key == _reRouteA.ReRoutes[0].Key);
fc.ReRoutes.ShouldContain(x => x.Key == _reRouteB.ReRoutes[0].Key); fc.ReRoutes.ShouldContain(x => x.Key == _reRouteB.ReRoutes[0].Key);
fc.ReRoutes.ShouldContain(x => x.Key == _reRouteB.ReRoutes[1].Key); fc.ReRoutes.ShouldContain(x => x.Key == _reRouteB.ReRoutes[1].Key);
fc.ReRoutes.ShouldContain(x => x.UpstreamHost == _reRoute.ReRoutes[0].UpstreamHost); fc.ReRoutes.ShouldContain(x => x.UpstreamHost == _reRouteA.ReRoutes[0].UpstreamHost);
fc.ReRoutes.ShouldContain(x => x.UpstreamHost == _reRouteB.ReRoutes[0].UpstreamHost); fc.ReRoutes.ShouldContain(x => x.UpstreamHost == _reRouteB.ReRoutes[0].UpstreamHost);
fc.ReRoutes.ShouldContain(x => x.UpstreamHost == _reRouteB.ReRoutes[1].UpstreamHost); fc.ReRoutes.ShouldContain(x => x.UpstreamHost == _reRouteB.ReRoutes[1].UpstreamHost);

View File

@ -1,151 +1,151 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using Moq; using Moq;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Errors; using Ocelot.Errors;
using Ocelot.Headers; using Ocelot.Headers;
using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Infrastructure.Claims.Parser;
using Ocelot.Responses; using Ocelot.Responses;
using Shouldly; using Shouldly;
using TestStack.BDDfy; using TestStack.BDDfy;
using Xunit; using Xunit;
using System.Net.Http; using System.Net.Http;
using Ocelot.Request.Middleware; using Ocelot.Request.Middleware;
namespace Ocelot.UnitTests.Headers namespace Ocelot.UnitTests.Headers
{ {
public class AddHeadersToRequestTests public class AddHeadersToRequestClaimToThingTests
{ {
private readonly AddHeadersToRequest _addHeadersToRequest; private readonly AddHeadersToRequest _addHeadersToRequest;
private readonly Mock<IClaimsParser> _parser; private readonly Mock<IClaimsParser> _parser;
private readonly DownstreamRequest _downstreamRequest; private readonly DownstreamRequest _downstreamRequest;
private List<Claim> _claims; private List<Claim> _claims;
private List<ClaimToThing> _configuration; private List<ClaimToThing> _configuration;
private Response _result; private Response _result;
private Response<string> _claimValue; private Response<string> _claimValue;
public AddHeadersToRequestTests() public AddHeadersToRequestClaimToThingTests()
{ {
_parser = new Mock<IClaimsParser>(); _parser = new Mock<IClaimsParser>();
_addHeadersToRequest = new AddHeadersToRequest(_parser.Object); _addHeadersToRequest = new AddHeadersToRequest(_parser.Object);
_downstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); _downstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"));
} }
[Fact] [Fact]
public void should_add_headers_to_downstreamRequest() public void should_add_headers_to_downstreamRequest()
{ {
var claims = new List<Claim> var claims = new List<Claim>
{ {
new Claim("test", "data") new Claim("test", "data")
}; };
this.Given( this.Given(
x => x.GivenConfigurationHeaderExtractorProperties(new List<ClaimToThing> x => x.GivenConfigurationHeaderExtractorProperties(new List<ClaimToThing>
{ {
new ClaimToThing("header-key", "", "", 0) new ClaimToThing("header-key", "", "", 0)
})) }))
.Given(x => x.GivenClaims(claims)) .Given(x => x.GivenClaims(claims))
.And(x => x.GivenTheClaimParserReturns(new OkResponse<string>("value"))) .And(x => x.GivenTheClaimParserReturns(new OkResponse<string>("value")))
.When(x => x.WhenIAddHeadersToTheRequest()) .When(x => x.WhenIAddHeadersToTheRequest())
.Then(x => x.ThenTheResultIsSuccess()) .Then(x => x.ThenTheResultIsSuccess())
.And(x => x.ThenTheHeaderIsAdded()) .And(x => x.ThenTheHeaderIsAdded())
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_replace_existing_headers_on_request() public void should_replace_existing_headers_on_request()
{ {
this.Given( this.Given(
x => x.GivenConfigurationHeaderExtractorProperties(new List<ClaimToThing> x => x.GivenConfigurationHeaderExtractorProperties(new List<ClaimToThing>
{ {
new ClaimToThing("header-key", "", "", 0) new ClaimToThing("header-key", "", "", 0)
})) }))
.Given(x => x.GivenClaims(new List<Claim> .Given(x => x.GivenClaims(new List<Claim>
{ {
new Claim("test", "data") new Claim("test", "data")
})) }))
.And(x => x.GivenTheClaimParserReturns(new OkResponse<string>("value"))) .And(x => x.GivenTheClaimParserReturns(new OkResponse<string>("value")))
.And(x => x.GivenThatTheRequestContainsHeader("header-key", "initial")) .And(x => x.GivenThatTheRequestContainsHeader("header-key", "initial"))
.When(x => x.WhenIAddHeadersToTheRequest()) .When(x => x.WhenIAddHeadersToTheRequest())
.Then(x => x.ThenTheResultIsSuccess()) .Then(x => x.ThenTheResultIsSuccess())
.And(x => x.ThenTheHeaderIsAdded()) .And(x => x.ThenTheHeaderIsAdded())
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
public void should_return_error() public void should_return_error()
{ {
this.Given( this.Given(
x => x.GivenConfigurationHeaderExtractorProperties(new List<ClaimToThing> x => x.GivenConfigurationHeaderExtractorProperties(new List<ClaimToThing>
{ {
new ClaimToThing("", "", "", 0) new ClaimToThing("", "", "", 0)
})) }))
.Given(x => x.GivenClaims(new List<Claim>())) .Given(x => x.GivenClaims(new List<Claim>()))
.And(x => x.GivenTheClaimParserReturns(new ErrorResponse<string>(new List<Error> .And(x => x.GivenTheClaimParserReturns(new ErrorResponse<string>(new List<Error>
{ {
new AnyError() new AnyError()
}))) })))
.When(x => x.WhenIAddHeadersToTheRequest()) .When(x => x.WhenIAddHeadersToTheRequest())
.Then(x => x.ThenTheResultIsError()) .Then(x => x.ThenTheResultIsError())
.BDDfy(); .BDDfy();
} }
private void GivenClaims(List<Claim> claims) private void GivenClaims(List<Claim> claims)
{ {
_claims = claims; _claims = claims;
} }
private void GivenConfigurationHeaderExtractorProperties(List<ClaimToThing> configuration) private void GivenConfigurationHeaderExtractorProperties(List<ClaimToThing> configuration)
{ {
_configuration = configuration; _configuration = configuration;
} }
private void GivenThatTheRequestContainsHeader(string key, string value) private void GivenThatTheRequestContainsHeader(string key, string value)
{ {
_downstreamRequest.Headers.Add(key, value); _downstreamRequest.Headers.Add(key, value);
} }
private void GivenTheClaimParserReturns(Response<string> claimValue) private void GivenTheClaimParserReturns(Response<string> claimValue)
{ {
_claimValue = claimValue; _claimValue = claimValue;
_parser _parser
.Setup( .Setup(
x => x =>
x.GetValue(It.IsAny<IEnumerable<Claim>>(), x.GetValue(It.IsAny<IEnumerable<Claim>>(),
It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<int>())) It.IsAny<int>()))
.Returns(_claimValue); .Returns(_claimValue);
} }
private void WhenIAddHeadersToTheRequest() private void WhenIAddHeadersToTheRequest()
{ {
_result = _addHeadersToRequest.SetHeadersOnDownstreamRequest(_configuration, _claims, _downstreamRequest); _result = _addHeadersToRequest.SetHeadersOnDownstreamRequest(_configuration, _claims, _downstreamRequest);
} }
private void ThenTheResultIsSuccess() private void ThenTheResultIsSuccess()
{ {
_result.IsError.ShouldBe(false); _result.IsError.ShouldBe(false);
} }
private void ThenTheResultIsError() private void ThenTheResultIsError()
{ {
_result.IsError.ShouldBe(true); _result.IsError.ShouldBe(true);
} }
private void ThenTheHeaderIsAdded() private void ThenTheHeaderIsAdded()
{ {
var header = _downstreamRequest.Headers.First(x => x.Key == "header-key"); var header = _downstreamRequest.Headers.First(x => x.Key == "header-key");
header.Value.First().ShouldBe(_claimValue.Data); header.Value.First().ShouldBe(_claimValue.Data);
} }
class AnyError : Error class AnyError : Error
{ {
public AnyError() public AnyError()
: base("blahh", OcelotErrorCode.UnknownError) : base("blahh", OcelotErrorCode.UnknownError)
{ {
} }
} }
} }
} }

View File

@ -0,0 +1,75 @@
using Microsoft.AspNetCore.Http;
using Moq;
using Ocelot.Configuration.Creator;
using Ocelot.Headers;
using Ocelot.Infrastructure.Claims.Parser;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Headers
{
public class AddHeadersToRequestPlainTests
{
private readonly AddHeadersToRequest _addHeadersToRequest;
private HttpContext _context;
private AddHeader _addedHeader;
public AddHeadersToRequestPlainTests()
{
_addHeadersToRequest = new AddHeadersToRequest(Mock.Of<IClaimsParser>());
}
[Fact]
public void should_add_plain_text_header_to_downstream_request()
{
this.Given(_ => GivenHttpRequestWithoutHeaders())
.When(_ => WhenAddingHeader("X-Custom-Header", "PlainValue"))
.Then(_ => ThenTheHeaderGetsTakenOverToTheRequestHeaders())
.BDDfy();
}
[Fact]
public void should_overwrite_existing_header_with_added_header()
{
this.Given(_ => GivenHttpRequestWithHeader("X-Custom-Header", "This should get overwritten"))
.When(_ => WhenAddingHeader("X-Custom-Header", "PlainValue"))
.Then(_ => ThenTheHeaderGetsTakenOverToTheRequestHeaders())
.BDDfy();
}
private void GivenHttpRequestWithoutHeaders()
{
_context = new DefaultHttpContext();
}
private void GivenHttpRequestWithHeader(string headerKey, string headerValue)
{
_context = new DefaultHttpContext
{
Request =
{
Headers =
{
{ headerKey, headerValue }
}
}
};
}
private void WhenAddingHeader(string headerKey, string headerValue)
{
_addedHeader = new AddHeader(headerKey, headerValue);
_addHeadersToRequest.SetHeadersOnDownstreamRequest(new[] { _addedHeader }, _context);
}
private void ThenTheHeaderGetsTakenOverToTheRequestHeaders()
{
var requestHeaders = _context.Request.Headers;
requestHeaders.ContainsKey(_addedHeader.Key).ShouldBeTrue($"Header {_addedHeader.Key} was expected but not there.");
var value = requestHeaders[_addedHeader.Key];
value.ShouldNotBeNull($"Value of header {_addedHeader.Key} was expected to not be null.");
value.ToString().ShouldBe(_addedHeader.Value);
}
}
}

View File

@ -27,7 +27,8 @@ namespace Ocelot.UnitTests.Headers
private readonly HttpHeadersTransformationMiddleware _middleware; private readonly HttpHeadersTransformationMiddleware _middleware;
private readonly DownstreamContext _downstreamContext; private readonly DownstreamContext _downstreamContext;
private OcelotRequestDelegate _next; private OcelotRequestDelegate _next;
private readonly Mock<IAddHeadersToResponse> _addHeaders; private readonly Mock<IAddHeadersToResponse> _addHeadersToResponse;
private readonly Mock<IAddHeadersToRequest> _addHeadersToRequest;
public HttpHeadersTransformationMiddlewareTests() public HttpHeadersTransformationMiddlewareTests()
{ {
@ -38,8 +39,11 @@ namespace Ocelot.UnitTests.Headers
_logger = new Mock<IOcelotLogger>(); _logger = new Mock<IOcelotLogger>();
_loggerFactory.Setup(x => x.CreateLogger<AuthorisationMiddleware>()).Returns(_logger.Object); _loggerFactory.Setup(x => x.CreateLogger<AuthorisationMiddleware>()).Returns(_logger.Object);
_next = context => Task.CompletedTask; _next = context => Task.CompletedTask;
_addHeaders = new Mock<IAddHeadersToResponse>(); _addHeadersToResponse = new Mock<IAddHeadersToResponse>();
_middleware = new HttpHeadersTransformationMiddleware(_next, _loggerFactory.Object, _preReplacer.Object, _postReplacer.Object, _addHeaders.Object); _addHeadersToRequest = new Mock<IAddHeadersToRequest>();
_middleware = new HttpHeadersTransformationMiddleware(
_next, _loggerFactory.Object, _preReplacer.Object,
_postReplacer.Object, _addHeadersToResponse.Object, _addHeadersToRequest.Object);
} }
[Fact] [Fact]
@ -51,17 +55,24 @@ namespace Ocelot.UnitTests.Headers
.And(x => GivenTheHttpResponseMessageIs()) .And(x => GivenTheHttpResponseMessageIs())
.When(x => WhenICallTheMiddleware()) .When(x => WhenICallTheMiddleware())
.Then(x => ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly()) .Then(x => ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly())
.Then(x => ThenAddHeadersToRequestIsCalledCorrectly())
.And(x => ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly()) .And(x => ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly())
.And(x => ThenAddHeadersIsCalledCorrectly()) .And(x => ThenAddHeadersToResponseIsCalledCorrectly())
.BDDfy(); .BDDfy();
} }
private void ThenAddHeadersIsCalledCorrectly() private void ThenAddHeadersToResponseIsCalledCorrectly()
{ {
_addHeaders _addHeadersToResponse
.Verify(x => x.Add(_downstreamContext.DownstreamReRoute.AddHeadersToDownstream, _downstreamContext.DownstreamResponse), Times.Once); .Verify(x => x.Add(_downstreamContext.DownstreamReRoute.AddHeadersToDownstream, _downstreamContext.DownstreamResponse), Times.Once);
} }
private void ThenAddHeadersToRequestIsCalledCorrectly()
{
_addHeadersToRequest
.Verify(x => x.SetHeadersOnDownstreamRequest(_downstreamContext.DownstreamReRoute.AddHeadersToUpstream, _downstreamContext.HttpContext), Times.Once);
}
private void WhenICallTheMiddleware() private void WhenICallTheMiddleware()
{ {
_middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();