diff --git a/docs/features/headerstransformation.rst b/docs/features/headerstransformation.rst index 06ece33a6..744227e20 100644 --- a/docs/features/headerstransformation.rst +++ b/docs/features/headerstransformation.rst @@ -1,13 +1,29 @@ 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 `_ and I decided that it was going to be useful in various ways. +Add to Request +^^^^^^^^^^^^^^ + +This feature was requestes in `GitHub #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 ^^^^^^^^^^^^^^^ -This feature was requested in `GitHub #280 `_. I have only implemented -for responses but could add for requests in the future. +This feature was requested in `GitHub #280 `_. 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 -^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^ 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. diff --git a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs index d6dc0f34c..49931421c 100644 --- a/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs +++ b/src/Ocelot/Configuration/Builder/DownstreamReRouteBuilder.cs @@ -39,12 +39,14 @@ public class DownstreamReRouteBuilder private string _key; private List _delegatingHandlers; private List _addHeadersToDownstream; + private List _addHeadersToUpstream; public DownstreamReRouteBuilder() { _downstreamAddresses = new List(); _delegatingHandlers = new List(); _addHeadersToDownstream = new List(); + _addHeadersToUpstream = new List(); } public DownstreamReRouteBuilder WithDownstreamAddresses(List downstreamAddresses) @@ -233,6 +235,12 @@ public DownstreamReRouteBuilder WithAddHeadersToDownstream(List addHe return this; } + public DownstreamReRouteBuilder WithAddHeadersToUpstream(List addHeadersToUpstream) + { + _addHeadersToUpstream = addHeadersToUpstream; + return this; + } + public DownstreamReRoute Build() { return new DownstreamReRoute( @@ -263,7 +271,8 @@ public DownstreamReRoute Build() new PathTemplate(_downstreamPathTemplate), _reRouteKey, _delegatingHandlers, - _addHeadersToDownstream); + _addHeadersToDownstream, + _addHeadersToUpstream); } } } diff --git a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs index fa77402b1..c14e4aab1 100644 --- a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs @@ -1,227 +1,228 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.Options; -using Ocelot.Cache; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Validator; -using Ocelot.DependencyInjection; -using Ocelot.Logging; -using Ocelot.Responses; - -namespace Ocelot.Configuration.Creator -{ - /// - /// Register as singleton - /// - public class FileInternalConfigurationCreator : IInternalConfigurationCreator - { - private readonly IConfigurationValidator _configurationValidator; - private readonly IOcelotLogger _logger; - private readonly IClaimsToThingCreator _claimsToThingCreator; - private readonly IAuthenticationOptionsCreator _authOptionsCreator; - private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; - private readonly IRequestIdKeyCreator _requestIdKeyCreator; - private readonly IServiceProviderConfigurationCreator _serviceProviderConfigCreator; - private readonly IQoSOptionsCreator _qosOptionsCreator; - private readonly IReRouteOptionsCreator _fileReRouteOptionsCreator; - private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator; - private readonly IRegionCreator _regionCreator; - private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; - private readonly IAdministrationPath _adminPath; - private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator; - private readonly IDownstreamAddressesCreator _downstreamAddressesCreator; - - public FileInternalConfigurationCreator( - IConfigurationValidator configurationValidator, - IOcelotLoggerFactory loggerFactory, - IClaimsToThingCreator claimsToThingCreator, - IAuthenticationOptionsCreator authOptionsCreator, - IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator, - IRequestIdKeyCreator requestIdKeyCreator, - IServiceProviderConfigurationCreator serviceProviderConfigCreator, - IQoSOptionsCreator qosOptionsCreator, - IReRouteOptionsCreator fileReRouteOptionsCreator, - IRateLimitOptionsCreator rateLimitOptionsCreator, - IRegionCreator regionCreator, - IHttpHandlerOptionsCreator httpHandlerOptionsCreator, - IAdministrationPath adminPath, - IHeaderFindAndReplaceCreator headerFAndRCreator, - IDownstreamAddressesCreator downstreamAddressesCreator - ) - { - _downstreamAddressesCreator = downstreamAddressesCreator; - _headerFAndRCreator = headerFAndRCreator; - _adminPath = adminPath; - _regionCreator = regionCreator; - _rateLimitOptionsCreator = rateLimitOptionsCreator; - _requestIdKeyCreator = requestIdKeyCreator; - _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; - _authOptionsCreator = authOptionsCreator; - _configurationValidator = configurationValidator; - _logger = loggerFactory.CreateLogger(); - _claimsToThingCreator = claimsToThingCreator; - _serviceProviderConfigCreator = serviceProviderConfigCreator; - _qosOptionsCreator = qosOptionsCreator; - _fileReRouteOptionsCreator = fileReRouteOptionsCreator; - _httpHandlerOptionsCreator = httpHandlerOptionsCreator; - } - - public async Task> Create(FileConfiguration fileConfiguration) - { - var config = await SetUpConfiguration(fileConfiguration); - return config; - } - - private async Task> SetUpConfiguration(FileConfiguration fileConfiguration) - { - var response = await _configurationValidator.IsValid(fileConfiguration); - - if (response.Data.IsError) - { - return new ErrorResponse(response.Data.Errors); - } - - var reRoutes = new List(); - - foreach (var reRoute in fileConfiguration.ReRoutes) - { - var downstreamReRoute = SetUpDownstreamReRoute(reRoute, fileConfiguration.GlobalConfiguration); - - var ocelotReRoute = SetUpReRoute(reRoute, downstreamReRoute); - - reRoutes.Add(ocelotReRoute); - } - - foreach (var aggregate in fileConfiguration.Aggregates) - { - var ocelotReRoute = SetUpAggregateReRoute(reRoutes, aggregate, fileConfiguration.GlobalConfiguration); - reRoutes.Add(ocelotReRoute); - } - - var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration); - - var config = new InternalConfiguration(reRoutes, _adminPath.Path, serviceProviderConfiguration, fileConfiguration.GlobalConfiguration.RequestIdKey); - - return new OkResponse(config); - } - - public ReRoute SetUpAggregateReRoute(List reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) - { - var applicableReRoutes = reRoutes - .SelectMany(x => x.DownstreamReRoute) - .Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key)) - .ToList(); - - if(applicableReRoutes.Count != aggregateReRoute.ReRouteKeys.Count) - { - //todo - log or throw or return error whatever? - } - - //make another re route out of these - var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(aggregateReRoute); - - var reRoute = new ReRouteBuilder() - .WithUpstreamPathTemplate(aggregateReRoute.UpstreamPathTemplate) - .WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod) - .WithUpstreamTemplatePattern(upstreamTemplatePattern) - .WithDownstreamReRoutes(applicableReRoutes) - .WithUpstreamHost(aggregateReRoute.UpstreamHost) - .WithAggregator(aggregateReRoute.Aggregator) - .Build(); - - return reRoute; - } - - private ReRoute SetUpReRoute(FileReRoute fileReRoute, DownstreamReRoute downstreamReRoutes) - { - var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); - - var reRoute = new ReRouteBuilder() - .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) - .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) - .WithUpstreamTemplatePattern(upstreamTemplatePattern) - .WithDownstreamReRoute(downstreamReRoutes) - .WithUpstreamHost(fileReRoute.UpstreamHost) - .Build(); - - return reRoute; - } - - private DownstreamReRoute SetUpDownstreamReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) - { - var fileReRouteOptions = _fileReRouteOptionsCreator.Create(fileReRoute); - - var requestIdKey = _requestIdKeyCreator.Create(fileReRoute, globalConfiguration); - - var reRouteKey = CreateReRouteKey(fileReRoute); - - var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); - - var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute); - - var claimsToHeaders = _claimsToThingCreator.Create(fileReRoute.AddHeadersToRequest); - - var claimsToClaims = _claimsToThingCreator.Create(fileReRoute.AddClaimsToRequest); - - var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest); - - var qosOptions = _qosOptionsCreator.Create(fileReRoute); - - var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting); - - var region = _regionCreator.Create(fileReRoute); - - var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileReRoute); - - var hAndRs = _headerFAndRCreator.Create(fileReRoute); - - var downstreamAddresses = _downstreamAddressesCreator.Create(fileReRoute); - - var reRoute = new DownstreamReRouteBuilder() - .WithKey(fileReRoute.Key) - .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) - .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) - .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) - .WithUpstreamTemplatePattern(upstreamTemplatePattern) - .WithIsAuthenticated(fileReRouteOptions.IsAuthenticated) - .WithAuthenticationOptions(authOptionsForRoute) - .WithClaimsToHeaders(claimsToHeaders) - .WithClaimsToClaims(claimsToClaims) - .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) - .WithIsAuthorised(fileReRouteOptions.IsAuthorised) - .WithClaimsToQueries(claimsToQueries) - .WithRequestIdKey(requestIdKey) - .WithIsCached(fileReRouteOptions.IsCached) - .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region)) - .WithDownstreamScheme(fileReRoute.DownstreamScheme) - .WithLoadBalancer(fileReRoute.LoadBalancer) - .WithDownstreamAddresses(downstreamAddresses) - .WithReRouteKey(reRouteKey) - .WithIsQos(fileReRouteOptions.IsQos) - .WithQosOptions(qosOptions) - .WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting) - .WithRateLimitOptions(rateLimitOption) - .WithHttpHandlerOptions(httpHandlerOptions) - .WithServiceName(fileReRoute.ServiceName) - .WithUseServiceDiscovery(fileReRoute.UseServiceDiscovery) - .WithUpstreamHeaderFindAndReplace(hAndRs.Upstream) - .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) - .WithUpstreamHost(fileReRoute.UpstreamHost) - .WithDelegatingHandlers(fileReRoute.DelegatingHandlers) - .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) - .Build(); - - return reRoute; - } - - 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)}"; - return loadBalancerKey; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Ocelot.Cache; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Validator; +using Ocelot.DependencyInjection; +using Ocelot.Logging; +using Ocelot.Responses; + +namespace Ocelot.Configuration.Creator +{ + /// + /// Register as singleton + /// + public class FileInternalConfigurationCreator : IInternalConfigurationCreator + { + private readonly IConfigurationValidator _configurationValidator; + private readonly IOcelotLogger _logger; + private readonly IClaimsToThingCreator _claimsToThingCreator; + private readonly IAuthenticationOptionsCreator _authOptionsCreator; + private readonly IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator; + private readonly IRequestIdKeyCreator _requestIdKeyCreator; + private readonly IServiceProviderConfigurationCreator _serviceProviderConfigCreator; + private readonly IQoSOptionsCreator _qosOptionsCreator; + private readonly IReRouteOptionsCreator _fileReRouteOptionsCreator; + private readonly IRateLimitOptionsCreator _rateLimitOptionsCreator; + private readonly IRegionCreator _regionCreator; + private readonly IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; + private readonly IAdministrationPath _adminPath; + private readonly IHeaderFindAndReplaceCreator _headerFAndRCreator; + private readonly IDownstreamAddressesCreator _downstreamAddressesCreator; + + public FileInternalConfigurationCreator( + IConfigurationValidator configurationValidator, + IOcelotLoggerFactory loggerFactory, + IClaimsToThingCreator claimsToThingCreator, + IAuthenticationOptionsCreator authOptionsCreator, + IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator, + IRequestIdKeyCreator requestIdKeyCreator, + IServiceProviderConfigurationCreator serviceProviderConfigCreator, + IQoSOptionsCreator qosOptionsCreator, + IReRouteOptionsCreator fileReRouteOptionsCreator, + IRateLimitOptionsCreator rateLimitOptionsCreator, + IRegionCreator regionCreator, + IHttpHandlerOptionsCreator httpHandlerOptionsCreator, + IAdministrationPath adminPath, + IHeaderFindAndReplaceCreator headerFAndRCreator, + IDownstreamAddressesCreator downstreamAddressesCreator + ) + { + _downstreamAddressesCreator = downstreamAddressesCreator; + _headerFAndRCreator = headerFAndRCreator; + _adminPath = adminPath; + _regionCreator = regionCreator; + _rateLimitOptionsCreator = rateLimitOptionsCreator; + _requestIdKeyCreator = requestIdKeyCreator; + _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; + _authOptionsCreator = authOptionsCreator; + _configurationValidator = configurationValidator; + _logger = loggerFactory.CreateLogger(); + _claimsToThingCreator = claimsToThingCreator; + _serviceProviderConfigCreator = serviceProviderConfigCreator; + _qosOptionsCreator = qosOptionsCreator; + _fileReRouteOptionsCreator = fileReRouteOptionsCreator; + _httpHandlerOptionsCreator = httpHandlerOptionsCreator; + } + + public async Task> Create(FileConfiguration fileConfiguration) + { + var config = await SetUpConfiguration(fileConfiguration); + return config; + } + + private async Task> SetUpConfiguration(FileConfiguration fileConfiguration) + { + var response = await _configurationValidator.IsValid(fileConfiguration); + + if (response.Data.IsError) + { + return new ErrorResponse(response.Data.Errors); + } + + var reRoutes = new List(); + + foreach (var reRoute in fileConfiguration.ReRoutes) + { + var downstreamReRoute = SetUpDownstreamReRoute(reRoute, fileConfiguration.GlobalConfiguration); + + var ocelotReRoute = SetUpReRoute(reRoute, downstreamReRoute); + + reRoutes.Add(ocelotReRoute); + } + + foreach (var aggregate in fileConfiguration.Aggregates) + { + var ocelotReRoute = SetUpAggregateReRoute(reRoutes, aggregate, fileConfiguration.GlobalConfiguration); + reRoutes.Add(ocelotReRoute); + } + + var serviceProviderConfiguration = _serviceProviderConfigCreator.Create(fileConfiguration.GlobalConfiguration); + + var config = new InternalConfiguration(reRoutes, _adminPath.Path, serviceProviderConfiguration, fileConfiguration.GlobalConfiguration.RequestIdKey); + + return new OkResponse(config); + } + + public ReRoute SetUpAggregateReRoute(List reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) + { + var applicableReRoutes = reRoutes + .SelectMany(x => x.DownstreamReRoute) + .Where(r => aggregateReRoute.ReRouteKeys.Contains(r.Key)) + .ToList(); + + if(applicableReRoutes.Count != aggregateReRoute.ReRouteKeys.Count) + { + //todo - log or throw or return error whatever? + } + + //make another re route out of these + var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(aggregateReRoute); + + var reRoute = new ReRouteBuilder() + .WithUpstreamPathTemplate(aggregateReRoute.UpstreamPathTemplate) + .WithUpstreamHttpMethod(aggregateReRoute.UpstreamHttpMethod) + .WithUpstreamTemplatePattern(upstreamTemplatePattern) + .WithDownstreamReRoutes(applicableReRoutes) + .WithUpstreamHost(aggregateReRoute.UpstreamHost) + .WithAggregator(aggregateReRoute.Aggregator) + .Build(); + + return reRoute; + } + + private ReRoute SetUpReRoute(FileReRoute fileReRoute, DownstreamReRoute downstreamReRoutes) + { + var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); + + var reRoute = new ReRouteBuilder() + .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) + .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) + .WithUpstreamTemplatePattern(upstreamTemplatePattern) + .WithDownstreamReRoute(downstreamReRoutes) + .WithUpstreamHost(fileReRoute.UpstreamHost) + .Build(); + + return reRoute; + } + + private DownstreamReRoute SetUpDownstreamReRoute(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration) + { + var fileReRouteOptions = _fileReRouteOptionsCreator.Create(fileReRoute); + + var requestIdKey = _requestIdKeyCreator.Create(fileReRoute, globalConfiguration); + + var reRouteKey = CreateReRouteKey(fileReRoute); + + var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute); + + var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute); + + var claimsToHeaders = _claimsToThingCreator.Create(fileReRoute.AddHeadersToRequest); + + var claimsToClaims = _claimsToThingCreator.Create(fileReRoute.AddClaimsToRequest); + + var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest); + + var qosOptions = _qosOptionsCreator.Create(fileReRoute); + + var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting); + + var region = _regionCreator.Create(fileReRoute); + + var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileReRoute); + + var hAndRs = _headerFAndRCreator.Create(fileReRoute); + + var downstreamAddresses = _downstreamAddressesCreator.Create(fileReRoute); + + var reRoute = new DownstreamReRouteBuilder() + .WithKey(fileReRoute.Key) + .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) + .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) + .WithUpstreamHttpMethod(fileReRoute.UpstreamHttpMethod) + .WithUpstreamTemplatePattern(upstreamTemplatePattern) + .WithIsAuthenticated(fileReRouteOptions.IsAuthenticated) + .WithAuthenticationOptions(authOptionsForRoute) + .WithClaimsToHeaders(claimsToHeaders) + .WithClaimsToClaims(claimsToClaims) + .WithRouteClaimsRequirement(fileReRoute.RouteClaimsRequirement) + .WithIsAuthorised(fileReRouteOptions.IsAuthorised) + .WithClaimsToQueries(claimsToQueries) + .WithRequestIdKey(requestIdKey) + .WithIsCached(fileReRouteOptions.IsCached) + .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region)) + .WithDownstreamScheme(fileReRoute.DownstreamScheme) + .WithLoadBalancer(fileReRoute.LoadBalancer) + .WithDownstreamAddresses(downstreamAddresses) + .WithReRouteKey(reRouteKey) + .WithIsQos(fileReRouteOptions.IsQos) + .WithQosOptions(qosOptions) + .WithEnableRateLimiting(fileReRouteOptions.EnableRateLimiting) + .WithRateLimitOptions(rateLimitOption) + .WithHttpHandlerOptions(httpHandlerOptions) + .WithServiceName(fileReRoute.ServiceName) + .WithUseServiceDiscovery(fileReRoute.UseServiceDiscovery) + .WithUpstreamHeaderFindAndReplace(hAndRs.Upstream) + .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) + .WithUpstreamHost(fileReRoute.UpstreamHost) + .WithDelegatingHandlers(fileReRoute.DelegatingHandlers) + .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) + .WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream) + .Build(); + + return reRoute; + } + + 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)}"; + return loadBalancerKey; + } + } +} diff --git a/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs b/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs index 7bfc6d8ef..1a5f1b6a1 100644 --- a/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs +++ b/src/Ocelot/Configuration/Creator/HeaderFindAndReplaceCreator.cs @@ -22,23 +22,31 @@ public HeaderFindAndReplaceCreator(IPlaceholders placeholders, IOcelotLoggerFact public HeaderTransformations Create(FileReRoute fileReRoute) { var upstream = new List(); + var addHeadersToUpstream = new List(); foreach(var input in fileReRoute.UpstreamHeaderTransform) { - var hAndr = Map(input); - if(!hAndr.IsError) + if (input.Value.Contains(",")) { - 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 { - _logger.LogWarning($"Unable to add UpstreamHeaderTransform {input.Key}: {input.Value}"); + addHeadersToUpstream.Add(new AddHeader(input.Key, input.Value)); } } var downstream = new List(); var addHeadersToDownstream = new List(); - + foreach(var input in fileReRoute.DownstreamHeaderTransform) { if(input.Value.Contains(",")) @@ -59,7 +67,7 @@ public HeaderTransformations Create(FileReRoute fileReRoute) } } - return new HeaderTransformations(upstream, downstream, addHeadersToDownstream); + return new HeaderTransformations(upstream, downstream, addHeadersToDownstream, addHeadersToUpstream); } private Response Map(KeyValuePair input) diff --git a/src/Ocelot/Configuration/Creator/HeaderTransformations.cs b/src/Ocelot/Configuration/Creator/HeaderTransformations.cs index 461e5c350..72b6e1f66 100644 --- a/src/Ocelot/Configuration/Creator/HeaderTransformations.cs +++ b/src/Ocelot/Configuration/Creator/HeaderTransformations.cs @@ -7,9 +7,11 @@ public class HeaderTransformations public HeaderTransformations( List upstream, List downstream, - List addHeader) + List addHeaderToDownstream, + List addHeaderToUpstream) { - AddHeadersToDownstream = addHeader; + AddHeadersToDownstream = addHeaderToDownstream; + AddHeadersToUpstream = addHeaderToUpstream; Upstream = upstream; Downstream = downstream; } @@ -19,5 +21,6 @@ public HeaderTransformations( public List Downstream { get; } public List AddHeadersToDownstream { get; } + public List AddHeadersToUpstream { get; } } } diff --git a/src/Ocelot/Configuration/DownstreamReRoute.cs b/src/Ocelot/Configuration/DownstreamReRoute.cs index 00accc07a..90e96ec53 100644 --- a/src/Ocelot/Configuration/DownstreamReRoute.cs +++ b/src/Ocelot/Configuration/DownstreamReRoute.cs @@ -34,7 +34,8 @@ public DownstreamReRoute( PathTemplate downstreamPathTemplate, string reRouteKey, List delegatingHandlers, - List addHeadersToDownstream) + List addHeadersToDownstream, + List addHeadersToUpstream) { AddHeadersToDownstream = addHeadersToDownstream; DelegatingHandlers = delegatingHandlers; @@ -64,6 +65,7 @@ public DownstreamReRoute( AuthenticationOptions = authenticationOptions; DownstreamPathTemplate = downstreamPathTemplate; ReRouteKey = reRouteKey; + AddHeadersToUpstream = addHeadersToUpstream; } public string Key { get; private set; } @@ -94,5 +96,6 @@ public DownstreamReRoute( public string ReRouteKey { get; private set; } public List DelegatingHandlers {get;private set;} public List AddHeadersToDownstream {get;private set;} + public List AddHeadersToUpstream { get; private set; } } } diff --git a/src/Ocelot/Headers/AddHeadersToRequest.cs b/src/Ocelot/Headers/AddHeadersToRequest.cs index 1a71935a6..a7b5b6898 100644 --- a/src/Ocelot/Headers/AddHeadersToRequest.cs +++ b/src/Ocelot/Headers/AddHeadersToRequest.cs @@ -4,6 +4,7 @@ using Ocelot.Infrastructure.Claims.Parser; using Ocelot.Responses; using System.Net.Http; +using Microsoft.AspNetCore.Http; using Ocelot.Configuration.Creator; using Ocelot.Request.Middleware; @@ -41,5 +42,19 @@ public Response SetHeadersOnDownstreamRequest(List claimsToThings, return new OkResponse(); } + + public void SetHeadersOnDownstreamRequest(IEnumerable 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); + } + } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Headers/IAddHeadersToRequest.cs b/src/Ocelot/Headers/IAddHeadersToRequest.cs index c8b1c9672..a0afb0c0f 100644 --- a/src/Ocelot/Headers/IAddHeadersToRequest.cs +++ b/src/Ocelot/Headers/IAddHeadersToRequest.cs @@ -1,4 +1,6 @@ -namespace Ocelot.Headers +using Microsoft.AspNetCore.Http; + +namespace Ocelot.Headers { using System.Collections.Generic; using System.Net.Http; @@ -12,5 +14,6 @@ public interface IAddHeadersToRequest { Response SetHeadersOnDownstreamRequest(List claimsToThings, IEnumerable claims, DownstreamRequest downstreamRequest); + void SetHeadersOnDownstreamRequest(IEnumerable headers, HttpContext context); } } diff --git a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs index 9544fe7fb..9dfe3d641 100644 --- a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs @@ -9,16 +9,19 @@ public class HttpHeadersTransformationMiddleware : OcelotMiddleware private readonly OcelotRequestDelegate _next; private readonly IHttpContextRequestHeaderReplacer _preReplacer; private readonly IHttpResponseHeaderReplacer _postReplacer; - private readonly IAddHeadersToResponse _addHeaders; + private readonly IAddHeadersToResponse _addHeadersToResponse; + private readonly IAddHeadersToRequest _addHeadersToRequest; public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, IHttpContextRequestHeaderReplacer preReplacer, IHttpResponseHeaderReplacer postReplacer, - IAddHeadersToResponse addHeaders) + IAddHeadersToResponse addHeadersToResponse, + IAddHeadersToRequest addHeadersToRequest) :base(loggerFactory.CreateLogger()) { - _addHeaders = addHeaders; + _addHeadersToResponse = addHeadersToResponse; + _addHeadersToRequest = addHeadersToRequest; _next = next; _postReplacer = postReplacer; _preReplacer = preReplacer; @@ -31,13 +34,15 @@ public async Task Invoke(DownstreamContext context) //todo - this should be on httprequestmessage not httpcontext? _preReplacer.Replace(context.HttpContext, preFAndRs); + _addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.AddHeadersToUpstream, context.HttpContext); + await _next.Invoke(context); var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace; _postReplacer.Replace(context.DownstreamResponse, postFAndRs, context.DownstreamRequest); - _addHeaders.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse); + _addHeadersToResponse.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse); } } } diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index 5ff5e165b..40fe4f02f 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -819,6 +819,7 @@ private void ThenTheReRoutesAre(List expectedReRoutes) result.DownstreamReRoute[0].RequestIdKey.ShouldBe(expected.DownstreamReRoute[0].RequestIdKey); result.DownstreamReRoute[0].DelegatingHandlers.ShouldBe(expected.DownstreamReRoute[0].DelegatingHandlers); result.DownstreamReRoute[0].AddHeadersToDownstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToDownstream); + result.DownstreamReRoute[0].AddHeadersToUpstream.ShouldBe(expected.DownstreamReRoute[0].AddHeadersToUpstream, "AddHeadersToUpstream should be set"); } } @@ -901,7 +902,7 @@ private void ThenTheHeaderFindAndReplaceCreatorIsCalledCorrectly() private void GivenTheHeaderFindAndReplaceCreatorReturns() { - _headerFindAndReplaceCreator.Setup(x => x.Create(It.IsAny())).Returns(new HeaderTransformations(new List(), new List(), new List())); + _headerFindAndReplaceCreator.Setup(x => x.Create(It.IsAny())).Returns(new HeaderTransformations(new List(), new List(), new List(), new List())); } private void GivenTheFollowingIsReturned(ServiceProviderConfiguration serviceProviderConfiguration) diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs index c492af44f..afff32f9d 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationRepositoryTests.cs @@ -12,13 +12,17 @@ namespace Ocelot.UnitTests.Configuration { - public class FileConfigurationRepositoryTests + public class FileConfigurationRepositoryTests : IDisposable { private readonly Mock _hostingEnvironment = new Mock(); private IFileConfigurationRepository _repo; private FileConfiguration _result; 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() { @@ -221,5 +225,10 @@ private FileConfiguration FakeFileConfigurationForGet() ReRoutes = reRoutes }; } + + public void Dispose() + { + File.Delete($"./ocelot.{_environmentName}.json"); + } } } diff --git a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs index 1ec251cf9..df37ca8bc 100644 --- a/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HeaderFindAndReplaceCreatorTests.cs @@ -149,7 +149,6 @@ public void should_use_base_url_partial_placeholder() .Then(x => ThenTheFollowingDownstreamIsReturned(downstream)) .BDDfy(); } - [Fact] public void should_add_trace_id_header() { @@ -166,7 +165,45 @@ public void should_add_trace_id_header() this.Given(x => GivenTheReRoute(reRoute)) .And(x => GivenTheBaseUrlIs("http://ocelot.com/")) .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 + { + {"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 + { + {"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(); } @@ -180,11 +217,17 @@ private void GivenTheBaseUrlErrors() _placeholders.Setup(x => x.Get(It.IsAny())).Returns(new ErrorResponse(new AnyError())); } - private void ThenTheFollowingAddHeaderIsReturned(AddHeader addHeader) + private void ThenTheFollowingAddHeaderToDownstreamIsReturned(AddHeader addHeader) { _result.AddHeadersToDownstream[0].Key.ShouldBe(addHeader.Key); _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 downstream) { diff --git a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs index 67a1feabf..90018b3ea 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs @@ -6,6 +6,7 @@ namespace Ocelot.UnitTests.DependencyInjection { + using System; using System.Collections.Generic; using System.IO; using Newtonsoft.Json; @@ -17,7 +18,7 @@ public class ConfigurationBuilderExtensionsTests private string _result; private IConfigurationRoot _configRoot; private FileConfiguration _globalConfig; - private FileConfiguration _reRoute; + private FileConfiguration _reRouteA; private FileConfiguration _reRouteB; private FileConfiguration _aggregate; @@ -64,7 +65,7 @@ private void GivenMultipleConfigurationFiles() } }; - _reRoute = new FileConfiguration + _reRouteA = new FileConfiguration { ReRoutes = new List { @@ -160,17 +161,10 @@ private void GivenMultipleConfigurationFiles() } }; - var globalJson = JsonConvert.SerializeObject(_globalConfig); - File.WriteAllText("ocelot.global.json", globalJson); - - var reRouteJson = JsonConvert.SerializeObject(_reRoute); - 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); + File.WriteAllText("ocelot.global.json", JsonConvert.SerializeObject(_globalConfig)); + File.WriteAllText("ocelot.reRoutesA.json", JsonConvert.SerializeObject(_reRouteA)); + File.WriteAllText("ocelot.reRoutesB.json", JsonConvert.SerializeObject(_reRouteB)); + File.WriteAllText("ocelot.aggregates.json", JsonConvert.SerializeObject(_aggregate)); } private void WhenIAddOcelotConfiguration() @@ -195,21 +189,21 @@ private void ThenTheConfigsAreMerged() fc.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(_globalConfig.GlobalConfiguration.ServiceDiscoveryProvider.Port); 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[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[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[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[1].UpstreamHost); diff --git a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestTests.cs b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestClaimToThingTests.cs similarity index 95% rename from test/Ocelot.UnitTests/Headers/AddHeadersToRequestTests.cs rename to test/Ocelot.UnitTests/Headers/AddHeadersToRequestClaimToThingTests.cs index 8e323bb29..2ad59ca55 100644 --- a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestTests.cs +++ b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestClaimToThingTests.cs @@ -1,151 +1,151 @@ -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using Moq; -using Ocelot.Configuration; -using Ocelot.Errors; -using Ocelot.Headers; -using Ocelot.Infrastructure.Claims.Parser; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using System.Net.Http; -using Ocelot.Request.Middleware; - -namespace Ocelot.UnitTests.Headers -{ - public class AddHeadersToRequestTests - { - private readonly AddHeadersToRequest _addHeadersToRequest; - private readonly Mock _parser; - private readonly DownstreamRequest _downstreamRequest; - private List _claims; - private List _configuration; - private Response _result; - private Response _claimValue; - - public AddHeadersToRequestTests() - { - _parser = new Mock(); - _addHeadersToRequest = new AddHeadersToRequest(_parser.Object); - _downstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); - } - - [Fact] - public void should_add_headers_to_downstreamRequest() - { - var claims = new List - { - new Claim("test", "data") - }; - - this.Given( - x => x.GivenConfigurationHeaderExtractorProperties(new List - { - new ClaimToThing("header-key", "", "", 0) - })) - .Given(x => x.GivenClaims(claims)) - .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) - .When(x => x.WhenIAddHeadersToTheRequest()) - .Then(x => x.ThenTheResultIsSuccess()) - .And(x => x.ThenTheHeaderIsAdded()) - .BDDfy(); - } - - [Fact] - public void should_replace_existing_headers_on_request() - { - this.Given( - x => x.GivenConfigurationHeaderExtractorProperties(new List - { - new ClaimToThing("header-key", "", "", 0) - })) - .Given(x => x.GivenClaims(new List - { - new Claim("test", "data") - })) - .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) - .And(x => x.GivenThatTheRequestContainsHeader("header-key", "initial")) - .When(x => x.WhenIAddHeadersToTheRequest()) - .Then(x => x.ThenTheResultIsSuccess()) - .And(x => x.ThenTheHeaderIsAdded()) - .BDDfy(); - } - - [Fact] - public void should_return_error() - { - this.Given( - x => x.GivenConfigurationHeaderExtractorProperties(new List - { - new ClaimToThing("", "", "", 0) - })) - .Given(x => x.GivenClaims(new List())) - .And(x => x.GivenTheClaimParserReturns(new ErrorResponse(new List - { - new AnyError() - }))) - .When(x => x.WhenIAddHeadersToTheRequest()) - .Then(x => x.ThenTheResultIsError()) - .BDDfy(); - } - - private void GivenClaims(List claims) - { - _claims = claims; - } - - private void GivenConfigurationHeaderExtractorProperties(List configuration) - { - _configuration = configuration; - } - - private void GivenThatTheRequestContainsHeader(string key, string value) - { - _downstreamRequest.Headers.Add(key, value); - } - - private void GivenTheClaimParserReturns(Response claimValue) - { - _claimValue = claimValue; - _parser - .Setup( - x => - x.GetValue(It.IsAny>(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns(_claimValue); - } - - private void WhenIAddHeadersToTheRequest() - { - _result = _addHeadersToRequest.SetHeadersOnDownstreamRequest(_configuration, _claims, _downstreamRequest); - } - - private void ThenTheResultIsSuccess() - { - _result.IsError.ShouldBe(false); - } - - private void ThenTheResultIsError() - { - _result.IsError.ShouldBe(true); - } - - private void ThenTheHeaderIsAdded() - { - var header = _downstreamRequest.Headers.First(x => x.Key == "header-key"); - header.Value.First().ShouldBe(_claimValue.Data); - } - - class AnyError : Error - { - public AnyError() - : base("blahh", OcelotErrorCode.UnknownError) - { - } - } - } -} +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using Moq; +using Ocelot.Configuration; +using Ocelot.Errors; +using Ocelot.Headers; +using Ocelot.Infrastructure.Claims.Parser; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using System.Net.Http; +using Ocelot.Request.Middleware; + +namespace Ocelot.UnitTests.Headers +{ + public class AddHeadersToRequestClaimToThingTests + { + private readonly AddHeadersToRequest _addHeadersToRequest; + private readonly Mock _parser; + private readonly DownstreamRequest _downstreamRequest; + private List _claims; + private List _configuration; + private Response _result; + private Response _claimValue; + + public AddHeadersToRequestClaimToThingTests() + { + _parser = new Mock(); + _addHeadersToRequest = new AddHeadersToRequest(_parser.Object); + _downstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); + } + + [Fact] + public void should_add_headers_to_downstreamRequest() + { + var claims = new List + { + new Claim("test", "data") + }; + + this.Given( + x => x.GivenConfigurationHeaderExtractorProperties(new List + { + new ClaimToThing("header-key", "", "", 0) + })) + .Given(x => x.GivenClaims(claims)) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .When(x => x.WhenIAddHeadersToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .And(x => x.ThenTheHeaderIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_replace_existing_headers_on_request() + { + this.Given( + x => x.GivenConfigurationHeaderExtractorProperties(new List + { + new ClaimToThing("header-key", "", "", 0) + })) + .Given(x => x.GivenClaims(new List + { + new Claim("test", "data") + })) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .And(x => x.GivenThatTheRequestContainsHeader("header-key", "initial")) + .When(x => x.WhenIAddHeadersToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .And(x => x.ThenTheHeaderIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_return_error() + { + this.Given( + x => x.GivenConfigurationHeaderExtractorProperties(new List + { + new ClaimToThing("", "", "", 0) + })) + .Given(x => x.GivenClaims(new List())) + .And(x => x.GivenTheClaimParserReturns(new ErrorResponse(new List + { + new AnyError() + }))) + .When(x => x.WhenIAddHeadersToTheRequest()) + .Then(x => x.ThenTheResultIsError()) + .BDDfy(); + } + + private void GivenClaims(List claims) + { + _claims = claims; + } + + private void GivenConfigurationHeaderExtractorProperties(List configuration) + { + _configuration = configuration; + } + + private void GivenThatTheRequestContainsHeader(string key, string value) + { + _downstreamRequest.Headers.Add(key, value); + } + + private void GivenTheClaimParserReturns(Response claimValue) + { + _claimValue = claimValue; + _parser + .Setup( + x => + x.GetValue(It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(_claimValue); + } + + private void WhenIAddHeadersToTheRequest() + { + _result = _addHeadersToRequest.SetHeadersOnDownstreamRequest(_configuration, _claims, _downstreamRequest); + } + + private void ThenTheResultIsSuccess() + { + _result.IsError.ShouldBe(false); + } + + private void ThenTheResultIsError() + { + _result.IsError.ShouldBe(true); + } + + private void ThenTheHeaderIsAdded() + { + var header = _downstreamRequest.Headers.First(x => x.Key == "header-key"); + header.Value.First().ShouldBe(_claimValue.Data); + } + + class AnyError : Error + { + public AnyError() + : base("blahh", OcelotErrorCode.UnknownError) + { + } + } + } +} diff --git a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestPlainTests.cs b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestPlainTests.cs new file mode 100644 index 000000000..69fd4f69e --- /dev/null +++ b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestPlainTests.cs @@ -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()); + } + + [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); + } + } +} diff --git a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs index cc65624c5..7b13cc6d0 100644 --- a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs @@ -27,7 +27,8 @@ public class HttpHeadersTransformationMiddlewareTests private readonly HttpHeadersTransformationMiddleware _middleware; private readonly DownstreamContext _downstreamContext; private OcelotRequestDelegate _next; - private readonly Mock _addHeaders; + private readonly Mock _addHeadersToResponse; + private readonly Mock _addHeadersToRequest; public HttpHeadersTransformationMiddlewareTests() { @@ -38,8 +39,11 @@ public HttpHeadersTransformationMiddlewareTests() _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; - _addHeaders = new Mock(); - _middleware = new HttpHeadersTransformationMiddleware(_next, _loggerFactory.Object, _preReplacer.Object, _postReplacer.Object, _addHeaders.Object); + _addHeadersToResponse = new Mock(); + _addHeadersToRequest = new Mock(); + _middleware = new HttpHeadersTransformationMiddleware( + _next, _loggerFactory.Object, _preReplacer.Object, + _postReplacer.Object, _addHeadersToResponse.Object, _addHeadersToRequest.Object); } [Fact] @@ -51,17 +55,24 @@ public void should_call_pre_and_post_header_transforms() .And(x => GivenTheHttpResponseMessageIs()) .When(x => WhenICallTheMiddleware()) .Then(x => ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly()) + .Then(x => ThenAddHeadersToRequestIsCalledCorrectly()) .And(x => ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly()) - .And(x => ThenAddHeadersIsCalledCorrectly()) + .And(x => ThenAddHeadersToResponseIsCalledCorrectly()) .BDDfy(); } - private void ThenAddHeadersIsCalledCorrectly() + private void ThenAddHeadersToResponseIsCalledCorrectly() { - _addHeaders + _addHeadersToResponse .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() { _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult();