From fa09e4cf7ac2377548176f00470235a6782d9755 Mon Sep 17 00:00:00 2001 From: Felix Boers Date: Sat, 14 Apr 2018 07:41:12 +0200 Subject: [PATCH] Support adding custom plaintext headers to downstream requests (#314) --- docs/features/headerstransformation.rst | 26 +- .../Builder/DownstreamReRouteBuilder.cs | 11 +- .../Creator/FileOcelotConfigurationCreator.cs | 3 +- .../Creator/HeaderFindAndReplaceCreator.cs | 20 +- .../Creator/HeaderTransformations.cs | 7 +- src/Ocelot/Configuration/DownstreamReRoute.cs | 5 +- src/Ocelot/Headers/AddHeadersToRequest.cs | 17 +- src/Ocelot/Headers/IAddHeadersToRequest.cs | 5 +- .../HttpHeadersTransformationMiddleware.cs | 13 +- .../FileConfigurationCreatorTests.cs | 3 +- .../HeaderFindAndReplaceCreatorTests.cs | 49 ++- ...> AddHeadersToRequestClaimToThingTests.cs} | 302 +++++++++--------- .../Headers/AddHeadersToRequestPlainTests.cs | 75 +++++ ...ttpHeadersTransformationMiddlewareTests.cs | 23 +- 14 files changed, 376 insertions(+), 183 deletions(-) rename test/Ocelot.UnitTests/Headers/{AddHeadersToRequestTests.cs => AddHeadersToRequestClaimToThingTests.cs} (95%) create mode 100644 test/Ocelot.UnitTests/Headers/AddHeadersToRequestPlainTests.cs diff --git a/docs/features/headerstransformation.rst b/docs/features/headerstransformation.rst index 6a3331411..544ae2b01 100644 --- a/docs/features/headerstransformation.rst +++ b/docs/features/headerstransformation.rst @@ -1,15 +1,31 @@ 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 configuration.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 configuration.json.. +If you want to add a header to your downstream response please add the following to a ReRoute in configuration.json. .. code-block:: json @@ -50,7 +66,7 @@ Add the following to a ReRoute in configuration.json in order to replace http:// }, Post Downstream Request -^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^ Add the following to a ReRoute in configuration.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This transformation will take place after Ocelot has received the response from the downstream service. 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/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index 821c0ed71..cee03813b 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -214,7 +214,8 @@ private DownstreamReRoute SetUpDownstreamReRoute(FileReRoute fileReRoute, FileGl .WithDownstreamHeaderFindAndReplace(hAndRs.Downstream) .WithUpstreamHost(fileReRoute.UpstreamHost) .WithDelegatingHandlers(fileReRoute.DelegatingHandlers) - .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) + .WithAddHeadersToDownstream(hAndRs.AddHeadersToDownstream) + .WithAddHeadersToUpstream(hAndRs.AddHeadersToUpstream) .Build(); return reRoute; 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 04dcc0c1b..479c3e28e 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -829,6 +829,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"); } } @@ -911,7 +912,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/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/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();